diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..2293b70a1 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,46 @@ +name: Benchmarks +on: + push: + branches: + - master + +permissions: + contents: write + deployments: write + +jobs: + benchmark: + name: Run benchmark + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Setup JDK + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' + - name: Build with Maven + run: | + mvn --batch-mode --update-snapshots verify -Dstyle.color=always -Dmaven.javadoc.skip=true -Pbenchmark + - name: Run benchmark + run: | + java -jar target/benchmarks.jar -wi 3 -i 3 -f 1 -rf json + - name: Store raw benchmark result as artifact + uses: actions/upload-artifact@v4 + with: + name: benchmark-result + path: jmh-result.json + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: JSON Schema Validator Benchmark + tool: 'jmh' + output-file-path: jmh-result.json + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t/github-action-not-triggering-gh-pages-upon-push/16096 + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: true + comment-on-alert: false + max-items-in-chart: 50 + summary-always: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..20d7a905e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ 8, 11, 17, 21 ] + name: Java ${{ matrix.java }} + steps: + - uses: actions/checkout@v4 + + - name: Setup JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + + - name: Build with Maven + run: mvn --batch-mode --update-snapshots verify -Dstyle.color=always + + - name: Upload coverage to Codecov + if: matrix.java == '8' + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b868ff45a..000000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -dist: xenial - -language: java - -cache: - directories: - - $HOME/.m2 - -jdk: - - openjdk8 - - openjdk11 - - oraclejdk11 - -after_success: - - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f99948f0f..c76f84673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Change Log All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). +This format is based on [Keep a Changelog](http://keepachangelog.com/). + +This project does not adhere to [Semantic Versioning](https://semver.org/) and minor version changes can have incompatible API changes. These incompatible API changes will largely affect those who have custom validator or walker implementations. Those who just use the library to validate using the standard JSON Schema Draft specifications may not need changes. ## [Unreleased] @@ -10,7 +11,611 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- fixes #532 Invalid (non-string) $schema produces NullPointerException +## 1.5.9- 2025-09-13 + +### Added + +### Changed + +- Fix idn-hostname format and update json schema test suite (#1191) Thanks @justin-tay +- Fix JoniRegularExpression compatibility issues with ECMA-262 (#1193) Thanks @justin-tay +- Fix time format validation (#1188) Thanks @justin-tay +- Fix eclipse junit issue due to m2e-core (#1189) Thanks @justin-tay +- Support readOnly and writeOnly for required validation (#1186) Thanks @justin-tay +- Spanish translations (#1183) Thanks @MickMonaghanGW +- Upgrade to ITU 1.14.0 (#1184) Thanks @ethlo +- Add mpenet/legba to list of librairies (#1181) Thanks @mpenet + +## 1.5.8- 2025-06-27 + +### Added + +### Changed + +- upgrade maven-gpg to 3.2.7 +- Fix walk with validate when node is null (#1169) Thanks @justin-tay +- add central-publishing-maven-plugin + + +## 1.5.7- 2025-05-23 + +### Added + +### Changed + +- upgrade nexus-staging verison +- Fix OverrideValidatorTest (#1159) Thanks @justin-tay +- update slf4j to 2.0.17 +- update jackson to 2.18.3 + + +## 1.5.6- 2025-02-19 + +### Added + +### Changed + +- Set requires static for optional and excludable dependencies (#1155) Thanks @justin-tay +- Fix NPE when walking a missing node that will have missing properties (#1152) Thanks @justin-tay +- Fix relative iris with colons (#1147) Thanks @justin-tay +- Fix explicit disabling of format assertions (#1145) Thanks @justin-tay + +## 1.5.5 - 2025-01-14 + +### Added + +### Changed + +- Fix custom error message for union type (#1138) Thanks @justin-tay +- Update itu 1.10.2 -> 1.10.3 (#1143) Thanks @MelvinFrohike + +## 1.5.4 - 2024-11-24 + +### Added + +### Changed + +- Fix nullable issue (#1134) Thanks @justin-tay +- fixes #1131 pom.xml scm url URI is invalid (#1132) +- Remove warning for exclusiveMinimum and exclusiveMaximum for Draft 4 (#1127) Thanks @justin-tay +- Clean up code (#1130) Thanks @jkosternl +- Test Code Cleanup (#1128) Thanks @jkosternl + +## 1.5.3 - 2024-10-31 + +### Added + +### Changed + +- Upgrade many libraries (#1122) Thanks @jkosternl +- Fix hasAdjacentKeywordInEvaluationPath optimisation (#1124) Thanks @justin-tay +- Correct URL to jsonschema-validation-output-machines.md documentation (#1121) Thanks @jkosternl + + +## 1.5.2 - 2024-09-19 + +### Added + +### Changed + +- Fix exclusiveMinimum and exclusiveMaximum for OpenAPI 3.0 (#1115) Thanks @justin-tay +- Bump jackson-databind from 2.17.1 to 2.17.2 (#1111) Thanks @justin-tay +- Bump io.undertow:undertow-core from 2.2.33.Final to 2.2.35.Final (#1110) Thanks @justin-tay +- Fix PatternValidator to not log for fail fast (#1106) Thanks @justin-tay + +## 1.5.1 - 2024-07-25 + +### Added + +### Changed + +- Fix enum validator messages for object, array and text nodes (#1095) Thanks @justin-tay +- Optimize hasAdjacentKeywordInEvaluationPath (#1092) Thanks @justin-tay + +## 1.5.0 - 2024-07-05 + +### Added + +### Changed + +- Improve performance and fixes (#1086) Thanks @justin-tay +- Fix activeDialect for custom meta schema (#1084) Thanks @justin-tay +- Remove alternate custom error message mechanism (#1083) Thanks @justin-tay +- Fix evaluationPath and schemaLocation in getSubSchema for number in fragment (#1081) Thanks @justin-tay +- Bump io.undertow:undertow-core from 2.2.31.Final to 2.2.33.Final (#1080) Thanks @justin-tay +- Add security considerations and mitigations (#1079) Thanks @justin-tay +- Fix oneOf when discriminator enabled but no discriminator (#1078) Thanks @justin-tay +- Fix json pointer with number in fragment (#1077) Thanks @justin-tay + + +## 1.4.3 - 2024-06-25 + +### Added + +### Changed + +- Fix schema location with hash in fragment (#1075) Thanks @justin-tay + +## 1.4.2 - 2024-06-21 + +### Added + +### Changed + +- Resolve javadoc warning messages #1073 +- Fix iri, iri-reference, uri and uri-reference (#1071) Thanks @justin-tay +- fixes #1072 Downgrade maven-source-plugin to 3.2.1 + +## 1.4.1 - 2024-06-20 + +### Added + +### Changed + +- Add builder for SchemaValidatorsConfig (#1068) Thanks @justin-tay +- Improve documentation on regular expressions (#1066) Thanks @justin-tay +- Adds JsonNodeReader (#1065) Thanks @justin-tay +- Bump jackson-databind from 2.17.0 to 2.17.1 (#1064) Thanks @justin-tay +- Deprecate validateAndCollect in favor of explicitly calling loadCollectors (#1063) Thanks @justin-tay +- Set initial array list size (#1062) Thanks @justin-tay +- Refactor to make more fields final (#1060) Thanks @justin-tay +- Deprecate config to disable unevaluatedItems and unevaluatedProperties evaluation as no longer needed (#1059) Thanks @justin-tay +- Add GraalJS regular expression and factory implementation (#1058) Thanks @justin-tay +- Allow customization of const message to include input value (#1057) Thanks @justin-tay +- Optimize logging by creating a debug flag (#1054) Thanks @justin-tay +- Release delegate held by cached supplier (#1056) Thanks @justin-tay +- Remove incorrect logic for oneOf, anyOf and properties (#1053) Thanks @justin-tay +- Detect invalid escape for ecmascript (#1049) Thanks @justin-tay +- Fix uri and uri-reference incorrect validation failure (#1052) Thanks @justin-tay +- Fix for incorrect validation failure for %-encoded '[' and ']' characters. (#1051) Thanks @4naesthetic +- Optimize getValueNodeType (#1048) Thanks @justin-tay +- Allow tracking of json node location information (#1046) Thanks @justin-tay +- Update example in javadoc for JsonMetaSchema builder (#1043) Thanks @khouari1 +- Validate allOf, oneOf and anyOf contains array (#1039) Thanks @justin-tay +- Update JSON Schema badges (#1041) Thanks @justin-tay +- Fix schema location for escaped json pointer (#1038) Thanks @justin-tay +- Allow customization of assertion for outputunit (#1033) Thanks @justin-tay +- change example.com connection error IOException (#1028) +- Fix broken link in README (#1029) Thanks @danielaparker +- Update dependency versions (#1023) Thanks @justin-tay +- upgrade slf4j to 2.0.13 +- upgrade jackson to 2.17.0 +- Add options to control caching of schemas (#1018) Thanks @justin-tay +- Update github actions (#1022) Thanks @justin-tay +- Fix code example in README.md (#1020) Thanks @tombentley +- Fix the broken link to if-then-else.json (#1014) Thanks @emmanuel-ferdman +- Changed defs to $defs in compatibility.md (#1013) Thanks @danielaparker +- Add OpenAPI JsonMetaSchema (#1011) Thanks @justin-tay +- Fix walk for if validator with validation (#1010) Thanks @justin-tay +- Fix 2012-12 typo (#1008) Thanks @justin-tay +- Check type validator is of correct class (#1003) Thanks @justin-tay +- Fix root classpath resolve (#1000) Thanks @justin-tay +- upgrade undertow to 2.2.31.Final (#998) +- Walk items schema instead of walking instance data (#993) Thanks @justin-tay + + +## 1.4.0 - 2024-03-16 + +### Added + +### Changed + +- Explicitly handle if the discriminator property value is null (#988) Thanks @justin-tay +- Refactor walk (#986) Thanks @justin-tay +- Fixes uri, uri-reference, iri, iri-reference formats and does iri to uri conversion (#983) Thanks @justin-tay +- Support custom vocabularies and unknown keyword and meta-schema handling (#980) Thanks @justin-tay +- Fix message (#975) Thanks @justin-tay +- Make ethlo excludable (#974) Thanks @justin-tay + +## 1.3.3 - 2024-02-19 + +### Added + +- Support GraalVM and refactor (#972) Thanks @justin-tay + +### Changed + +- Fixes for discriminator (#971) Thanks @justin-tay +- Fix validation messages (#969) Thanks @justin-tay +- Add unevaluatedProperties test (#968) Thanks @justin-tay +- Reduce memory usage and improve performance (#966) Thanks @justin-tay +- Set result at the end of schema processing (#963) Thanks @justin-tay + + +## 1.3.2 - 2024-02-07 + +### Added + +### Changed + + +- Update upgrading doc on fail fast (#961) Thanks @justin-tay +- Improve schema retrieval docs (#959) Thanks @justin-tay +- Refactor format validation (#958) Thanks @justin-tay +- Add test for OpenAPI 3.1 schema validation (#956) Thanks @justin-tay +- Fix patternProperties annotation (#955) Thanks @justin-tay +- Add test for type integer (#954) Thanks @justin-tay +- Improve vocabulary support (#953) Thanks @justin-tay +- Fix resolve (#952) Thanks @justin-tay +- Locale.ENGLISH should set. (#951) Thanks @justin-tay +- Fix issues with hierarchy output report (#947) Thanks @justin-tay +- Add test for type loose for array and update doc for behavior (#946) Thanks @justin-tay +- Support type loose for multipleOf validator (#945) Thanks @justin-tay +- Fix for required annotations for evaluation not collected (#944) Thanks @justin-tay + + +## 1.3.1 - 2024-01-31 + +### Added + +### Changed + +- fixes #942 Add annotation support refactor keywords to use annotations implement output formats. Thanks @justin-tay + +## 1.3.0 - 2024-01-26 + +### Added + +### Changed + +- fixes #934 update javadoc and a test case. +- fixes #931 Support Draft 2020-12 and refactor schema retrieval. Thanks @justin-tay +- fixes #930 Fix getSchema() anchor fragment lookup. Thanks @justin-tay +- fixes #929 Upgrade ITU library to version 1.8. Thanks @ethlo + +### Upgrade Guide + +With #931 implemented, it breaks the API. Users code might need to change in order to move to this version. + +## 1.2.0 - 2024-01-19 + +### Added + +### Changed + +- fixes #913 Update docs on CollectorContext. Thanks @justin-tay +- fixes #910 Refactor validation message generation. Thanks @justin-tay +- fixes #923 Basic test on URI create to improve coverage. Thanks @pradoshtnair +- fixes #915 Refactor of paths. Thanks @justin-tay +- fixes #922 Support schema resource. Thanks @justin-tay + +### Upgrade Guide + +With #915 and #922 implemented, it breaks the API. Users code might need to change in order to move to this version. + +## 1.1.0 - 2023-12-15 + +### Added + +### Changed + +- fixes #906 Cannot load JSON schemas with URN value in id field. Thanks @martin-sladecek +- upgrade logback to 1.4.14 +- fixes #896 Refactor to remove ThreadLocal usage. Thanks @justin-tay +- upgrade slf4j to 2.0.9 +- fixes #900 compile configuration is depricated. Thanks @saurvkmr +- fixes #898 Escape single quotes in validation messages. Thanks @sdurrenmatt +- fixes #888 Fix JDK regex support. Thanks @Stephan202 +- fixes #891 fix: make JsonSchemaFactory more thread-safe. Thanks @mpayne-coveo +- fixes #876 Adapt collector context documentation. Thanks @holgpar +- fixes #890 Added test cases for not allowed validator, Handled invalid keyword. Thanks @Ketul3012 +- fixes #887 Fix pl_PL message translations. Thanks @brempusz +- fixes #886 Fix invalid class passed to getLogger. Thanks @brempusz +- upgrade jackson to 2.15.3 +- fixes #883 docs clarify commons-lang3 exclusion only required for 1.0.81. Thanks @JonasGroeger +- fixes #866 Fix identation in example in walkers.md. Thanks @bpaquet + +### Upgrade Guide + +With #896 implemented, it breaks the API. Users code might need to change in order to move to this version. + + +## 1.0.87 - 2023-09-08 + +### Added + +- fixes #852 New resource bundle languages added for issue. Thanks @channaveer1 + +### Changed + +- fixes #837 Use correct namespace URI to pass XML validation. Thanks @@jbliznak + + +## 1.0.86 - 2023-07-05 + +### Added + +- fixes #825 Adds support for $recursiveAnchor and $recursiveRef. Thanks @fdutton + +### Changed + +- fixes #827 Stops unevaluatedProperties and unevaluatedItems being applied recursively. Thanks @aznan2 +- fixes #834 Always normalize uri keys of JsonSchemaFactory.jsonMetaSchemas on both read and write. Thanks @stacywsmith + + +## 1.0.85 - 2023-06-22 + +### Added + +- fixes #823 Adds support for writeOnly. Thanks @fdutton + +### Changed + +- fixes #819 Reverts Undertow version to 2.2.25.Final. Thanks @fdutton + + +## 1.0.84 - 2023-06-09 + +### Added + +- fixes #813 Adds support for walking if-then-else. Thanks @fdutton +- fixes #811 Adds support for walking dependentSchemas. Thanks @fdutton + +### Changed + +- fixes #816 Ignores fail-fast when evaluating a member of an applicator. Thanks @fdutton +- fixes #815 Corrects Java's failure to match an end anchor when immediately preceded by a quantifier. Thanks @fdutton +- fixes #812 Ensures context is reset after validating regardless of which method is used by the client. Thanks @fdutton +- fixes #809 Ignores siblings of $ref when dialect is Draft 4, 6 or 7. Thanks @fdutton +- fixes #807 Updates Jacoco configuration to ignore the embedded Apache code. Thanks @fdutton +- fixes #790 Simplifies how evaluated properties and array items are tracked. Thanks @fdutton +- fixes #806 Enables unit-tests for refRemote validation. Thanks @fdutton +- fixes #805 Corrects issue with deserializing JSON Schema Test Suite tests. Thanks @fdutton +- fixes #801 Support config param to disable custom messages from schema. Thanks @anjnerajat +- fixes #795 Supports fail-fast when a pattern does not match. Thanks @fdutton +- fixes #793 Updating jackson version to 2.15.2 + +## 1.0.83 - 2023-05-26 + +### Added +- fixes #779 Adds support for cross-draft validation. Thanks @fdutton +- fixes #777 Adds support for handling integer overflow. Thanks @fdutton + +### Changed + +- fixes #788 update JsonSchema to fix the javadoc issues +- fixes #787 Allows to override date-time and duration validators. Thanks @josejulio +- fixes #786 Allow walking of schema for items keyword when non-array node is provided. Thanks @anjnerajat +- fixes #783 Resolves improper anchoring of patternProperties. Thanks @fdutton + + +## 1.0.82 - 2023-05-20 + +### Added +- fixes #775 Adds support for validating idn-hostname and idn-email. Thanks @fdutton +- fixes #769 Add minContains / maxContains correct keywords. Thanks @vwuilbea-in +- fixes #768 Adds support for validating an IRI. Thanks @fdutton +- fixes #766 Supports iri-reference format validation. Thanks @fdutton +- fixes #764 Supports uri-reference format. Thanks @fdutton +- fixes #762 Supports relative-json-pointer validation. Thanks @fdutton +- fixes #758 Adds support for validating uri-template formats. Thanks @fdutton + +### Changed + +- fixes #760 Enables validation of json-pointer formats. Thanks @fdutton +- fixes #752 Bug fix for JSON Pointer parsing. Thanks @costas80 +- fixes #754 Resolves incomplete validation of unevaluatedProperties. Thanks @fdutton +- fixes #750 Escape double-quote in produced JSON Path expressions. Thanks @costas80 +- fixes #749 Enables unit-tests for the unevaluatedItems keyword. Thanks @fdutton +- fixes #686 Better localisation support. Thanks @costas80 +- fixes #741 Updates LICENSE and NOTICE to comply with section 4d of the Apache License. Thanks @fdutton +- fixes #738 Enables unit-tests for ECMA 262 regular expressions. Thanks @fdutton +- fixes #735 Enables unit-tests for 'not' keyword. Thanks @fdutton +- fixes #733 Updates tests from JSON Schema Test Suite. Thanks @fdutton + + +## 1.0.81 - 2023-04-30 + +### Added + +### Changed + +- fixes #731 Improves performance. Thanks @fdutton +- fixes #730 Removes need for network access when executing unit-tests. Thanks @fdutton +- fixes #728 Adds explicit Java module descriptor for JDK9+. Thanks @aalmiray +- fixes #725 custom uri fetcher doc. Thanks @michapojo +- update the contributors and sponsors +- fixes #720 Produces validation messages when oneOf has no valid schemas. Thanks @fdutton + +## 1.0.80 - 2023-04-18 + +### Added + +### Changed + +- fixes #709 Throw the exception as it is in I18nSupport. Thanks @rishabh413 +- update javadoc comments +- fixe #716 Adds support for unevaluatedProperties that uses a non-boolean schema. Thanks @fdutton +- fixes #714 Adds explicit support for tracking evaluated properties. Thanks @fdutton +- fixes #712 Corrects malformed tests. Thanks @fdutton +- fixes #710 Add support for the Draft 2020-12 interpretation of prefixItems. Thanks @fdutton +- fixes #708 remove System.exit from I18nSupport. +- fixes #707 Corrects treating 1.0 as an integer. Thanks @fdutton +- fixes #706 Adds support for validating regular expressions. Thanks @fdutton +- fixes #705 Adds support for email addresses containing an IPv6 literal value. Thanks @fdutton +- fixes #704 Adds support for validating leap seconds. Thanks @fdutton +- fixes #703 Corrects validation of duration and provides the option to validate against the ISO 8601 duration format. Thanks @fdutton +- fixes #720 Adds support for minContains and maxContains. Thanks @fdutton +- Updates tests from JSON Schema Test Suite. Thanks @fdutton +- fixes #698 avoid warning for additionalItems keyword +- fixes #697 Moves JSON Schema Test Suite to a separate test-resources folder. Thanks @fdutton +- fixes #696 add then and else to as NonValidationKeyword for v7 +- fixes #690 Uses JUnit dynamic tests to generate tests from specification files. Thanks @fdutton +- upgrade slf4j to 2.0.7 +- upgrade logback to 1.4.6. +- fixes #687 Return valid JSONPath (or JSONPointer) expressions for each ValidationMessage. Thanks @costas80 +- fixes #688 CI Bump used latest non-LTS Java: 19 -> 20. Thanks @valfirst + +## 1.0.79 - 2023-03-27 + +### Added + +### Changed + +- add a doc for metaschema validation +- fixes #682 Adds support for translating one URI into another. Thanks @fdutton +- fixes #604 add disabled test case to reproduce the NPE. +- fixes changing ReadOnlyValidator to use boolean property instead of array. Thanks @jorgesartori +- fixes #679 Add option to disable uri schema cache in JsonSchemaFactory. Thanks @Kaaviyan +- fixes #664 Avoid throwing exceptions and error-level logging. Thanks @CremboC +- fixes #675 Update README.md file. Thanks @hcnicepink +- fixes #672 add multiple language doc. +- fixes #671 Support time offsets in the time format. Thanks @JDziurlaj + + +## 1.0.78 - 2023-03-04 + +### Added + +### Changed + +- update the README.md to indicate that 202012 version is only partially supported. +- fixes #668 handle references to yaml sub-schemas. Thanks @danfelicetta-RL +- fixes #664 Provide/unify schema path for applicator schemas. Thanks @htdan +- fixes #666 Clarify usage of Apache commons lang in README.md. Thanks @loadedice +- fixes #663 Use full schema path to look up type validators for anyOf operator. Thanks @pshevche +- fixes #661 Make DependentRequired error message more helpful. Thanks @bernie-schelberg-mywave + + +## 1.0.77 - 2023-02-13 + +### Added + +- fixes #637 Setup CI based on GH Actions. Thanks @valfirst +- fixes #635 add persian language to json validator. Thanks @mahdimalverdi +### Changed + +- upgrade jackson to 2.14.2 +- fixes #651 Map BinaryNodes to type string. Thanks @k-oliver +- fixes #649 Improve logging performance. Thanks @valfirst +- fixes #648 Drop unused test dependency: Mockito. Thanks @valfirst +- fixes #647 Use Javadoc badge with dynamic version instead of plain link in README. Thanks @valfirst +- fixes #646 Add ability to detect spec version optionally. Thanks @valfirst +- fixes #645 Add MavenCentral badge to README. Thanks @valfirst +- fixes #644 Improve example of Gradle dependency in README. Thanks @valfirst +- fixes #643 Make sure all constants are static final. Thanks @valfirst +- fixes #642 Remove unused fields from JsonSchemaVersion. Thanks @valfirst +- fixes #641 Improve error messages on spec version detection. Thanks @valfirst +- fixes #640 Update build badge from README to point GH Actions CI. Thanks @valfirst +- fixes #639 Drop Travis CI config. Thanks @valfirst +- fixes #638 Restore code coverage calculation. Thanks @valfirst +- fixes #636 Adding tests for overriding error messages at schema level for individual keywords. Thanks @anjnerajat +- fixes #634 Quick fix for issue causing the wrong custom message to be used. Thanks @chaosape +- fixes #627 custom message for format. Thanks @vickyrathod + +## 1.0.76 - 2022-12-19 + +### Added + +### Changed + +- fixes #629 adding new walk method to start walking from a specific part of a given schema node. Thanks @prashanthjos + +## 1.0.75 - 2022-12-10 + +### Added + +### Changed + +- fixes #628 schema path fixes in oneOf,allOf and anyOf validators. Thanks @prashanthjos + +## 1.0.74 - 2022-12-02 + +### Added + +### Changed + +- upgrade undertow to 2.3.0.Final +- upgrade jackson to 2.14.0 +- fixes #620 upgrade commons-lang3 to 3.12.0 +- fixes #619 Add support for subschema references in getSchema. Thanks @aznan2 +- fixes #626 Correcting the oneOf,anyOf and allOf child schema validators. Thanks @prashanthjos +- fixes #617 Beautify code blocks. Thanks @limboinf +- fixes #614 Update spec version tests. Thanks @tuncererdogan +- fixes #613 Update the specversion.md and pom.xml. Thanks @tuncererdogan + +## 1.0.73 - 2022-09-19 + +### Added +- fixes #593 Add validator for duration format. Thanks @iouakrim + +### Changed + +- upgrade undertow to 2.2.18.Final to 2.2.19.Final +- fixes #563 Support adding custom message at attribute level. Thanks @makeItEasyQ +- fixes #606 Handle matched state in AnyOfValidator. Thanks @sgerke-1L +- fixes #598 Add italian translation. Thanks @sbernardo +- fixes #594 Remove commons lang as a compile time dependency. Thanks @agentgt +- fixes #592 Add NonValidationKeyword "else" on 201909 and 202012. Thanks @ionutalex88 + + +## 1.0.72 - 2022-07-17 + +### Added + +### Changed + +- upgrade undertow to 2.2.14.Final to 2.2.18.Final +- fixes #586 Add V202012 to SpecVersionDetector And JsonMetaSchema Thanks @Tuxzx +- fixes #585 Changed data type to preserve order of schema attributes. Thanks @sabarinathan590 + +## 1.0.71 - 2022-06-15 + +### Added + +### Changed + +- upgrade jackson to 2.13.3 +- upgrade logback to 1.2.11 +- upgrade slf4j to 1.7.36 +- fixes #575 upgrade com.ethlo.time:itu to version 1.7.0 Thanks @jody-mcdonnell +- fixes #380 Add support for draft 2020-12 Thanks @open-abbott +- fixes #582 Fix unevaluatedPropeties with patternProperties and type union. Thanks @jkevan + +## 1.0.70 - 2022-05-23 + +### Added + +- fixes #558 Add French translation for validation messages. Thanks @sebastienrospars + +### Changed + +- fixes #535 part 2 fix the same issue in AnyOfValidator. Thanks @AndreasALoew +- fixes #570 Upgrade javadoc plugin. Thanks @poorguy-tech +- fixes #569 Fix broken tests on non-english setup. Thanks @dreis2211 +- fixes #566 Remove unused variable in JsonNodeUtil. Thanks @dreis2211 +- fixes #565 Improve performance of URLFactory.create. Thanks @dreis2211 +- fixes #561 Prevent from throwing an exception when setting default values. Thanks @josejulio + +## 1.0.69 - 2022-04-18 + +### Added + +- fixes #534 Adding Unevaluated properties keyword. Thanks @prashanthjos + +### Changed + +- fixes #554 removed unnecessary check. Thanks @harishvashistha +- fixes #555 Setting default value even if that value is null. Thanks @harishvashistha +- fixes #544 Fixing unevaluated properties with larger test base. Thanks @prashanthjos +- fixes #552 Add schemaPath to ValidationMessage. Thanks @ymszzq +- fixes #541 Allow fetching properties from map with comparator. Thanks @0x4a616e + + +## 1.0.68 - 2022-03-27 + +### Added + +- fixes #534 Adding Unevaluated properties keyword. Thanks @prashanthjos + +### Changed + +- fixes #537 Fix oneOf bug. Thanks @RenegadeWizard and @sychlak +- fixes #511 Improve validation messages (German and default) Thanks @AndreasALoew +- fixes #539 Refactoring-code. Thanks @Sahil3198 +- fixes #532 Invalid (non-string) $schema produces NullPointerException. Thanks @christi-square +- fixes #530 Fixed a typo in the validators documentation. Thanks @jontrost +- fixes #529 Updates to German translation. Thanks @rustermi ## 1.0.67 - 2022-03-05 @@ -70,7 +675,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- fixes #456 OneOf only validate the first sub schema. This was a defect introduced in 1.0.58 and everyone should upgrade to 1.0.62 if you are using 1.0.58 to 1.0.61. +- fixes #456 OneOf only validate the first sub schema. This was a defect introduced in 1.0.58 and everyone should upgrade to 1.0.62 if you are using 1.0.58 to 1.0.61. ## 1.0.61 - 2021-10-09 @@ -100,7 +705,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## 1.0.58 - 2021-08-23 ### Added -- +- - fixes #439 add i18n support for ValidationMessage. Thanks @leaves615 - fixes #438 Adding custom message support in the schema. Thanks @adilath18 @@ -185,7 +790,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - fixes #387 Resolve the test case errors for TypeFactoryTest - fixes #385 Fixing concurrency and compilation issues. Thanks @prashanthjos - fixes #383 Nested oneOf gives incorrect validation error. Thanks @JonasProgrammer -- fixes #379 Add lossless narrowing convertion. Thanks @hkupty +- fixes #379 Add lossless narrowing convertion. Thanks @hkupty - fixes #378 Upgrade Jackson to 2.12.1 and Undertow to 2.2.4.Final ## 1.0.49 - 2021-02-17 @@ -383,7 +988,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Update description in pom.xml to match readme.md. Thanks @reftel +- Update description in pom.xml to match readme.md. Thanks @reftel - fixes #232 update meta schema URI to https - fixes #229 move the remotes to resource from draftv4 - fixes #228 support boolean schema in the dependencies validator @@ -760,7 +1365,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Fixes #50 Support custom meta schemas with custom keywords and formats. Thanks @kkalass -- Fixes #49 Use LinkedHashSets for ValidationMessages. Thanks @ehrmann +- Fixes #49 Use LinkedHashSets for ValidationMessages. Thanks @ehrmann - Fixes #48 Remove unnecessary todo. Thanks @ehrmann - Fixes #47 Change access modifiers in ValidationMessage. Thanks @ehrmann - Fixes #45 Added test case for loading schemas from classpath. Thanks @kenwa @@ -768,7 +1373,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## 0.1.11 - 2017-10-18 ### Added -- Fixes #43 Load reference schemas from classpath is supported. Thanks @kenwa +- Fixes #43 Load reference schemas from classpath is supported. Thanks @kenwa ### Changed diff --git a/LICENSE b/LICENSE index 8dada3eda..7a4a3ea24 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +199,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/NOTICE b/NOTICE index d86508dac..79b3f092a 100644 --- a/NOTICE +++ b/NOTICE @@ -1,122 +1,13 @@ - Json-schema-validator - ===================== +JSON Schema Validator +Copyright (c) 2016 and onwards Network New Technologies Inc. +// ------------------------------------------------------------------ +// NOTICE file corresponding to the section 4d of The Apache License, +// Version 2.0, in this case for +// ------------------------------------------------------------------ -Copyright (c) 2019 Network New Technologies Inc. +Apache Commons Validator +Copyright 2001-2023 The Apache Software Foundation -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - -========================================================================== -Third Party Dependencies -========================================================================== - -This project includes or depends on code from third party projects. - -The following are attribution notices from dependencies: - ------------------ -Undertow ------------------ - -JBoss, Home of Professional Open Source. -Copyright 2014 Red Hat, Inc., and individual contributors -as indicated by the @author tags. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ------------------ -Jackson ------------------ - -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 - ------------------ -SLF4J ------------------ - -Copyright (c) 2004-2017 QOS.ch -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -https://www.slf4j.org/license.html - ------------------ -Commons-lang3 ------------------ - -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ------------------ -Logback ------------------ - -Logback: the reliable, generic, fast and flexible logging framework. -Copyright (C) 1999-2015, QOS.ch. All rights reserved. - -This program and the accompanying materials are dual-licensed under -either the terms of the Eclipse Public License v1.0 as published by -the Eclipse Foundation - - or (per the licensee's choosing) - -under the terms of the GNU Lesser General Public License version 2.1 -as published by the Free Software Foundation. +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 998448c9b..c9c15a268 100644 --- a/README.md +++ b/README.md @@ -4,100 +4,572 @@ [Subreddit](https://www.reddit.com/r/lightapi/) | [Youtube](https://www.youtube.com/channel/UCHCRMWJVXw8iB7zKxF55Byw) | [Documentation](https://doc.networknt.com/library/json-schema-validator/) | -[Javadocs](https://www.javadoc.io/doc/com.networknt/json-schema-validator) | [Contribution Guide](https://doc.networknt.com/contribute/) | -[![Build Status](https://travis-ci.org/networknt/json-schema-validator.svg?branch=master)](https://travis-ci.org/networknt/json-schema-validator) [![codecov.io](https://codecov.io/github/networknt/json-schema-validator/coverage.svg?branch=master)](https://codecov.io/github/networknt/json-schema-validator?branch=master) +[![CI](https://github.com/networknt/json-schema-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/networknt/json-schema-validator/actions/workflows/ci.yml) +[![Maven Central](https://img.shields.io/maven-central/v/com.networknt/json-schema-validator.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.networknt%20a%3Ajson-schema-validator) +[![codecov.io](https://codecov.io/github/networknt/json-schema-validator/coverage.svg?branch=master)](https://codecov.io/github/networknt/json-schema-validator?branch=master) +[![Javadocs](http://www.javadoc.io/badge/com.networknt/json-schema-validator.svg)](https://www.javadoc.io/doc/com.networknt/json-schema-validator) +This is a Java implementation of the [JSON Schema Core Draft v4, v6, v7, v2019-09 and v2020-12](https://json-schema.org/specification) specification for JSON schema validation. This implementation supports [Customizing Meta-Schemas, Vocabularies, Keywords and Formats](doc/custom-meta-schema.md). -This is a Java implementation of the [JSON Schema Core Draft v4, v6, v7 and v2019-09](http://json-schema.org/latest/json-schema-core.html) specification for JSON schema validation. In addition, it also works for OpenAPI 3.0 request/response validation with some [configuration flags](doc/config.md). The default JSON parser is the [Jackson](https://github.com/FasterXML/jackson) that is the most popular one. As it is a key component in our [light-4j](https://github.com/networknt/light-4j) microservices framework to validate request/response against OpenAPI specification for [light-rest-4j](http://www.networknt.com/style/light-rest-4j/) and RPC schema for [light-hybrid-4j](http://www.networknt.com/style/light-hybrid-4j/) at runtime, performance is the most important aspect in the design. +In addition, [OpenAPI](doc/openapi.md) 3 request/response validation is supported with the use of the appropriate meta-schema. For users who want to collect information from a JSON node based on the schema, the [walkers](doc/walkers.md) can help. The JSON parser used is the [Jackson](https://github.com/FasterXML/jackson) parser. As it is a key component in our [light-4j](https://github.com/networknt/light-4j) microservices framework to validate request/response against OpenAPI specification for [light-rest-4j](http://www.networknt.com/style/light-rest-4j/) and RPC schema for [light-hybrid-4j](http://www.networknt.com/style/light-hybrid-4j/) at runtime, performance is the most important aspect in the design. + +## JSON Schema Specification compatibility + +[![Supported Dialects](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-networknt-json-schema-validator) +[![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/dialects/draft2020-12) +[![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/dialects/draft2019-09) +[![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/dialects/draft7) +[![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/dialects/draft6) +[![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft4.json)](https://bowtie.report/#/dialects/draft4) + +Information on the compatibility support for each version, including known issues, can be found in the [Compatibility with JSON Schema versions](doc/compatibility.md) document. + +Since [Draft 2019-09](https://json-schema.org/draft/2019-09/json-schema-validation#rfc.section.7) the `format` keyword only generates annotations by default and does not generate assertions. + +This behavior can be overridden to generate assertions by setting the `setFormatAssertionsEnabled` to `true` in `SchemaValidatorsConfig` or `ExecutionConfig`. + +## Upgrading to new versions + +This library can contain breaking changes in `minor` version releases that may require code changes. + +Information on notable or breaking changes when upgrading the library can be found in the [Upgrading to new versions](doc/upgrading.md) document. + +The [Releases](https://github.com/networknt/json-schema-validator/releases) page will contain information on the latest versions. + +## Comparing against other implementations + +The [JSON Schema Validation Comparison](https://github.com/creek-service/json-schema-validation-comparison) project from Creek has an informative [Comparison of JVM based Schema Validation Implementations](https://www.creekservice.org/json-schema-validation-comparison/) which compares both the functional and performance characteristics of a number of different Java implementations. +* [Functional comparison](https://www.creekservice.org/json-schema-validation-comparison/functional#summary-results-table) +* [Performance comparison](https://www.creekservice.org/json-schema-validation-comparison/performance#json-schema-test-suite-benchmark) + +The [Bowtie](https://github.com/bowtie-json-schema/bowtie) project has a [report](https://bowtie.report/) that compares functional characteristics of different implementations, including non-Java implementations, but does not do any performance benchmarking. ## Why this library #### Performance -It is the fastest Java JSON Schema Validator as far as I know. Here is the testing result compare with the other two open-source implementations. It is about 32 times faster than the Fge and five times faster than the Everit. +This should be the fastest Java JSON Schema Validator implementation. -fge: 7130ms +The following is the benchmark results from the [JSON Schema Validator Perftest](https://github.com/networknt/json-schema-validator-perftest) project that uses the [Java Microbenchmark Harness](https://github.com/openjdk/jmh). -everit-org: 1168ms +Note that the benchmark results are highly dependent on the input data workloads and schemas used for the validation. -networknt: 223ms +In this case this workload is using the Draft 4 specification and largely tests the performance of the evaluating the `properties` keyword. You may refer to [Results of performance comparison of JVM based JSON Schema Validation Implementations](https://www.creekservice.org/json-schema-validation-comparison/performance#json-schema-test-suite-benchmark) for benchmark results for more typical workloads -You can run the performance tests for three libraries from [https://github.com/networknt/json-schema-validator-perftest](https://github.com/networknt/json-schema-validator-perftest) +If performance is an important consideration, the specific sample workloads should be benchmarked, as there are different performance characteristics when certain keywords are used. For instance the use of the `unevaluatedProperties` or `unevaluatedItems` keyword will trigger annotation collection in the related validators, such as the `properties` or `items` validators, and annotation collection will adversely affect performance. -#### Parser +##### NetworkNT 1.4.1 -It uses Jackson that is the most popular JSON parser in Java. If you are using Jackson parser already in your project, it is natural to choose this library over others for schema validation. +``` +Benchmark Mode Cnt Score Error Units +NetworkntBenchmark.testValidate thrpt 10 8352.126 ± 61.870 ops/s +NetworkntBenchmark.testValidate:gc.alloc.rate thrpt 10 721.296 ± 5.342 MB/sec +NetworkntBenchmark.testValidate:gc.alloc.rate.norm thrpt 10 90560.013 ± 0.001 B/op +NetworkntBenchmark.testValidate:gc.count thrpt 10 61.000 counts +NetworkntBenchmark.testValidate:gc.time thrpt 10 68.000 ms +``` + +###### Everit 1.14.1 + +``` +Benchmark Mode Cnt Score Error Units +EveritBenchmark.testValidate thrpt 10 3775.453 ± 44.023 ops/s +EveritBenchmark.testValidate:gc.alloc.rate thrpt 10 1667.345 ± 19.437 MB/sec +EveritBenchmark.testValidate:gc.alloc.rate.norm thrpt 10 463104.030 ± 0.003 B/op +EveritBenchmark.testValidate:gc.count thrpt 10 140.000 counts +EveritBenchmark.testValidate:gc.time thrpt 10 158.000 ms +``` + +#### Functionality + +This implementation is tested against the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite). As tests are continually added to the suite, these test results may not be current. + +| Implementations | Overall | DRAFT_03 | DRAFT_04 | DRAFT_06 | DRAFT_07 | DRAFT_2019_09 | DRAFT_2020_12 | +|-----------------|-------------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------------| +| NetworkNt | pass: r:4803 (100.0%) o:2372 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | | pass: r:610 (100.0%) o:251 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:822 (100.0%) o:318 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:906 (100.0%) o:541 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1220 (100.0%) o:625 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1245 (100.0%) o:637 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | + +* Note that this uses the `JoniRegularExpressionFactory` for the `pattern` and `format` `regex` tests. + +#### Jackson Parser + +This library uses [Jackson](https://github.com/FasterXML/jackson) which is a Java JSON parser that is widely used in other projects. If you are already using the Jackson parser in your project, it is natural to choose this library over others for schema validation. #### YAML Support -The library works with JSON and YAML on both schema definitions and input data. +The library works with JSON and YAML on both schema definitions and input data. #### OpenAPI Support -The OpenAPI 3.0 specification is using JSON schema to validate the request/response, but there are some differences. With a configuration file, you can enable the library to work with OpenAPI 3.0 validation. +The OpenAPI 3.0 specification is using JSON schema to validate the request/response, but there are some differences. With a configuration file, you can enable the library to work with OpenAPI 3.0 validation. -#### Dependency +#### Minimal Dependencies -Following the design principle of the Light Platform, this library has minimum dependencies to ensure there are no dependency conflicts when using it. +Following the design principle of the Light Platform, this library has minimal dependencies to ensure there are no dependency conflicts when using it. -Here are the dependencies. +##### Required Dependencies + +The following are the dependencies that will automatically be included when this library is included. + +```xml + + + org.slf4j + slf4j-api + ${version.slf4j} + -``` + com.fasterxml.jackson.core jackson-databind ${version.jackson} - org.slf4j - slf4j-api - ${version.slf4j} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${version.jackson} - org.apache.commons - commons-lang3 - ${version.common-lang3} + + com.ethlo.time + itu + ${version.itu} + +``` + +##### Optional Dependencies + +The following are the optional dependencies that may be required for certain options. + +These are not automatically included and setting the relevant option without adding the library will result in a `ClassNotFoundException`. + +```xml + + + + + org.graalvm.js + js + ${version.graaljs} + + + + + + + org.jruby.joni + joni + ${version.joni} + +``` + +##### Excludable Dependencies + +The following are required dependencies that are automatically included, but can be explicitly excluded if they are not required. + +The YAML dependency can be excluded if this is not required. Attempting to process schemas or input that are YAML will result in a `ClassNotFoundException`. + +```xml + + com.networknt + json-schema-validator + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + +``` + +The Ethlo Time dependency can be excluded if accurate validation of the `date-time` format is not required. The `date-time` format will then use `java.time.OffsetDateTime` to determine if the `date-time` is valid . + +```xml + + com.networknt + json-schema-validator + + + com.ethlo.time + itu + + ``` #### Community -This library is very active with a lot of contributors. New features and bug fixes are handled quickly by the team members. Because it is an essential dependency of the [light-4j](https://github.com/networknt/light-4j) framework in the same GitHub organization, it will be evolved and maintained along with the framework. +This library is very active with a lot of contributors. New features and bug fixes are handled quickly by the team members. Because it is an essential dependency of the [light-4j](https://github.com/networknt/light-4j) framework in the same GitHub organization, it will be evolved and maintained along with the framework. ## Prerequisite The library supports Java 8 and up. If you want to build from the source code, you need to install JDK 8 locally. To support multiple version of JDK, you can use [SDKMAN](https://www.networknt.com/tool/sdk/) -## Dependency +## Usage -This package is available on Maven central. +### Adding the dependency -Maven: +This package is available on Maven central. + +#### Maven: ```xml com.networknt json-schema-validator - 1.0.67 + 1.5.9 ``` -Gradle: +#### Gradle: -``` +```java dependencies { - compile(group: "com.networknt", name: "json-schema-validator", version: "1.0.67"); + implementation(group: 'com.networknt', name: 'json-schema-validator', version: '1.5.9'); } ``` -For the latest version, please check the [release](https://github.com/networknt/json-schema-validator/releases) page. +### Validating inputs against a schema + +The following example demonstrates how inputs are validated against a schema. It comprises the following steps. + +* Creating a schema factory with the default schema dialect and how the schemas can be retrieved. + * Configuring mapping the `$id` to a retrieval URI using `schemaMappers`. + * Configuring how the schemas are loaded using the retrieval URI using `schemaLoaders`. + For instance a `Map schemas` containing a mapping of retrieval URI to schema data as a `String` can by configured using `builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas))`. This also accepts a `Function schemaRetrievalFunction`. +* Creating a configuration for controlling validator behavior. +* Loading a schema from a schema location along with the validator configuration. +* Using the schema to validate the data along with setting any execution specific configuration like for instance the locale or whether format assertions are enabled. + +```java +// This creates a schema factory that will use Draft 2020-12 as the default if $schema is not specified +// in the schema data. If $schema is specified in the schema data then that schema dialect will be used +// instead and this version is ignored. +JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> + // This creates a mapping from $id which starts with https://www.example.org/ to the retrieval URI classpath:schema/ + builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.example.org/", "classpath:schema/")) +); + +SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder(); +// By default the JDK regular expression implementation which is not ECMA 262 compliant is used +// Note that setting this requires including optional dependencies +// builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance()); +// builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance()); +SchemaValidatorsConfig config = builder.build(); + +// Due to the mapping the schema will be retrieved from the classpath at classpath:schema/example-main.json. +// If the schema data does not specify an $id the absolute IRI of the schema location will be used as the $id. +JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.example.org/example-main.json"), config); +String input = "{\r\n" + + " \"main\": {\r\n" + + " \"common\": {\r\n" + + " \"field\": \"invalidfield\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + +Set assertions = schema.validate(input, InputFormat.JSON, executionContext -> { + // By default since Draft 2019-09 the format keyword only generates annotations and not assertions + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true); +}); +``` + +### Validating a schema against a meta-schema + +The following example demonstrates how a schema is validated against a meta-schema. + +This is actually the same as validating inputs against a schema except in this case the input is the schema and the schema used is the meta-schema. + +Note that the meta-schemas for Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft 2020-12 are bundled with the library and these classpath resources will be used by default. + +```java +JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + +SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder(); +// By default the JDK regular expression implementation which is not ECMA 262 compliant is used +// Note that setting this requires including optional dependencies +// builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance()); +// builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance()); +SchemaValidatorsConfig config = builder.build(); + +// Due to the mapping the meta-schema will be retrieved from the classpath at classpath:draft/2020-12/schema. +JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V202012), config); +String input = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"key\": {\r\n" + + " \"title\" : \"My key\",\r\n" + + " \"type\": \"invalidtype\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; +Set assertions = schema.validate(input, InputFormat.JSON, executionContext -> { + // By default since Draft 2019-09 the format keyword only generates annotations and not assertions + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true); +}); +``` +### Results and output formats + +#### Results + +The following types of results are generated by the library. + +| Type | Description +|-------------|------------------- +| Assertions | Validation errors generated by a keyword on a particular input data instance. This is generally described in a `ValidationMessage` or in a `OutputUnit`. Note that since Draft 2019-09 the `format` keyword no longer generates assertions by default and instead generates only annotations unless configured otherwise using a configuration option or by using a meta-schema that uses the appropriate vocabulary. +| Annotations | Additional information generated by a keyword for a particular input data instance. This is generally described in a `OutputUnit`. Annotation collection and reporting is turned off by default. Annotations required by keywords such as `unevaluatedProperties` or `unevaluatedItems` are always collected for evaluation purposes and cannot be disabled but will not be reported unless configured to do so. + +The following information is used to describe both types of results. + +| Type | Description +|-------------------|------------------- +| Evaluation Path | This is the set of keys from the root through which evaluation passes to reach the schema for evaluating the instance. This includes `$ref` and `$dynamicRef`. eg. ```/properties/bar/$ref/properties/bar-prop``` +| Schema Location | This is the canonical IRI of the schema plus the JSON pointer fragment to the schema that was used for evaluating the instance. eg. ```https://json-schema.org/schemas/example#/$defs/bar/properties/bar-prop``` +| Instance Location | This is the JSON pointer fragment to the instance data that was being evaluated. eg. ```/bar/bar-prop``` + +Assertions contains the following additional information + +| Type | Description +|-------------------|------------------- +| Message | The validation error message. +| Code | The error code. +| Message Key | The message key used for generating the message for localization. +| Arguments | The arguments used for generating the message. +| Type | The keyword that generated the message. +| Property | The property name that caused the validation error for example for the `required` keyword. Note that this is not part of the instance location as that points to the instance node. +| Schema Node | The `JsonNode` pointed to by the Schema Location. This is the schema data that caused the input data to fail. It is possible to get the location information by configuring the `JsonSchemaFactory` with a `JsonNodeReader` that uses the `LocationJsonNodeFactoryFactory` and using `JsonNodes.tokenLocationOf(schemaNode)`. +| Instance Node | The `JsonNode` pointed to by the Instance Location. This is the input data that failed validation. It is possible to get the location information by configuring the `JsonSchemaFactory` with a `JsonNodeReader` that uses the `LocationJsonNodeFactoryFactory` and using `JsonNodes.tokenLocationOf(instanceNode)`. +| Error | The error. +| Details | Additional details that can be set by custom keyword validator implementations. This is not used by the library. + +Annotations contains the following additional information + +| Type | Description +|-------------------|------------------- +| Value | The annotation value generated + +##### Line and Column Information + +The library can be configured to store line and column information in the `JsonNode` instances for the instance and schema nodes. This will adversely affect performance and is not configured by default. + +This is done by configuring a `JsonNodeReader` that uses the `LocationJsonNodeFactoryFactory`on the `JsonSchemaFactory`. The `JsonLocation` information can then be retrieved using `JsonNodes.tokenLocationOf(jsonNode)`. + +```java +String schemaData = "{\r\n" + + " \"$id\": \"https://schema/myschema\",\r\n" + + " \"properties\": {\r\n" + + " \"startDate\": {\r\n" + + " \"format\": \"date\",\r\n" + + " \"minLength\": 6\r\n" + + " }\r\n" + + " }\r\n" + + "}"; +String inputData = "{\r\n" + + " \"startDate\": \"1\"\r\n" + + "}"; +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, + builder -> builder.jsonNodeReader(JsonNodeReader.builder().locationAware().build())); +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build(); +JsonSchema schema = factory.getSchema(schemaData, InputFormat.JSON, config); +Set messages = schema.validate(inputData, InputFormat.JSON, executionContext -> { + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true); +}); +List list = messages.stream().collect(Collectors.toList()); +ValidationMessage format = list.get(0); +JsonLocation formatInstanceNodeTokenLocation = JsonNodes.tokenLocationOf(format.getInstanceNode()); +JsonLocation formatSchemaNodeTokenLocation = JsonNodes.tokenLocationOf(format.getSchemaNode()); +ValidationMessage minLength = list.get(1); +JsonLocation minLengthInstanceNodeTokenLocation = JsonNodes.tokenLocationOf(minLength.getInstanceNode()); +JsonLocation minLengthSchemaNodeTokenLocation = JsonNodes.tokenLocationOf(minLength.getSchemaNode()); + +assertEquals("format", format.getType()); +assertEquals("date", format.getSchemaNode().asText()); +assertEquals(5, formatSchemaNodeTokenLocation.getLineNr()); +assertEquals(17, formatSchemaNodeTokenLocation.getColumnNr()); +assertEquals("1", format.getInstanceNode().asText()); +assertEquals(2, formatInstanceNodeTokenLocation.getLineNr()); +assertEquals(16, formatInstanceNodeTokenLocation.getColumnNr()); +assertEquals("minLength", minLength.getType()); +assertEquals("6", minLength.getSchemaNode().asText()); +assertEquals(6, minLengthSchemaNodeTokenLocation.getLineNr()); +assertEquals(20, minLengthSchemaNodeTokenLocation.getColumnNr()); +assertEquals("1", minLength.getInstanceNode().asText()); +assertEquals(2, minLengthInstanceNodeTokenLocation.getLineNr()); +assertEquals(16, minLengthInstanceNodeTokenLocation.getColumnNr()); +assertEquals(16, minLengthInstanceNodeTokenLocation.getColumnNr()); +``` + + +#### Output formats + +This library implements the Flag, List and Hierarchical output formats defined in the [Specification for Machine-Readable Output for JSON Schema Validation and Annotation](https://github.com/json-schema-org/json-schema-spec/blob/8270653a9f59fadd2df0d789f22d486254505bbe/jsonschema-validation-output-machines.md). + +The List and Hierarchical output formats are particularly helpful for understanding how the system arrived at a particular result. + +| Output Format | Description +|-------------------|------------------- +| Default | Generates the list of assertions. +| Boolean | Returns `true` if the validation is successful. Note that the fail fast option is turned on by default for this output format. +| Flag | Returns an `OutputFlag` object with `valid` having `true` if the validation is successful. Note that the fail fast option is turned on by default for this output format. +| List | Returns an `OutputUnit` object with `details` with a list of `OutputUnit` objects with the assertions and annotations. Note that annotations are not collected by default and it has to be enabled as it will impact performance. +| Hierarchical | Returns an `OutputUnit` object with a hierarchy of `OutputUnit` objects for the evaluation path with the assertions and annotations. Note that annotations are not collected by default and it has to be enabled as it will impact performance. + +The following example shows how to generate the hierarchical output format with annotation collection and reporting turned on and format assertions turned on. + +```java +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); +SchemaValidatorsConfig config = SchemaValidatorsConfig().builder().formatAssertionsEnabled(true).build(); +JsonSchema schema = factory.getSchema(SchemaLocation.of("https://json-schema.org/schemas/example"), config); + +OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { + executionContext.getExecutionConfig().setAnnotationCollectionEnabled(true); + executionContext.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true); +}); +``` +The following is sample output from the Hierarchical format. + +```json +{ + "valid" : false, + "evaluationPath" : "", + "schemaLocation" : "https://json-schema.org/schemas/example#", + "instanceLocation" : "", + "droppedAnnotations" : { + "properties" : [ "foo", "bar" ], + "title" : "root" + }, + "details" : [ { + "valid" : false, + "evaluationPath" : "/properties/foo/allOf/0", + "schemaLocation" : "https://json-schema.org/schemas/example#/properties/foo/allOf/0", + "instanceLocation" : "/foo", + "errors" : { + "required" : "required property 'unspecified-prop' not found" + } + }, { + "valid" : false, + "evaluationPath" : "/properties/foo/allOf/1", + "schemaLocation" : "https://json-schema.org/schemas/example#/properties/foo/allOf/1", + "instanceLocation" : "/foo", + "droppedAnnotations" : { + "properties" : [ "foo-prop" ], + "title" : "foo-title", + "additionalProperties" : [ "foo-prop", "other-prop" ] + }, + "details" : [ { + "valid" : false, + "evaluationPath" : "/properties/foo/allOf/1/properties/foo-prop", + "schemaLocation" : "https://json-schema.org/schemas/example#/properties/foo/allOf/1/properties/foo-prop", + "instanceLocation" : "/foo/foo-prop", + "errors" : { + "const" : "must be a constant value 1" + }, + "droppedAnnotations" : { + "title" : "foo-prop-title" + } + } ] + }, { + "valid" : false, + "evaluationPath" : "/properties/bar/$ref", + "schemaLocation" : "https://json-schema.org/schemas/example#/$defs/bar", + "instanceLocation" : "/bar", + "droppedAnnotations" : { + "properties" : [ "bar-prop" ], + "title" : "bar-title" + }, + "details" : [ { + "valid" : false, + "evaluationPath" : "/properties/bar/$ref/properties/bar-prop", + "schemaLocation" : "https://json-schema.org/schemas/example#/$defs/bar/properties/bar-prop", + "instanceLocation" : "/bar/bar-prop", + "errors" : { + "minimum" : "must have a minimum value of 10" + }, + "droppedAnnotations" : { + "title" : "bar-prop-title" + } + } ] + } ] +} +``` + +## Configuration + +### Execution Configuration + +| Name | Description | Default Value +|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------- +| `annotationCollectionEnabled` | Controls whether annotations are collected during processing. Note that collecting annotations will adversely affect performance. | `false` +| `annotationCollectionFilter` | The predicate used to control which keyword to collect and report annotations for. This requires `annotationCollectionEnabled` to be `true`. | `keyword -> false` +| `locale` | The locale to use for generating messages in the `ValidationMessage`. Note that this value is copied from `SchemaValidatorsConfig` for each execution. | `Locale.getDefault()` +| `failFast` | Whether to return failure immediately when an assertion is generated. Note that this value is copied from `SchemaValidatorsConfig` for each execution but is automatically set to `true` for the Boolean and Flag output formats. | `false` +| `formatAssertionsEnabled` | The default is to generate format assertions from Draft 4 to Draft 7 and to only generate annotations from Draft 2019-09. Setting to `true` or `false` will override the default behavior. | `null` +| `debugEnabled` | Controls whether debug logging is enabled for logging the node information when processing. Note that this will generate a lot of logs that will affect performance. | `false` + +### Schema Validators Configuration + +| Name | Description | Default Value +|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------- +| `applyDefaultsStrategy` | The strategy for applying defaults when walking when missing or null nodes are encountered. | `ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY` +| `cacheRefs` | Whether the schemas loaded from refs will be cached and reused for subsequent runs. Setting this to `false` will affect performance but may be neccessary to prevent high memory usage for the cache if multiple nested applicators like `anyOf`, `oneOf` and `allOf` are used. | `true` +| `discriminatorKeywordEnabled` | Whether the `discriminator` keyword is handled according to OpenAPI 3. | `false` +| `errorMessageKeyword` | The keyword to use for custom error messages in the schema. If not set this features is disabled. This is typically set to `errorMessage` or `message`. | `null` +| `executionContextCustomizer` | This can be used to customize the `ExecutionContext` generated by the `JsonSchema` for each validation run. | `null` +| `failFast` | Whether to return failure immediately when an assertion is generated. | `false` +| `formatAssertionsEnabled` | The default is to generate format assertions from Draft 4 to Draft 7 and to only generate annotations from Draft 2019-09. Setting to `true` or `false` will override the default behavior. | `null` +| `javaSemantics` | Whether java semantics is used for the `type` keyword. | `false` +| `locale` | The locale to use for generating messages in the `ValidationMessage`. | `Locale.getDefault()` +| `losslessNarrowing` | Whether lossless narrowing is used for the `type` keyword. | `false` +| `messageSource` | This is used to retrieve the locale specific messages. | `DefaultMessageSource.getInstance()` +| `nullableKeywordEnabled` | Whether the `nullable` keyword is handled according to OpenAPI 3.0. This affects the `enum` and `type` keywords. | `false` +| `pathType` | The path type to use for reporting the instance location and evaluation path. Set to `PathType.JSON_PATH` to use JSON Path. | `PathType.JSON_POINTER` +| `preloadJsonSchema` | Whether the schema will be preloaded before processing any input. This will use memory but the execution of the validation will be faster. | `true` +| `preloadJsonSchemaRefMaxNestingDepth` | The max depth of the evaluation path to preload when preloading refs. | `40` +| `readOnly` | Whether schema is read only. This affects the `readOnly` keyword. | `null` +| `regularExpressionFactory` | The factory to use to create regular expressions for instance `JoniRegularExpressionFactory` or `GraalJSRegularExpressionFactory`. This requires the dependency to be manually added to the project or a `ClassNotFoundException` will be thrown. | `JDKRegularExpressionFactory.getInstance()` +| `schemaIdValidator` | This is used to customize how the `$id` values are validated. Note that the default implementation allows non-empty fragments where no base IRI is specified and also allows non-absolute IRI `$id` values in the root schema. | `JsonSchemaIdValidator.DEFAULT` +| `strict` | This is set whether keywords are strict in their validation. What this does depends on the individual validators. | +| `typeLoose` | Whether types are interpreted in a loose manner. If set to true, a single value can be interpreted as a size 1 array. Strings may also be interpreted as number, integer or boolean. | `false` +| `writeOnly` | Whether schema is write only. This affects the `writeOnly` keyword. | `null` + +## Performance Considerations + +When the library creates a schema from the schema factory, it creates a distinct validator instance for each location on the evaluation path. This means if there are different `$ref` that reference the same schema location, different validator instances are created for each evaluation path. + +When the schema is created, the library will by default automatically preload all the validators needed and resolve references. This can be disabled with the `preloadJsonSchema` option in the `SchemaValidatorsConfig`. At this point, no exceptions will be thrown if a reference cannot be resolved. If there are references that are cyclic, only the first cycle will be preloaded. If you wish to ensure that remote references can all be resolved, the `initializeValidators` method needs to be called on the `JsonSchema` which will throw an exception if there are references that cannot be resolved. + +Instances for `JsonSchemaFactory` and the `JsonSchema` created from it are designed to be thread-safe provided its configuration is not modified and should be cached and reused. Not reusing the `JsonSchema` means that the schema data needs to be repeated parsed with validator instances created and references resolved. When references are resolved, the validators created will be cached. For schemas that have deeply nested references, the memory needed for the validators may be very high, in which case the caching may need to be disabled using the `cacheRefs` option in the `SchemaValidatorsConfig`. Disabling this will mean the validators from the references need to be re-created for each validation run which will impact performance. + +Collecting annotations will adversely affect validation performance. + +The earlier draft specifications contain less keywords that can potentially impact performance. For instance the use of the `unevaluatedProperties` or `unevaluatedItems` keyword will trigger annotation collection in the related validators, such as the `properties` or `items` validators. + +This does not mean that using a schema with a later draft specification will automatically cause a performance impact. For instance, the `properties` validator will perform checks to determine if annotations need to be collected, and checks if the meta-schema contains the `unevaluatedProperties` keyword and whether the `unevaluatedProperties` keyword exists adjacent the evaluation path. + +## Security Considerations + +The library assumes that the schemas being loaded are trusted. This security model assumes the use case where the schemas are bundled with the application on the classpath. + +| Issue | Description | Mitigation +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------- +| Schema Loading | The library by default will load schemas from the classpath and over the internet if needed. | A `DisallowSchemaLoader` can be configured to not allow schema retrieval. Alternatively an `AllowSchemaLoader` can be configured to restrict the retrieval IRIs that are allowed. +| Schema Caching | The library by default preloads and caches references when loading schemas. While there is a max nesting depth when preloading schemas it is still possible to construct a schema that has a fan out that consumes a lot of memory from the server. | Set `cacheRefs` option in `SchemaValidatorsConfig` to false. +| Regular Expressions | The library does not validate if a given regular expression is susceptable to denial of service ([ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)). | An `AllowRegularExpressionFactory` can be configured to perform validation on the regular expressions that are allowed. +| Validation Errors | The library by default attempts to return all validation errors. The use of applicators such as `allOf` with a large number of schemas may result in a large number of validation errors taking up memory. | Set `failFast` option in `SchemaValidatorsConfig` to immediately return when the first error is encountered. The `OutputFormat.BOOLEAN` or `OutputFormat.FLAG` also can be used. ## [Quick Start](doc/quickstart.md) +## [Customizing Schema Retrieval](doc/schema-retrieval.md) + +## [Customizing Meta-Schemas, Vocabularies, Keywords and Formats](doc/custom-meta-schema.md) + +## [OpenAPI Specification](doc/openapi.md) + ## [Validators](doc/validators.md) ## [Configuration](doc/config.md) @@ -106,65 +578,62 @@ For the latest version, please check the [release](https://github.com/networknt/ ## [YAML Validation](doc/yaml.md) -## [Schema Mapping](doc/schema-map.md) - -## [Customized URIFetcher](doc/cust-fetcher.md) - -## [Customized MetaSchema](doc/cust-meta.md) - ## [Collector Context](doc/collector-context.md) ## [JSON Schema Walkers and WalkListeners](doc/walkers.md) -## [ECMA-262 Regex](doc/ecma-262.md) - -## [Custom Message](doc/cust-msg.md) +## [Regular Expressions](doc/ecma-262.md) -## Known issues +## [Custom Error Messages](doc/cust-msg.md) -I have just updated the test suites from the [official website](https://github.com/json-schema-org/JSON-Schema-Test-Suite) as the old ones were copied from another Java validator. Now there are several issues that need to be addressed. All of them are edge cases, in my opinion, but need to be investigated. As my old test suites were inherited from another Java JSON Schema Validator, I guess other Java Validator would have the same issues as these issues are in the Java language itself. +## [Multiple Language](doc/multiple-language.md) -[#7](https://github.com/networknt/json-schema-validator/issues/7) +## [MetaSchema Validation](doc/metaschema-validation.md) -[#5](https://github.com/networknt/json-schema-validator/issues/5) +## [Validating RFC 3339 durations](doc/duration.md) ## Projects -The [light-rest-4j](https://github.com/networknt/light-rest-4j), [light-graphql-4j](https://github.com/networknt/light-graphql-4j) and [light-hybrid-4j](https://github.com/networknt/light-hybrid-4j) use this library to validate the request and response based on the specifications. If you are using other frameworks like Spring Boot, you can use the [OpenApiValidator](https://github.com/mservicetech/openapi-schema-validation), a generic OpenAPI 3.0 validator based on the OpenAPI 3.0 specification. +The [light-rest-4j](https://github.com/networknt/light-rest-4j), [light-graphql-4j](https://github.com/networknt/light-graphql-4j) and [light-hybrid-4j](https://github.com/networknt/light-hybrid-4j) use this library to validate the request and response based on the specifications. If you are using other frameworks like Spring Boot, you can use the [OpenApiValidator](https://github.com/mservicetech/openapi-schema-validation), a generic OpenAPI 3.0 validator based on the OpenAPI 3.0 specification. If you have a project using this library, please submit a PR to add your project below. +* [mpenet/legba](https://github.com/mpenet/legba/) - OpenAPI service library for clojure, adhering to the [RING spec](https://github.com/ring-clojure/ring) + ## Contributors -Thanks to the following people who have contributed to this project. If you are using this library, please consider to be a sponsor for one of the contributors. +Thanks to the following people who have contributed to this project. If you are using this library, please consider to be a sponsor for one of the contributors. [@stevehu](https://github.com/sponsors/stevehu) -[@jiachen1120](https://github.com/jiachen1120) +[@prashanth-chaitanya](https://github.com/prashanth-chaitanya) + +[@fdutton](https://github.com/fdutton) + +[@valfirst](https://github.com/valfirst) [@BalloonWen](https://github.com/BalloonWen) -[@eskabetxe](https://github.com/eskabetxe) +[@jiachen1120](https://github.com/jiachen1120) [@ddobrin](https://github.com/ddobrin) +[@eskabetxe](https://github.com/eskabetxe) + [@ehrmann](https://github.com/ehrmann) -[@rhwood](https://github.com/rhwood) +[@prashanthjos](https://github.com/prashanthjos) -[@nitin1891](https://github.com/nitin1891) +[@Subhajitdas298](https://github.com/Subhajitdas298) -[@jawaff](https://github.com/jawaff) +[@FWiesner](https://github.com/FWiesner) -[@kosty](https://github.com/kosty) - -[@chenyan71](https://github.com/chenyan71) +[@rhwood](https://github.com/rhwood) -[@chrisken](https://github.com/chrisken) +[@jawaff](https://github.com/jawaff) -[@NicholasAzar](https://github.com/NicholasAzar) +[@nitin1891](https://github.com/nitin1891) -[@basinilya](https://github.com/basinilya) For all contributors, please visit https://github.com/networknt/json-schema-validator/graphs/contributors @@ -177,6 +646,3 @@ If you are a contributor, please join the [GitHub Sponsors](https://github.com/s ### Corporation Sponsors - - - diff --git a/doc/collector-context.md b/doc/collector-context.md index 055ddb8e5..c8b555996 100644 --- a/doc/collector-context.md +++ b/doc/collector-context.md @@ -1,87 +1,120 @@ ### CollectorContext - -There could be usecases where we want collect the information while we are validating the data. A simple example could be fetching some value from a database or from a microservice based on the data (which could be a text or a JSON object. It should be noted that this should be a simple operation or validation might take more time to complete.) in a given JSON node and the schema keyword we are using. +There could be use cases where we want collect the information while we are validating the data. A simple example could be fetching some value from a database or from a microservice based on the data (which could be a text or a JSON object. It should be noted that this should be a simple operation or validation might take more time to complete.) in a given JSON node and the schema keyword we are using. The fetched data can be stored somewhere so that it can be used later after the validation is done. Since the current validation logic already parses the data and schema, both validation and collecting the required information can be done in one go. -CollectorContext and Collector classes are designed to work with this use case. +The `CollectorContext` and `Collector` classes are designed to work with this use case. #### How to use CollectorContext -Objects of CollectorContext live on ThreadLocal which is unique for every thread. This allows users to add objects to context at many points in the framework like Formats, Validators (Effectively CollectorContext can be used at any touch point in the validateAndCollect method thread call). - -CollectorContext instance can be obtained by calling the getInstance static method on CollectorContext. This method gives an instance from the ThreadLocal for the current thread. +The `CollectorContext` is stored as a variable on the `ExecutionContext` that is used during the validation. This allows users to add objects to context at many points in the framework like Formats and Validators where the `ExecutionContext` is available as a parameter. -Collectors are added to CollectorContext. Collectors allow to collect the objects. A Collector is added to CollectorContext with a name and corresponding Collector instance. +By default the `CollectorContext` created by the library contains maps backed by `HashMap`. If the `CollectorContext` needs to be shared by multiple threads then a `ConcurrentHashMap` needs to be used. +```java +CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>(), new ConcurrentHashMap<>()); ``` -CollectorContext collectorContext = CollectorContext.getInstance(); + +Collectors are added to `CollectorContext`. Collectors allow to collect the objects. A `Collector` is added to `CollectorContext` with a name and corresponding `Collector` instance. + +```java +CollectorContext collectorContext = executionContext.getCollectorContext(); collectorContext.add(SAMPLE_COLLECTOR_NAME, new Collector>() { - @Override - public List collect() { - List references = new ArrayList(); - references.add(getDatasourceMap().get(node.textValue())); - return references; - } + @Override + public List collect() { + List references = new ArrayList(); + references.add(getDatasourceMap().get(node.textValue())); + return references; + } }); ``` However there might be use cases where we want to add a simple Object like String, Integer, etc, into the Context. This can be done the same way a collector is added to the context. +```java +CollectorContext collectorContext = executionContext.getCollectorContext(); +collectorContext.add(SAMPLE_COLLECTOR, "sample-string"); ``` -CollectorContext collectorContext = CollectorContext.getInstance(); -collectorContext.add(SAMPLE_COLLECTOR,"sample-string") - -``` - -To validate the schema with the ability to use CollectorContext, validateAndCollect method has to be invoked on the JsonSchema class. This class returns a ValidationResult that contains the errors encountered during validation and a CollectorContext instance. Objects constructed by Collectors or directly added to CollectorContext can be retrieved from CollectorContext by using the name they were added with. +Implementations that need to modify values the `CollectorContext` should do so in a thread-safe manner. -``` - ValidationResult validationResult = jsonSchema.validateAndCollect(jsonNode); - CollectorContext context = validationResult.getCollectorContext(); - List contextValue = (List)context.get(SAMPLE_COLLECTOR); - +```java +CollectorContext collectorContext = executionContext.getCollectorContext(); +AtomicInteger count = (AtomicInteger) collectorContext.getCollectorMap().computeIfAbsent(SAMPLE_COLLECTOR, + (key) -> new AtomicInteger(0)); +count.incrementAndGet(); ``` -Note that CollectorContext will be removed from ThreadLocal once validateAndCollect method returns. Also the data collected by Collectors is loaded into CollectorContext only after all the validations are done. +To use the `CollectorContext` while validating, the `CollectorContext` should be instantiated outside and set for every validation execution. + +At the end of all the runs the `CollectorContext.loadCollectors()` method can be called if needed for the `Collector` implementations to aggregate values. + +```java +// This creates a CollectorContext that can be used by multiple threads although this is not neccessary in this example +CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>(), new ConcurrentHashMap<>()); +// This adds a custom collect keyword that sets values in the CollectorContext whenever it gets processed +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()).keyword(new CollectKeyword()).build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +JsonSchema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); +for (int i = 0; i < 50; i++) { + // The shared CollectorContext is set on the ExecutionContext for every run to aggregate data from all the runs + schema.validate("1", InputFormat.JSON, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); +} +// This is called for Collector implementations to aggregate data +collectorContext.loadCollectors(); +AtomicInteger result = (AtomicInteger) collectorContext.get("collect"); +assertEquals(50, result.get()); +``` -There might be usecases where a collector needs to collect the data at multiple touch points. For example one usecase might be collecting data in a validator and a formatter. If you are using a Collector rather than a Object, the combine method of the Collector allows to define how we want to combine the data into existing Collector. CollectorContext combineWithCollector method calls the combine method on the Collector. User just needs to call the CollectorContext combineWithCollector method every time some data needs to merged into existing Collector. The collect method on the Collector is called by the framework at the end of validation to return the data that was collected. +There might be use cases where a collector needs to collect the data at multiple touch points. For example one use case might be collecting data in a validator and a formatter. If you are using a `Collector` rather than a `Object`, the combine method of the `Collector` allows to define how we want to combine the data into existing `Collector`. `CollectorContext` `combineWithCollector` method calls the combine method on the `Collector`. User just needs to call the `CollectorContext` `combineWithCollector` method every time some data needs to merged into existing `Collector`. The `collect` method on the `Collector` is called by explicitly calling `CollectorContext.loadCollectors()` at the end of processing. -``` - class CustomCollector implements Collector> { +```java +class CustomCollector implements Collector> { - List returnList = new ArrayList(); + List returnList = new ArrayList<>(); - private Map referenceMap = null; + private Map referenceMap = null; - public CustomCollector() { - referenceMap = getDatasourceMap(); - } + public CustomCollector() { + referenceMap = getDatasourceMap(); + } - @Override - public List collect() { - return returnList; - } + @Override + public List collect() { + return returnList; + } - @Override - public void combine(Object object) { - returnList.add(referenceMap.get((String) object)); - } + @Override + public void combine(Object object) { + synchronized(returnList) { + returnList.add(referenceMap.get((String) object)); + } + } } +``` -CollectorContext collectorContext = CollectorContext.getInstance(); -if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector()); +```java +private class CustomValidator extends AbstractJsonValidator { + @Override + public Set validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation) { + CollectorContext collectorContext = executionContext.getCollectorContext(); + CustomCollector customCollector = (CustomCollector) collectorContext.getCollectorMap().computeIfAbsent(SAMPLE_COLLECTOR, + key -> new CustomCollector()); + customCollector.combine(node.textValue()); + return Collections.emptySet(); + } } -collectorContext.combineWithCollector(SAMPLE_COLLECTOR, node.textValue()); - ``` -One important thing to note when using Collectors is if we call get method on CollectorContext before the validation is complete, we would get back a Collector instance that was added to CollectorContext. +One important thing to note when using Collectors is if we call get method on `CollectorContext` before the validation is complete, we would get back a `Collector` instance that was added to `CollectorContext`. -``` +```java // Returns Collector before validation is done. Collector> collector = collectorContext.get(SAMPLE_COLLECTOR); @@ -92,15 +125,10 @@ List data = collectorContext.get(SAMPLE_COLLECTOR); If you are using simple objects and if the data needs to be collected from multiple touch points, logic is straightforward as shown. -``` - -CollectorContext collectorContext = CollectorContext.getInstance(); -// If collector name is not added to context add one. -if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, new ArrayList()); +```java +List returnList = (List) collectorContext.getCollectorMap() + .computeIfAbsent(SAMPLE_COLLECTOR, key -> new ArrayList()); +synchronized(returnList) { + returnList.add(node.textValue()); } -// In this case we are adding a list to CollectorContext. -List returnList = (List) collectorContext.get(SAMPLE_COLLECTOR); - ``` - diff --git a/doc/compatibility.md b/doc/compatibility.md index 6f01eb7cb..3292bf4ba 100644 --- a/doc/compatibility.md +++ b/doc/compatibility.md @@ -1,76 +1,181 @@ +## Compatibility with JSON Schema versions + +[![Supported Dialects](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-networknt-json-schema-validator) +[![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/dialects/draft2020-12) +[![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/dialects/draft2019-09) +[![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/dialects/draft7) +[![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/dialects/draft6) +[![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-com.networknt-json-schema-validator%2Fcompliance%2Fdraft4.json)](https://bowtie.report/#/dialects/draft4) + +The `pattern` and `format` `regex` validator by default uses the JDK regular expression implementation which is not ECMA-262 compliant and is thus not compliant with the JSON Schema specification. The library can however be configured to use a ECMA-262 compliant regular expression implementation such as `GraalJS` or `Joni`. + +Annotation processing and reporting are implemented. Note that the collection of annotations will have an adverse performance impact. + +This implements the Flag, List and Hierarchical output formats defined in the [Specification for Machine-Readable Output for JSON Schema Validation and Annotation](https://github.com/json-schema-org/json-schema-spec/blob/8270653a9f59fadd2df0d789f22d486254505bbe/jsonschema-validation-output-machines.md). + +The implementation supports the use of custom keywords, formats, vocabularies and meta-schemas. + +### Known Issues + +There are currently no known issues with the required functionality from the specification. + +The following are the tests results after running the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) as at 18 Jun 2024 using version 1.4.1. As the test suite is continously updated, this can result in changes in the results subsequently. + +| Implementations | Overall | DRAFT_03 | DRAFT_04 | DRAFT_06 | DRAFT_07 | DRAFT_2019_09 | DRAFT_2020_12 | +|-----------------|-------------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------------| +| NetworkNt | pass: r:4803 (100.0%) o:2372 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | | pass: r:610 (100.0%) o:251 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:822 (100.0%) o:318 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:906 (100.0%) o:541 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1220 (100.0%) o:625 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1245 (100.0%) o:637 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | ### Legend -Symbol | Meaning | -:-----:|---------| -🟢 | Fully implemented -🟡 | Partially implemented -🔴 | Not implemented -🚫 | Not defined in Schema Version. - -### Compatibility with JSON Schema versions - - Validation Keyword/Schema | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | ----------------- |:--------------:|:-------: |:-------: |:-------------:| -$ref | 🟢 | 🟢 | 🟢 | 🟢 -additionalProperties | 🟢 | 🟢 | 🟢 | 🟢 -additionalItems | 🟢 | 🟢 | 🟢 | 🟢 -allOf | 🟢 | 🟢 | 🟢 | 🟢 -anyOf | 🟢 | 🟢 | 🟢 | 🟢 -const | 🚫 | 🟢 | 🟢 | 🟢 -contains | 🚫 | 🟢 | 🟢 | 🟢 -contentEncoding | 🚫 | 🚫 | 🔴 | 🔴 -contentMediaType | 🚫 | 🚫 | 🔴 | 🔴 -dependencies | 🟢 | 🟢 |🟢 | 🟢 -enum | 🟢 | 🟢 | 🟢 | 🟢 -exclusiveMaximum (boolean) | 🟢 | 🚫 | 🚫 | 🚫 -exclusiveMaximum (numeric) | 🚫 | 🟢 | 🟢 | 🟢 -exclusiveMinimum (boolean) | 🟢 | 🚫 | 🚫 | 🚫 -exclusiveMinimum (numeric) | 🚫 | 🟢 | 🟢 | 🟢 -items | 🟢 | 🟢 | 🟢 | 🟢 -maximum | 🟢 | 🟢 | 🟢 | 🟢 -maxItems | 🟢 | 🟢 | 🟢 | 🟢 -maxLength | 🟢 | 🟢 | 🟢 | 🟢 -maxProperties | 🟢 | 🟢 | 🟢 | 🟢 -minimum | 🟢 | 🟢 | 🟢 | 🟢 -minItems | 🟢 | 🟢 | 🟢 | 🟢 -minLength | 🟢 | 🟢 | 🟢 | 🟢 -minProperties | 🟢 | 🟢 | 🟢 | 🟢 -multipleOf | 🟢 | 🟢 | 🟢 | 🟢 -not | 🟢 | 🟢 | 🟢 | 🟢 -oneOf | 🟢 | 🟢 | 🟢 | 🟢 -pattern | 🟢 | 🟢 | 🟢 | 🟢 -patternProperties | 🟢 | 🟢 | 🟢 | 🟢 -properties | 🟢 | 🟢 | 🟢 | 🟢 -propertyNames | 🚫 | 🔴 | 🔴 | 🔴 -required | 🟢 | 🟢 | 🟢 | 🟢 -type | 🟢 | 🟢 | 🟢 | 🟢 -uniqueItems | 🟢 | 🟢 | 🟢 | 🟢 - -### Semantic Validation (Format) - -Format | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | --------|---------|---------|---------|---------------| -date |🚫 | 🚫 | 🟢 | 🟢 -date-time | 🟢 | 🟢 | 🟢 | 🟢 -duration | 🚫 | 🚫 | 🔴 | 🔴 -email | 🟢 | 🟢 | 🟢 | 🟢 -hostname | 🟢 | 🟢 | 🟢 | 🟢 -idn-email | 🚫 | 🚫 | 🔴 | 🔴 -idn-hostname | 🚫 | 🚫 | 🔴 | 🔴 -ipv4 | 🟢 | 🟢 | 🟢 | 🟢 -ipv6 | 🟢 | 🟢 | 🟢 | 🟢 -iri | 🚫 | 🚫 | 🔴 | 🔴 -iri-reference | 🚫 | 🚫 | 🔴 | 🔴 -json-pointer | 🚫 | 🔴 | 🔴 | 🔴 -relative-json-pointer | 🚫 | 🔴 | 🔴 | 🔴 -regex | 🚫 | 🚫 | 🔴 | 🔴 -time | 🚫 | 🚫 | 🟢 | 🟢 -uri | 🟢 | 🟢 | 🟢 | 🟢 -uri-reference | 🚫 | 🔴 | 🔴 | 🔴 -uri-template | 🚫 | 🔴 | 🔴 | 🔴 -uuid | 🚫 | 🚫 | 🟢 | 🟢 - -### Footnotes +| Symbol | Meaning | +|:------:|:----------------------| +| 🟢 | Fully implemented | +| 🟡 | Partially implemented | +| 🔴 | Not implemented | +| 🚫 | Not defined | + +### Keywords Support + +| Keyword | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | Draft 2020-12 | +|:---------------------------|:-------:|:-------:|:-------:|:-------------:|:-------------:| +| $anchor | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| $defs | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| $dynamicAnchor | 🚫 | 🚫 | 🚫 | 🚫 | 🟢 | +| $dynamicRef | 🚫 | 🚫 | 🚫 | 🚫 | 🟢 | +| $id | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| $recursiveAnchor | 🚫 | 🚫 | 🚫 | 🟢 | 🚫 | +| $recursiveRef | 🚫 | 🚫 | 🚫 | 🟢 | 🚫 | +| $ref | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| $vocabulary | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| additionalItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| additionalProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| allOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| anyOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| const | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| contains | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| contentEncoding | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| contentMediaType | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| contentSchema | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| definitions | 🟢 | 🟢 | 🟢 | 🚫 | 🚫 | +| dependencies | 🟢 | 🟢 | 🟢 | 🚫 | 🚫 | +| dependentRequired | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| dependentSchemas | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| enum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| exclusiveMaximum (boolean) | 🟢 | 🚫 | 🚫 | 🚫 | 🚫 | +| exclusiveMaximum (numeric) | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| exclusiveMinimum (boolean) | 🟢 | 🚫 | 🚫 | 🚫 | 🚫 | +| exclusiveMinimum (numeric) | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| if-then-else | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| items | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| maxContains | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| minContains | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| maximum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| maxItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| maxLength | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| maxProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| minimum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| minItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| minLength | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| minProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| multipleOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| not | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| oneOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| pattern | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| patternProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| prefixItems | 🚫 | 🚫 | 🚫 | 🚫 | 🟢 | +| properties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| propertyNames | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| readOnly | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| required | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| type | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| unevaluatedItems | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| unevaluatedProperties | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| uniqueItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| writeOnly | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | + +In accordance with the specification, unknown keywords are treated as annotations. This is customizable by configuring a unknown keyword factory on the respective meta-schema. + +#### Content Encoding + +Since Draft 2019-09, the `contentEncoding` keyword does not generate assertions. + +#### Content Media Type + +Since Draft 2019-09, the `contentMediaType` keyword does not generate assertions. + +#### Content Schema + +The `contentSchema` keyword does not generate assertions. + +#### Pattern + +By default the `pattern` keyword uses the JDK regular expression implementation validating regular expressions. + +This is not ECMA-262 compliant and is thus not compliant with the JSON Schema specification. This is however the more likely desired behavior as other logic will most likely be using the default JDK regular expression implementation to perform downstream processing. + +The library can be configured to use a ECMA-262 compliant regular expression validator which is implemented using [GraalJS](https://github.com/oracle/graaljs) or [Joni](https://github.com/jruby/joni). This can be configured by setting `setRegularExpressionFactory` to the respective `GraalJSRegularExpressionFactory` or `JoniRegularExpressionFactory` instances. + +This also requires adding the `org.graalvm.js:js` or `org.jruby.joni:joni` dependency. + +```xml + + + + + org.graalvm.js + js + ${version.graaljs} + + + + + + + org.jruby.joni + joni + ${version.joni} + +``` + +#### Format + +Since Draft 2019-09 the `format` keyword only generates annotations by default and does not generate assertions. + +This can be configured on a schema basis by using a meta schema with the appropriate vocabulary. + +| Version | Vocabulary | Value | +|:----------------------|---------------------------------------------------------------|-------------------| +| Draft 2019-09 | `https://json-schema.org/draft/2019-09/vocab/format` | `true` | +| Draft 2020-12 | `https://json-schema.org/draft/2020-12/vocab/format-assertion`| `true`/`false` | + +This behavior can be overridden to generate assertions by setting the `setFormatAssertionsEnabled` option to `true`. + +| Format | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | Draft 2020-12 | +|:----------------------|:-------:|:-------:|:-------:|:-------------:|:-------------:| +| date | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| date-time | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| duration | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 | +| email | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| hostname | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| idn-email | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| idn-hostname | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| ipv4 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| ipv6 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| iri | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| iri-reference | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| json-pointer | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| relative-json-pointer | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| regex | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| time | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | +| uri | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| uri-reference | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| uri-template | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 | +| uuid | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 | + +##### Unknown Formats + +When the format assertion vocabularies are used in a meta schema, in accordance to the specification, unknown formats will result in assertions. If the format assertion vocabularies are not used, unknown formats will only result in assertions if the assertions are enabled and if `setStrict("format", true)`. + +##### Footnotes 1. Note that the validation are only optional for some of the keywords/formats. -2. Refer to the corresponding JSON schema for more information on whether the keyword/format is optional or not. +2. Refer to the corresponding JSON schema for more information on whether the keyword/format is optional or not. \ No newline at end of file diff --git a/doc/config.md b/doc/config.md index d03544b72..bdf1cfbd2 100644 --- a/doc/config.md +++ b/doc/config.md @@ -1,65 +1,47 @@ -### Configuration - -To control the behavior of the library, we have introduced SchemaValidatorsConfig recently. It gives users great flexibility when using the library in different contexts. - -For some users, it is just a JSON schema validator implemented mainly based on v4 with some additions from v5 to v7. - -For others, it is used as a critical component in the REST API frameworks to validate the request or response. The library was developed as part of the [light-4j](https://github.com/networknt/light-4j) framework in the beginning. - -Most of the configuration flags are used to control the difference between Swagger/OpenAPI specification and JSON schema specification as they are not the same. The future of the OpenAPI version might resolve this problem, but the release date is not set yet. +### Schema Validators Configuration + +| Name | Description | Default Value +|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------- +| `applyDefaultsStrategy` | The strategy for applying defaults when walking when missing or null nodes are encountered. | `ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY` +| `cacheRefs` | Whether the schemas loaded from refs will be cached and reused for subsequent runs. Setting this to `false` will affect performance but may be neccessary to prevent high memory usage for the cache if multiple nested applicators like `anyOf`, `oneOf` and `allOf` are used. | `true` +| `discriminatorKeywordEnabled` | Whether the `discriminator` keyword is handled according to OpenAPI 3. | `false` +| `errorMessageKeyword` | The keyword to use for custom error messages in the schema. If not set this features is disabled. This is typically set to `errorMessage` or `message`. | `null` +| `executionContextCustomizer` | This can be used to customize the `ExecutionContext` generated by the `JsonSchema` for each validation run. | `null` +| `failFast` | Whether to return failure immediately when an assertion is generated. | `false` +| `formatAssertionsEnabled` | The default is to generate format assertions from Draft 4 to Draft 7 and to only generate annotations from Draft 2019-09. Setting to `true` or `false` will override the default behavior. | `null` +| `javaSemantics` | Whether java semantics is used for the `type` keyword. | `false` +| `locale` | The locale to use for generating messages in the `ValidationMessage`. | `Locale.getDefault()` +| `losslessNarrowing` | Whether lossless narrowing is used for the `type` keyword. | `false` +| `messageSource` | This is used to retrieve the locale specific messages. | `DefaultMessageSource.getInstance()` +| `nullableKeywordEnabled` | Whether the `nullable` keyword is handled according to OpenAPI 3.0. This affects the `enum` and `type` keywords. | `false` +| `pathType` | The path type to use for reporting the instance location and evaluation path. Set to `PathType.JSON_PATH` to use JSON Path. | `PathType.JSON_POINTER` +| `preloadJsonSchema` | Whether the schema will be preloaded before processing any input. This will use memory but the execution of the validation will be faster. | `true` +| `preloadJsonSchemaRefMaxNestingDepth` | The max depth of the evaluation path to preload when preloading refs. | `40` +| `readOnly` | Whether schema is read only. This affects the `readOnly` keyword. | `null` +| `regularExpressionFactory` | The factory to use to create regular expressions for instance `JoniRegularExpressionFactory` or `GraalJSRegularExpressionFactory`. This requires the dependency to be manually added to the project or a `ClassNotFoundException` will be thrown. | `JDKRegularExpressionFactory.getInstance()` +| `schemaIdValidator` | This is used to customize how the `$id` values are validated. Note that the default implementation allows non-empty fragments where no base IRI is specified and also allows non-absolute IRI `$id` values in the root schema. | `JsonSchemaIdValidator.DEFAULT` +| `strict` | This is set whether keywords are strict in their validation. What this does depends on the individual validators. | +| `typeLoose` | Whether types are interpreted in a loose manner. If set to true, a single value can be interpreted as a size 1 array. Strings may also be interpreted as number, integer or boolean. | `false` +| `writeOnly` | Whether schema is write only. This affects the `writeOnly` keyword. | `null` #### How to use config -When you create a JsonSchema instance from the JsonSchemaFactory, you can pass an object of SchemaValidatorsConfig as the second parameter. +When you create a `JsonSchema` instance from the `JsonSchemaFactory`, you can pass an object of `SchemaValidatorsConfig` as the second parameter. +```java +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().typeLoose(false).build(); +JsonSchema schema = factory.getSchema(schema, config); ``` -SchemaValidatorsConfig config = new SchemaValidatorsConfig(); -config.setTypeLoose(false); -JsonSchema jsonSchema = JsonSchemaFactory.getInstance().getSchema(schema, config); -``` - -#### Configurations - -* typeLoose - -When typeLoose is true, the validator will convert strings to different types to match the type defined in the schema. This is mostly used to validate the JSON request or response for headers, query parameters, path parameters, and cookies. For the HTTP protocol, these are all strings and might be defined as other types in the schema. For example, the page number might be an integer in the schema but passed as a query parameter in string. - -* failFast - -When set to true, the validation process stops immediately when the first error occurs. This mostly used on microservices that is designed to [fail-fast](https://www.networknt.com/architecture/fail-fast/), or users don't want to see hundreds of errors for a big payload. Please be aware that the validator throws an exception in the case the first error occurs. To learn how to use it, please follow the [test case](https://github.com/networknt/json-schema-validator/blob/master/src/test/java/com/networknt/schema/V4JsonSchemaTest.java#L352). - -* handleNullableField - -When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable; however, it continues with validation against the nullable field. - -If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed - -If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type validator using the SchemaValidator to handle it. - -The default value is true in the SchemaValidatorsConfig object. - -For more details, please refer to this [issue](https://github.com/networknt/json-schema-validator/issues/183). - - -* uriMappings - -Map of public, typically internet-accessible schema URLs to alternate locations; this allows for offline validation of schemas that refer to public URLs. This is merged with any mappings the sonSchemaFactory -may have been built. - -The type for this variable is Map. - -* javaSemantics - -When set to true, use Java-specific semantics rather than native JavaScript semantics. - -For example, if the node type is `number` per JS semantics where the value can be losslesly interpreted as `java.lang.Long`, the validator would use `integer` as the node type instead of `number`. This is useful when schema type is `integer`, since validation would fail otherwise. - -For more details, please refer to this [issue](https://github.com/networknt/json-schema-validator/issues/334). - -* losslessNarrowing - -When set to true, can interpret round doubles as integers. - -Note that setting `javaSemantics = true` will achieve the same functionality at this time. -For more details, please refer to this [issue](https://github.com/networknt/json-schema-validator/issues/344). \ No newline at end of file +#### Details + +| Name | Details +|---------------------------|------------------------------------------------ +| `typeLoose` | When typeLoose is true, the validator will convert strings to different types to match the type defined in the schema. This is mostly used to validate the JSON request or response for headers, query parameters, path parameters, and cookies. For the HTTP protocol, these are all strings and might be defined as other types in the schema. For example, the page number might be an integer in the schema but passed as a query parameter in string. When it comes to validating arrays note that any item can also be interpreted as a size 1 array of that item so the item will be validated against the type defined for the array. +| `strict` | This is a map of keywords to whether the keyword's validators should perform a strict or permissive analysis. When strict is true, validators will perform strict checking against the schema. This is the default behavior. When set to false, validators are free to relax some constraints but not required. Each validator has its own understanding of what constitutes strict and permissive. +| `failFast` | When set to true, the validation process stops immediately when the first error occurs. This mostly used on microservices that is designed to [fail-fast](https://www.networknt.com/architecture/fail-fast/), or users don't want to see hundreds of errors for a big payload. +| `nullableKeywordEnabled` | When a field is set as `nullable` in the OpenAPI specification, the schema validator validates that it is `nullable`; however, it continues with validation against the `nullable` field. If `nullableKeywordEnabled` is set to true && incoming field is `nullable` && value is field: null --> succeed. If `nullableKeywordEnabled` is set to false && incoming field is `nullable` && value is field: null --> it is up to the type validator using the SchemaValidator to handle it. +| `javaSemantics` | When set to true, use Java-specific semantics rather than native JavaScript semantics. For example, if the node type is `number` per JS semantics where the value can be losslesly interpreted as `java.lang.Long`, the validator would use `integer` as the node type instead of `number`. This is useful when schema type is `integer`, since validation would fail otherwise. +| `losslessNarrowing` | When set to true, can interpret round doubles as integers. Note that setting `javaSemantics = true` will achieve the same functionality at this time. +| `pathType` | This defines how path expressions are defined and returned once validation is performed through `ValidationMessage` instances. This can either be set to `PathType.JSON_POINTER` for [JSONPointer](https://www.rfc-editor.org/rfc/rfc6901.html) expressions, or to `PathType.JSON_PATH` for [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) expressions. Doing so allows you to report the path for each finding and to potentially lookup nodes (see [here](https://github.com/networknt/json-schema-validator/blob/c41df270a71f8423c63cfaa379d2e9b3f570b73e/doc/yaml-line-numbers.md#scenario-2---validationmessage-line-locations) for an example). \ No newline at end of file diff --git a/doc/cust-fetcher.md b/doc/cust-fetcher.md deleted file mode 100644 index c6e5891a5..000000000 --- a/doc/cust-fetcher.md +++ /dev/null @@ -1 +0,0 @@ -The default URIFetcher implementation uses JDK connection/socket without handling network exceptions. It works in most of the cases; however, if you want to have a customized implementation, you can do so. One user has his implementation with urirest to handle the timeout. A detailed discussion can be found in this [issue](https://github.com/networknt/json-schema-validator/issues/240) diff --git a/doc/cust-meta.md b/doc/cust-meta.md deleted file mode 100644 index 738cfbaa2..000000000 --- a/doc/cust-meta.md +++ /dev/null @@ -1,6 +0,0 @@ -How to use your customized meta schema? - -The library supports customized meta schema with this builder method. We have provided default instances for v4, v6, v7, and v2019-09 but users can always create their own JsonMetaSchema instance with customized meta schema. - -https://github.com/networknt/json-schema-validator/blob/master/src/main/java/com/networknt/schema/JsonMetaSchema.java#L188 - diff --git a/doc/cust-msg.md b/doc/cust-msg.md index 2fce4c21a..cde6a00fe 100644 --- a/doc/cust-msg.md +++ b/doc/cust-msg.md @@ -1,11 +1,16 @@ -This document explains how users can create their custom message for schema validation. +# Custom Error Messages +Schema authors can provide their own custom messages within the schema using a specified keyword. +This is not enabled by default and the `SchemaValidatorsConfig` must be configured with the `errorMessageKeyword`. -We can provide the custom message in the json schema itself. +```java +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().errorMessageKeyword("errorMessage").build(); +``` - Example of schema with default message: - -```` +## Examples +### Example 1 : +The custom message can be provided outside properties for each type, as shown in the schema below. +```json { "type": "object", "properties": { @@ -17,46 +22,90 @@ We can provide the custom message in the json schema itself. "type": "array", "maxItems": 3 } + }, + "errorMessage": { + "maxItems": "MaxItem must be 3 only", + "type": "Invalid type" } } -```` - - - Example of schema with a custom message: - -```` +``` +### Example 2 : +To keep custom messages distinct for each type, one can even give them in each property. +```json { "type": "object", "properties": { - "firstName": { + "dateTime": { "type": "string", - "description": "The person's first name." + "format": "date", + "errorMessage": { + "format": "Keep date format yyyy-mm-dd" + } }, + "uuid": { + "type": "string", + "format": "uuid", + "errorMessage": { + "format": "Input should be uuid" + } + } + } +} +``` +### Example 3 : +For the keywords `required` and `dependencies`, different messages can be specified for different properties. + +```json +{ + "type": "object", + "properties": { "foo": { - "type": "array", - "maxItems": 3 + "type": "number" + }, + "bar": { + "type": "string" } }, - "message": { - "maxItems" : "MaxItem must be 3 only", - "type" : "Invalid type" + "required": ["foo", "bar"], + "errorMessage": { + "type": "should be an object", + "required": { + "foo": "'foo' is required", + "bar": "'bar' is required" + } } } -```` - - +``` +### Example 4 : +The message can use arguments but note that single quotes need to be escaped as `java.text.MessageFormat` will be used to format the message. -```` -"message": { - [validationType] : [customMessage] +```json +{ + "type": "object", + "properties": { + "foo": { + "type": "number" + }, + "bar": { + "type": "string" + } + }, + "required": ["foo", "bar"], + "errorMessage": { + "type": "should be an object", + "required": { + "foo": "{0}: ''foo'' is required", + "bar": "{0}: ''bar'' is required" + } } -```` - -In the message field users can declare their custom message. The key should be the validation type, and the value should be the custom message. - - -Also, we can make format the dynamic message with properties returned from [ValidationMessage.java](https://github.com/networknt/json-schema-validator/blob/master/src/main/java/com/networknt/schema/ValidationMessage.java) class such as arguments, path e.t.c. - - +} +``` -Take a look at the [PR](https://github.com/networknt/json-schema-validator/pull/438) \ No newline at end of file +## Format +```json +"errorMessage": { + "[keyword]": "[customMessage]" +} +``` +Users can provide custom messages in the configured keyword, typically `errorMessage` or `message` field. +The `keyword` should be the key and the `customMessage` should be the value. \ No newline at end of file diff --git a/doc/custom-meta-schema.md b/doc/custom-meta-schema.md new file mode 100644 index 000000000..7854052db --- /dev/null +++ b/doc/custom-meta-schema.md @@ -0,0 +1,206 @@ +# Customizing Meta-Schemas, Vocabularies, Keywords and Formats + +The meta-schemas, vocabularies, keywords and formats can be customized with appropriate configuration of the `JsonSchemaFactory` that is used to create instances of `JsonSchema`. + +## Creating a custom keyword + +A custom keyword can be implemented by implementing the `com.networknt.schema.Keyword` interface. + +```java +public class EqualsKeyword implements Keyword { + @Override + public String getValue() { + return "equals"; + } + @Override + public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, + JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) + throws JsonSchemaException, Exception { + return new EqualsValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, this, validationContext, false); + } +} +``` + +```java +public class EqualsValidator extends BaseJsonValidator { + private static ErrorMessageType ERROR_MESSAGE_TYPE = new ErrorMessageType() { + @Override + public String getErrorCode() { + return "equals"; + } + }; + + private final String value; + public EqualsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, + JsonSchema parentSchema, Keyword keyword, + ValidationContext validationContext, boolean suppressSubSchemaRetrieval) { + super(schemaLocation, evaluationPath, schemaNode, parentSchema, ERROR_MESSAGE_TYPE, keyword, validationContext, + suppressSubSchemaRetrieval); + this.value = schemaNode.textValue(); + } + @Override + public Set validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation) { + if (!node.asText().equals(value)) { + return Collections + .singleton(message().message("{0}: must be equal to ''{1}''") + .arguments(value) + .instanceLocation(instanceLocation).instanceNode(node).build()); + }; + return Collections.emptySet(); + } +} +``` + +## Adding a keyword to a standard dialect + +A custom keyword can be added to a standard dialect by customizing its meta-schema which is identified by its IRI. + +The following adds a custom keyword to the Draft 2020-12 dialect. + +```java +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .keyword(new EqualsKeyword()) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +## Creating a custom meta-schema + +A custom meta-schema can be created by using a standard dialect as a base. + +The following creates a custom meta-schema `https://www.example.com/schema` with a custom keyword using the Draft 2020-12 dialect as a base. + +```java +JsonMetaSchema dialect = JsonMetaSchema.getV202012(); +JsonMetaSchema metaSchema = JsonMetaSchema.builder("https://www.example.com/schema", dialect) + .keyword(new EqualsKeyword()) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +## Associating vocabularies to a dialect + +Custom vocabularies can be associated with a particular dialect by configuring a `com.networknt.schema.VocabularyFactory` on its meta-schema. + +```java +VocabularyFactory vocabularyFactory = iri -> { + if ("https://www.example.com/vocab/equals".equals(iri)) { + return new Vocabulary("https://www.example.com/vocab/equals", new EqualsKeyword()); + } + return null; +}; +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .vocabularyFactory(vocabularyFactory) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +The following custom meta-schema `https://www.example.com/schema` will use the custom vocabulary `https://www.example.com/vocab/equals`. + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com/schema", + "$vocabulary": { + "https://www.example.com/vocab/equals": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } + ] +} +``` + +Note that `"https://www.example.com/vocab/equals": true` means that if the vocabulary is unknown the meta-schema will fail to successfully load while `"https://www.example.com/vocab/equals": false` means that an unknown vocabulary will still successfully load. + +## Unknown keywords + +By default unknown keywords are treated as annotations. This can be customized by configuring a `com.networknt.schema.KeywordFactory` on its meta-schema. + +The following configuration will cause a `InvalidSchemaException` to be thrown if an unknown keyword is used. + +```java +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +## Creating a custom format + +A custom format can be implemented by implementing the `com.networknt.schema.Format` interface. + +```java +public class MatchNumberFormat implements Format { + private final BigDecimal compare; + + public MatchNumberFormat(BigDecimal compare) { + this.compare = compare; + } + @Override + public boolean matches(ExecutionContext executionContext, ValidationContext validationContext, JsonNode value) { + JsonType nodeType = TypeFactory.getValueNodeType(value, validationContext.getConfig()); + if (nodeType != JsonType.NUMBER && nodeType != JsonType.INTEGER) { + return true; + } + BigDecimal number = value.isBigDecimal() ? value.decimalValue() : BigDecimal.valueOf(value.doubleValue()); + number = new BigDecimal(number.toPlainString()); + return number.compareTo(compare) == 0; + } + @Override + public String getName() { + return "matchnumber"; + } +} +``` + +## Adding a format to a standard dialect + +A custom format can be added to a standard dialect by customizing its meta-schema which is identified by its IRI. + +The following adds a custom format to the Draft 2020-12 dialect. + +```java +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .format(new MatchNumberFormat(new BigDecimal("12345"))) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +## Customizing the format keyword + +The format keyword implementation to use can be customized by supplying a `FormatKeywordFactory` to the meta-schema that creates an instance of the subclass of `FormatKeyword`. + +```java +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .formatKeywordFactory(CustomFormatKeyword::new) + .build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +``` + +## Unknown formats + +By default unknown formats are ignored unless the format assertion vocabulary is used for that meta-schema. Note that the format annotation vocabulary with the configuration to enable format assertions is not equivalent to the format assertion vocabulary. + +To ensure that errors are raised when unknown formats are used, the `SchemaValidatorsConfig` can be configured to set `format` as strict. + + +## Loading meta-schemas + +By default meta-schemas that aren't explicitly configured in the `JsonSchemaFactory` will be automatically loaded. + +This means that the following `JsonSchemaFactory` will still be able to process `$schema` with other dialects such as Draft 7 or Draft 2019-09 as the meta-schemas for those dialects will be automatically loaded. This will also attempt to load custom meta-schemas with custom vocabularies. Draft 2020-12 will be used by default if `$schema` is not defined. + +```java +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); +``` + +If this is undesirable, for instance to restrict the meta-schemas used only to those explicitly configured in the `JsonSchemaFactory` a `com.networknt.schema.JsonMetaSchemaFactory` can be configured. + +```java +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, + builder -> builder.metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance())); +``` diff --git a/doc/duration.md b/doc/duration.md new file mode 100644 index 000000000..0d3bc30d8 --- /dev/null +++ b/doc/duration.md @@ -0,0 +1,30 @@ +## Validating RFC 3339 durations + +JSON Schema Draft 2019-09 and later uses RFC 3339 to define dates and times. +RFC 3339 bases its definition of duration of what is in the 1988 version of +ISO 1801, which is over 35 years old and has undergone many changes with +updates in 1991, 2000, 2004, 2019 and an amendment in 2022. + +There are notable differences between the current version of ISO 8601 and +RFC 3339: +* ISO 8601-2:2019 permits negative durations +* ISO 8601-2:2019 permits combining weeks with other terms (e.g. `P1Y13W`) + +There are also notable differences in how RFC 3339 defines a duration compared +with how the Java Date/Time API defines it: +* `java.time.Duration` accepts fractional seconds; RFC 3339 does not +* `java.time.Period` does not accept a time component while RFC 3339 accepts both date and time components +* `java.time.Duration` accepts days but not years, months or weeks + +By default, the duration validator performs a strict check that the value +conforms to RFC 3339. You can relax this constraint by setting strict to false. + +```java +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().strict("duration", false).build(); +JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config); +``` + +The relaxed check permits: +* Fractional seconds +* Negative durations +* Combining weeks with other terms diff --git a/doc/ecma-262.md b/doc/ecma-262.md index c0e71db40..aea236e02 100644 --- a/doc/ecma-262.md +++ b/doc/ecma-262.md @@ -1,29 +1,89 @@ -For the pattern validator, we now have two options for regex in the library. The default one is java.util. regex; however, you can use the ECMA-262 standard library org.jruby.joni by configuration. +# Regular Expressions -As we know, the JSON schema is designed based on the Javascript language and its regex. The Java internal implementation has some differences which don't comply with the standard. For most users, these edge cases are not the issue as they are not using them anyway. Even when they are using it, they are expecting the Java regex result as the application is built on the Java platform. For users who want to ensure that they are using 100% standard patter validator, we have provided an option to override the default regex library with org.jruby.joni that is complying with the ECMA-262 standard. +For the `pattern` and `format` `regex` validators there are 3 built in options in the library. -### Which one to choose? +A custom implementation can be made by implementing `com.networknt.schema.regex.RegularExpressionFactory` to return a custom implementation of `com.networknt.schema.regex.RegularExpression`. -If you want a faster regex lib and don't care about the slight difference between Java and Javascript regex, then you don't need to do anything. The default regex lib is the java.util.regex. +| Regular Expression Factory | Description | +|--------------------------------------------------|----------------------------------------------------| +| `JDKRegularExpressionFactory` | Uses Java's standard `java.util.regex` and calls the `find()` method. Note that `matches()` is not called as that attempts to match the entire string, implicitly adding anchors. This is the default implementation and does not require any additional libraries. | +| `JoniRegularExpressionFactory` | Uses `org.joni.Regex` with `Syntax.ECMAScript`. This requires adding the `org.jruby.joni:joni` dependency which will require about 2MB. | +| `GraalJSRegularExpressionFactory` | Uses GraalJS with `new RegExp(pattern, 'u')`. This requires adding the `org.graalvm.js:js` dependency which will require about 50MB. | -If you want to ensure full compliance, use the org.jruby.joni. It is 1.5 times slower then java.util.regex. Depending on your use case, it might not be an issue. +## Specification -### How to switch? +The use of Regular Expressions is specified in JSON Schema at https://json-schema.org/draft/2020-12/json-schema-core#name-regular-expressions. -Here is the test case that shows how to pass a config object to use the ECMA-262 library. +``` +Keywords MAY use regular expressions to express constraints, or constrain the instance value to be a regular expression. These regular expressions SHOULD be valid according to the regular expression dialect described in ECMA-262, section 21.2.1 [ecma262]. + +Regular expressions SHOULD be built with the "u" flag (or equivalent) to provide Unicode support, or processed in such a way which provides Unicode support as defined by ECMA-262. +Furthermore, given the high disparity in regular expression constructs support, schema authors SHOULD limit themselves to the following regular expression tokens: + +individual Unicode characters, as defined by the JSON specification [RFC8259]; +simple character classes ([abc]), range character classes ([a-z]); +complemented character classes ([^abc], [^a-z]); +simple quantifiers: "+" (one or more), "*" (zero or more), "?" (zero or one), and their lazy versions ("+?", "*?", "??"); +range quantifiers: "{x}" (exactly x occurrences), "{x,y}" (at least x, at most y, occurrences), {x,} (x occurrences or more), and their lazy versions; +the beginning-of-input ("^") and end-of-input ("$") anchors; +simple grouping ("(...)") and alternation ("|"). +Finally, implementations MUST NOT take regular expressions to be anchored, neither at the beginning nor at the end. This means, for instance, the pattern "es" matches "expression". ``` -@Test(expected = JsonSchemaException.class) -public void testInvalidPatternPropertiesValidatorECMA262() throws Exception { - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setEcma262Validator(true); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - JsonSchema schema = factory.getSchema("{\"patternProperties\":6}", config); - - JsonNode node = getJsonNodeFromStringContent(""); - Set errors = schema.validate(node); - Assert.assertEquals(errors.size(), 0); + +## Considerations when selecting implementation + +If strict compliance with the regular expression dialect described in ECMA-262 is required. Then only the `GraalJS` implementation meets that criteria. + +The `Joni` implementation is configured to attempt to match the ECMA-262 regular expression dialect. However this dialect isn't directly maintained by its maintainers as it doesn't come from its upstream `Oniguruma`. The current implementation has known issues matching inputs with newlines and not respecting `^` and `$` anchors. + +The `JDK` implementation is the default and uses `java.util.regex` with the `find()` method. + +As the implementations are used when validating regular expressions, using `format` `regex`, one consideration is how the regular expression is used. For instance if the system that consumes the input is implemented in Javascript then the `GraalJS` implementation will ensure that this regular expression will work. If the system that consumes the input is implemented in Java then the `JDK` implementation may be better. + +## Configuration of implementation + +The following test case shows how to pass a config object to use the `GraalJS` factory. + +```java +public class RegularExpressionTest { + @Test + public void testInvalidRegexValidatorECMA262() throws Exception { + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder() + .regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance()) + .build(); + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + JsonSchema schema = factory.getSchema("{\r\n" + + " \"format\": \"regex\"\r\n" + + "}", config); + Set errors = schema.validate("\"\\\\a\"", InputFormat.JSON, executionContext -> { + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true); + }); + assertFalse(errors.isEmpty()); + } } ``` +## Performance + +The following is the relative performance of the different implementations. + +``` +Benchmark Mode Cnt Score Error Units +RegularExpressionBenchmark.graaljs thrpt 6 362696.226 ± 15811.099 ops/s +RegularExpressionBenchmark.graaljs:gc.alloc.rate thrpt 6 2584.386 ± 112.708 MB/sec +RegularExpressionBenchmark.graaljs:gc.alloc.rate.norm thrpt 6 7472.003 ± 0.001 B/op +RegularExpressionBenchmark.graaljs:gc.count thrpt 6 130.000 counts +RegularExpressionBenchmark.graaljs:gc.time thrpt 6 144.000 ms +RegularExpressionBenchmark.jdk thrpt 6 2776184.321 ± 41838.479 ops/s +RegularExpressionBenchmark.jdk:gc.alloc.rate thrpt 6 1482.565 ± 22.343 MB/sec +RegularExpressionBenchmark.jdk:gc.alloc.rate.norm thrpt 6 560.000 ± 0.001 B/op +RegularExpressionBenchmark.jdk:gc.count thrpt 6 74.000 counts +RegularExpressionBenchmark.jdk:gc.time thrpt 6 78.000 ms +RegularExpressionBenchmark.joni thrpt 6 1810229.581 ± 35230.798 ops/s +RegularExpressionBenchmark.joni:gc.alloc.rate thrpt 6 1463.887 ± 28.483 MB/sec +RegularExpressionBenchmark.joni:gc.alloc.rate.norm thrpt 6 848.003 ± 0.001 B/op +RegularExpressionBenchmark.joni:gc.count thrpt 6 73.000 counts +RegularExpressionBenchmark.joni:gc.time thrpt 6 77.000 ms +``` diff --git a/doc/metaschema-validation.md b/doc/metaschema-validation.md new file mode 100644 index 000000000..5c49ddf8b --- /dev/null +++ b/doc/metaschema-validation.md @@ -0,0 +1,20 @@ +If you have an use case to validate custom schemas against the one of the JSON schema draft version, here is the code that you can do it. + +``` + public static final Function> validateAgainstMetaSchema = + schema -> { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); + JsonSchema metaSchema = factory.getSchema(getSchemaUri()); + return metaSchema.validate(schema).stream() + .map((validation) -> new SchemaValidationMessage(validation.getMessage())) + .collect(Collectors.toSet()); + }; + +``` + +This should now work but does not support all the keywords because the JsonMetaSchema of SpecVersion.VersionFlag.V201909 is lacking these features. + +You can fix the issue by resolving the vocabularies to a local resource file and re-do the JsonMetaSchema for 2019 based on that. + + + diff --git a/doc/migration-2.0.0.md b/doc/migration-2.0.0.md new file mode 100644 index 000000000..8294847f9 --- /dev/null +++ b/doc/migration-2.0.0.md @@ -0,0 +1,138 @@ +## Migration to 2.0.0 from 1.5.x + +### Compatibility + +| Version | Java Compatibility | Jackson Version | Comments | +| ----------------- | ------------------ | --------------- | -------------------------------------------------------------------------------------------------- | +| `2.0.0` | Java 8 | Jackson 2 | This allows clients that still require to use Java 8 to have an incremental upgrade path to 3.0.0. | +| `3.0.0` (Planned) | Java 17 | Jackson 3 | The change to Java compatibility is because Jackson 3 requires Java 17. | + +### Major Changes + +- Removal of deprecated methods and functionality from 1.x. +- Major renaming of many of the public APIs and moving of classes into sub-packages. +- Errors are returned as a `List` instead of a `Set`. +- External resources will not be automatically fetched by default. This now requires opt-in via configuration. + - This is to conform to the specification that requires such functionality to be disabled by default to prefer offline operation. Note however that classpath resources will still be automatically loaded. + +#### Renaming and Refactoring + +| Old | New | Comments | +| ----------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `com.networknt.schema.JsonMetaSchema` | `com.networknt.schema.dialect.Dialect` | Renamed to convey that this represents the dialect which has a set of keywords and vocabularies with precise semantics. The dialect id is used to identify the meta-schema which can be used to validate that a schema conforms to this dialect. | +| `com.networknt.schema.JsonMetaSchemaFactory` | `com.networknt.schema.dialect.DialectRegistry` | Renamed to convey that this stores a set of registered dialects that will be used when `$schema` is found in a schema. For instance it is possible to override a standard dialect specified by a specification by registering a dialect using that dialect id. | +| `com.networknt.schema.JsonSchema` | `com.networknt.schema.Schema` | Simplify naming. It is no longer possible to associate a configuration with a specific schema. | +| `com.networknt.schema.JsonSchemaFactory` | `com.networknt.schema.SchemaRegistry` | Renamed to convey that this stores a set of registered schema resources. All schemas created will use the same configuration used for the registry. Therefore all the keywords of the schemas in the registry will be consistent configured. If there is a need for different configuration, a separate schema registry should be used. | +| `com.networknt.schema.ValidationMessage` | `com.networknt.schema.Error` | Renamed to convey the intent better as a validation error raised by assertion keywords when processing instance data, or as a parse error when processing the schema data. The instance location has also been removed from the message. Therefore calling `error.getMessage()` no longer has the instance location pre-pended to the message. Calling `error.toString()` will return the message with the instance location prepended if the instance location exists. | +| `com.networknt.schema.SchemaValidatorsConfig` | `com.networknt.schema.SchemaRegistryConfig` | Renamed to convey that this configuration is shared by all schemas from the same schema registry. The walk configuration has been moved out. | +| `com.networknt.schema.SchemaValidatorsConfig` | `com.networknt.schema.walk.WalkConfig` | The walk configuration has been moved to a separate class. | +| `com.networknt.schema.ErrorMessageType` | No replacement | The concept of error codes have been removed, instead the message keys used for generating the localised messages can be used instead to distinguish the error messages. | +| `com.networknt.schema.ValidationContext` | `com.networknt.schema.SchemaContext` | Renamed to convey that this is the schema context shared for all the schemas and validators for the same overall schema with the same dialect. | +| `com.networknt.schema.ValidatorTypeCode` | `com.networknt.schema.keyword.KeywordType` | Renamed to convey that these are keywords as the error codes have been removed. | +| `com.networknt.schema.JsonSchemaValidator` | `com.networknt.schema.Validator` | Simplify naming. | +| `com.networknt.schema.JsonValidator` | `com.networknt.schema.keyword.KeywordValidator` | Renamed to convey the intent that this is the validator created for keywords. | +| `com.networknt.schema.walk.JsonSchemaWalker` | `com.networknt.schema.walk.Walker` | Simplify naming. | +| `com.networknt.schema.SpecVersion.VersionFlag` | `com.networknt.schema.SpecificationVersion` | Renamed and flatten the hierarchy. | +| `com.networknt.schema.SchemaId` | `com.networknt.schema.dialect.DialectId` | Renamed to convey that this is the dialect id used for the `$schema` keyword in schemas and `$id` keyword in meta-schemas. | +| `com.networknt.schema.VocabularyFactory` | `com.networknt.schema.vocabulary.VocabularyRegistry` | Renamed to convey that this stores a set of registered vocabularies that contain keywords. | +| `com.networknt.schema.JsonSchemaIdValidator` | `com.networknt.schema.SchemaIdValidator` | Simplify naming. | +| `com.networknt.schema.JsonSchemaRef` | `com.networknt.schema.SchemaRef` | Simplify naming. | +| `com.networknt.schema.JsonSchemaException` | `com.networknt.schema.SchemaException` | Simplify naming. | +| `com.networknt.schema.JsonNodePath` | `com.networknt.schema.NodePath` | Simplify naming. | +| `com.networknt.schema.serialization.JsonNodeReader` | `com.networknt.schema.serialization.NodeReader` | Simplify naming. | +| `com.networknt.schema.annotation.JsonNodeAnnotation` | `com.networknt.schema.annotation.Annotation` | Simplify naming. | +| `com.networknt.schema.annotation.JsonNodeAnnotations` | `com.networknt.schema.annotation.Annotations` | Simplify naming. | +| `com.networknt.schema.ValidationResult` | `com.networknt.schema.Result` | Renamed to convey that this stores not just validation results but the output from walking. | +| `com.networknt.schema.VersionCode` | `com.networknt.schema.SpecificationVersionRange` | Renamed to convey that this contains specification version ranges. | + +#### Configuration + +##### Schema Validators Configuration + +The `com.networknt.schema.SchemaValidatorsConfig` file has been replaced by either `com.networknt.schema.SchemaRegistryConfig` or `com.networknt.schema.walk.WalkConfig` or moved to `com.networknt.schema.ExecutionConfig` and can no longer be configured on a per schema basis. + +| Name | Migration | +| ------------------------------------- | -------------------------------------------------------- | +| `applyDefaultsStrategy` | `com.networknt.schema.walk.WalkConfig` | +| `cacheRefs` | `com.networknt.schema.SchemaRegistryConfig` | +| `discriminatorKeywordEnabled` | Removed. Dialect must contain a `discriminator` keyword. | +| `errorMessageKeyword` | `com.networknt.schema.SchemaRegistryConfig` | +| `executionContextCustomizer` | `com.networknt.schema.SchemaRegistryConfig` | +| `failFast` | `com.networknt.schema.SchemaRegistryConfig` | +| `formatAssertionsEnabled` | `com.networknt.schema.SchemaRegistryConfig` | +| `javaSemantics` | `com.networknt.schema.SchemaRegistryConfig` | +| `locale` | `com.networknt.schema.SchemaRegistryConfig` | +| `losslessNarrowing` | `com.networknt.schema.SchemaRegistryConfig` | +| `messageSource` | `com.networknt.schema.SchemaRegistryConfig` | +| `nullableKeywordEnabled` | Removed. Dialect must contain a `nullable` keyword. | +| `pathType` | `com.networknt.schema.SchemaRegistryConfig` | +| `preloadJsonSchema` | `com.networknt.schema.SchemaRegistryConfig` | +| `preloadJsonSchemaRefMaxNestingDepth` | `com.networknt.schema.SchemaRegistryConfig` | +| `readOnly` | `com.networknt.schema.ExecutionConfig` | +| `regularExpressionFactory` | `com.networknt.schema.SchemaRegistryConfig` | +| `schemaIdValidator` | `com.networknt.schema.SchemaRegistryConfig` | +| `strict` | `com.networknt.schema.SchemaRegistryConfig` | +| `typeLoose` | `com.networknt.schema.SchemaRegistryConfig` | +| `writeOnly` | `com.networknt.schema.ExecutionConfig` | +| `itemWalkListeners` | `com.networknt.schema.walk.WalkConfig` | +| `keywordWalkListeners` | `com.networknt.schema.walk.WalkConfig` | +| `propertyWalkListeners` | `com.networknt.schema.walk.WalkConfig` | + +#### API + +```java +package com.example.demo; + +import java.util.List; +import java.util.Map; + +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.dialect.Dialects; + +public class Demo { + public static void main(String[] args) { + String schemaData = """ + { + "$id": "https://example.com/address.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "streetAddress": { + "type": "string" + }, + "locality": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "countryName": { + "type": "string" + } + }, + "required": [ "locality", "region", "countryName" ] + } + """; + String instanceData = """ + { + "streetAddress": "456 Main St", + "region": "State", + "postalCode": "12345", + "countryName": "Country" + } + """; + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012(), + builder -> builder.schemas(Map.of("https://example.com/address.schema.json", schemaData))); + Schema schema = schemaRegistry.getSchema(SchemaLocation.of("https://example.com/address.schema.json")); + List errors = schema.validate(instanceData, InputFormat.JSON); + System.out.println(errors); + } +} +``` diff --git a/doc/multiple-language.md b/doc/multiple-language.md new file mode 100644 index 000000000..92881501d --- /dev/null +++ b/doc/multiple-language.md @@ -0,0 +1,70 @@ +The error messages have been translated to several languages by contributors, defined in the `jsv-messages.properties` resource +bundle under https://github.com/networknt/json-schema-validator/tree/master/src/main/resources. To use one of the +available translations the simplest approach is to set your default locale before running the validation: + +```java +// Set the default locale to German (needs only to be set once before using the validator) +Locale.setDefault(Locale.GERMAN); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); +JsonSchema schema = factory.getSchema(source); +... +``` + +Note that the above approach changes the locale for the entire JVM which is probably not what you want to do if you are +using this in an application expected to support multiple languages (for example a localised web application). In this +case you should use the `SchemaValidatorsConfig` class before loading your schema: + +```java +// Set the configuration with a specific locale (you can create this before each validation) +SchemaValidatorsConfig config = new SchemaValidatorsConfig(); +config.setLocale(myLocale); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); +JsonSchema schema = factory.getSchema(source, config); +... +``` + +Besides setting the locale and using the default resource bundle, you may also specify your own to cover any languages you +choose without adapting the library's source, or to override default messages. In doing so you however you should ensure that your resource bundle covers all the keys defined by the default bundle. + +```java +// Set the configuration with a custom message source +MessageSource messageSource = new ResourceBundleMessageSource("my-messages"); +SchemaValidatorsConfig config = new SchemaValidatorsConfig(); +config.setMessageSource(messageSource); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); +JsonSchema schema = factory.getSchema(source, config); +... +``` + +It is possible to override specific keys from the default resource bundle. Note however that you will need to supply all the languages for that specific key as it will not fallback on the default resource bundle. For instance the jsv-messages-override resource bundle will take precedence when resolving the message key. + +```java +// Set the configuration with a custom message source +MessageSource messageSource = new ResourceBundleMessageSource("jsv-messages-override", DefaultMessageSource.BUNDLE_BASE_NAME); +SchemaValidatorsConfig config = new SchemaValidatorsConfig(); +config.setMessageSource(messageSource); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); +JsonSchema schema = factory.getSchema(source, config); +... +``` + +The following approach can be used to determine the locale to use on a per user basis using a language tag priority list. + +```java +SchemaValidatorsConfig config = new SchemaValidatorsConfig(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); +JsonSchema schema = factory.getSchema(source, config); + +// Uses the fr locale for this user +Locale locale = Locales.findSupported("it;q=0.9,fr;q=1.0"); +ExecutionContext executionContext = jsonSchema.createExecutionContext(); +executionContext.getExecutionConfig().setLocale(locale); +Set messages = jsonSchema.validate(executionContext, rootNode); + +// Uses the it locale for this user +locale = Locales.findSupported("it;q=1.0,fr;q=0.9"); +executionContext = jsonSchema.createExecutionContext(); +executionContext.getExecutionConfig().setLocale(locale); +messages = jsonSchema.validate(executionContext, rootNode); +... +``` diff --git a/doc/openapi-discriminators.md b/doc/openapi-discriminators.md index 0983e98f4..80134e66c 100644 --- a/doc/openapi-discriminators.md +++ b/doc/openapi-discriminators.md @@ -2,28 +2,32 @@ ## OpenAPI 3.x discriminator support -Starting with `1.0.51`, `json-schema-validator` partly supports the use of discriminators as described under -https://github.com/OAI/OpenAPI-Specification/blame/master/versions/3.0.3.md#L2693 and following. +Starting with `1.0.51`, `json-schema-validator` partly supports the use of the [`discriminator`](https://github.com/OAI/OpenAPI-Specification/blob/7cc8f4c4e742a20687fa65ace54ed32fcb8c6df0/versions/3.1.0.md#discriminator-object) keyword. + +Note that the use of the `discriminator` keyword does not affect the validation of `anyOf` or `oneOf`. The use of `discriminator` is not equivalent to having a `if`/`then` with the `discriminator` propertyName. + +When a `discriminator` is used, the assertions generated by `anyOf` or `oneOf` will only be the assertions generated from the schema that the discriminator applies to. An assertion will be generated if a `discriminator` is used but there is no matching schema that maps to the value in the `propertyName`. ## How to use 1. Configure `SchemaValidatorsConfig` accordingly: ```java class Demo{ - void demo() { + void demo() { SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setOpenAPI3StyleDiscriminators(true); // defaults to false - } + } } ``` -2. Use the configured `SchemaValidatorsConfig` with the `JSONSchemaFactory` when creating the `JSONSchema` +2. Use the configured `SchemaValidatorsConfig` with the `JsonSchemaFactory` when creating the `JsonSchema` ```java class Demo{ - void demo() { + void demo() { SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setOpenAPI3StyleDiscriminators(true); // defaults to false - JsonSchema schema = validatorFactory.getSchema(schemaURI, schemaJacksonJsonNode, config); - } + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + JsonSchema schema = factory.getSchema(schemaURI, schemaJacksonJsonNode, config); + } } ``` 3. Ensure that the type field that you want to use as discriminator `propertyName` is required in your schema @@ -43,9 +47,6 @@ those parts that are indisputable are considered at this moment. * `propertyName` redefinition is prohibited on additive discriminators * `mapping` key redefinition is also prohibited on additive discriminators -* `oneOf` ignores discriminators as today it is not clear from the spec whether `oneOf` + `discriminator` should be equal to - `anyOf` + `discriminator` or not. Especially if `oneOf` should respect the discriminator and skip the other schemas, it's - functionally not JSON Schema `oneOf` anymore as multiple matches would not make the validation fail anymore. * the specification indicates that inline properties should be ignored. So, this example would respect `foo` ```yaml @@ -71,11 +72,11 @@ those parts that are indisputable are considered at this moment. ## Schema Examples -more examples in https://github.com/networknt/json-schema-validator/blob/master/src/test/resources/openapi3/discriminator.json +More examples in https://github.com/networknt/json-schema-validator/blob/master/src/test/resources/openapi3/discriminator.json ### Base type and extended type (the `anyOf` forward references are required) -#### the simplest example: +#### Example: ```json { @@ -187,5 +188,3 @@ more examples in https://github.com/networknt/json-schema-validator/blob/master/ } } ``` - -### diff --git a/doc/openapi.md b/doc/openapi.md new file mode 100644 index 000000000..da1c6ab45 --- /dev/null +++ b/doc/openapi.md @@ -0,0 +1,48 @@ +# OpenAPI Specification + +The library includes support for the [OpenAPI Specification](https://swagger.io/specification/). + +## Validating a request / response defined in an OpenAPI document + +The library can be used to validate requests and responses with the use of the appropriate meta-schema. + +| Dialect | Meta-schema | +|--------------------------------------------------|----------------------------------------------------| +| `https://spec.openapis.org/oas/3.0/dialect` | `com.networknt.schema.oas.OpenApi30.getInstance()` | +| `https://spec.openapis.org/oas/3.1/dialect/base` | `com.networknt.schema.oas.OpenApi31.getInstance()` | + +```java +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, + builder -> builder.metaSchema(OpenApi31.getInstance()) + .defaultMetaSchemaIri(OpenApi31.getInstance().getIri())); +JsonSchema schema = factory.getSchema(SchemaLocation.of( + "classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetRequest")); +String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"bark\": \"woof\"\r\n" + + "}"; +Set messages = schema.validate(input, InputFormat.JSON); +``` + +## Validating an OpenAPI document + +The library can be used to validate OpenAPI documents, however the OpenAPI meta-schema documents are not bundled with the library. + +It is recommended that the relevant meta-schema documents are placed in the classpath and are mapped otherwise they will be loaded over the internet. + +The following are the documents required to validate a OpenAPI 3.1 document +* `https://spec.openapis.org/oas/3.1/schema-base/2022-10-07` +* `https://spec.openapis.org/oas/3.1/schema/2022-10-07` +* `https://spec.openapis.org/oas/3.1/dialect/base` +* `https://spec.openapis.org/oas/3.1/meta/base` + +```java +SchemaValidatorsConfig config = new SchemaValidatorsConfig(); +config.setPathType(PathType.JSON_POINTER); +JsonSchema schema = JsonSchemaFactory + .getInstance(VersionFlag.V202012, + builder -> builder.schemaMappers(schemaMappers -> schemaMappers + .mapPrefix("https://spec.openapis.org/oas/3.1", "classpath:oas/3.1"))) + .getSchema(SchemaLocation.of("https://spec.openapis.org/oas/3.1/schema-base/2022-10-07"), config); +Set messages = schema.validate(openApiDocument, InputFormat.JSON); +``` diff --git a/doc/quickstart.md b/doc/quickstart.md index 1425149c7..f427e7b7e 100644 --- a/doc/quickstart.md +++ b/doc/quickstart.md @@ -1,83 +1,142 @@ ## Quick Start -To use the validator, we need to have both the JsonSchema object and JsonNode object constructed. -There are many ways to do that. -Here is base test class, that shows several ways to construct these from String, Stream, Url, and JsonNode. -Please pay attention to the JsonSchemaFactory class as it is the way to construct the JsonSchema object. +To use the validator, the `JsonSchema` first needs to be loaded. For performance it is recommended that the `JsonSchema` is cached. -```java -public class BaseJsonSchemaValidatorTest { - - private ObjectMapper mapper = new ObjectMapper(); +The following examples demonstrate loading the `JsonSchema` in the following manner. +* `SchemaLocation` with the value of the `$id` of the schema which is mapped using the `SchemaMapper` to the retrieval IRI which is on the classpath +* `SchemaLocation` with the value of the `$id` of the schema where the content of the schema is supplied using the `SchemaLoader` +* `SchemaLocation` with the value of the retrieval IRI which is on the classpath +* `String` with the content of the schema +* `JsonNode` with the content of the schema - protected JsonNode getJsonNodeFromClasspath(String name) throws IOException { - InputStream is1 = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(name); - return mapper.readTree(is1); - } - - protected JsonNode getJsonNodeFromStringContent(String content) throws IOException { - return mapper.readTree(content); - } +The preferred method of loading a schema is by using a `SchemaLocation` and by configuring the appropriate `SchemaMapper` and `SchemaLoader` on the `JsonSchemaFactory`. The `SchemaMapper` is use to map the `$id` to the retrieval IRI. The `SchemaLoader` is used to actually load the content of the schema. - protected JsonNode getJsonNodeFromUrl(String url) throws IOException { - return mapper.readTree(new URL(url)); - } +Loading a schema from a `String` or `JsonNode` is not recommended as a relative `$ref` will not be properly resolved as there is no base IRI. - protected JsonSchema getJsonSchemaFromClasspath(String name) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - InputStream is = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(name); - return factory.getSchema(is); +```java +package com.example; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.SpecVersion.VersionFlag; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Sample test. + */ +public class SampleTest { + @Test + void schemaFromSchemaLocationMapping() throws JsonMappingException, JsonProcessingException { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.schemaMappers( + schemaMappers -> schemaMappers.mapPrefix("https://www.example.com/schema", "classpath:schema"))); + /* + * This should be cached for performance. + */ + JsonSchema schemaFromSchemaLocation = factory + .getSchema(SchemaLocation.of("https://www.example.com/schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromSchemaLocation.initializeValidators(); + Set errors = schemaFromSchemaLocation.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertEquals(1, errors.size()); } - protected JsonSchema getJsonSchemaFromStringContent(String schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - return factory.getSchema(schemaContent); + @Test + void schemaFromSchemaLocationContent() throws JsonMappingException, JsonProcessingException { + String schemaData = "{\"enum\":[1, 2, 3, 4]}"; + + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, + builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas( + Collections.singletonMap("https://www.example.com/schema/example-ref.json", schemaData)))); + /* + * This should be cached for performance. + */ + JsonSchema schemaFromSchemaLocation = factory + .getSchema(SchemaLocation.of("https://www.example.com/schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromSchemaLocation.initializeValidators(); + Set errors = schemaFromSchemaLocation.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertEquals(1, errors.size()); } - protected JsonSchema getJsonSchemaFromUrl(String uri) throws URISyntaxException { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - return factory.getSchema(new URI(uri)); + @Test + void schemaFromClasspath() throws JsonMappingException, JsonProcessingException { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + /* + * This should be cached for performance. + * + * Loading from using the retrieval IRI is not recommended as it may cause + * confusing when resolving relative $ref when $id is also used. + */ + JsonSchema schemaFromClasspath = factory.getSchema(SchemaLocation.of("classpath:schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromClasspath.initializeValidators(); + Set errors = schemaFromClasspath.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertEquals(1, errors.size()); } - protected JsonSchema getJsonSchemaFromJsonNode(JsonNode jsonNode) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - return factory.getSchema(jsonNode); + @Test + void schemaFromString() throws JsonMappingException, JsonProcessingException { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + /* + * This should be cached for performance. + * + * Loading from a String is not recommended as there is no base IRI to use for + * resolving relative $ref. + */ + JsonSchema schemaFromString = factory + .getSchema("{\"enum\":[1, 2, 3, 4]}"); + Set errors = schemaFromString.validate("7", InputFormat.JSON, + executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertEquals(1, errors.size()); } - // Automatically detect version for given JsonNode - protected JsonSchema getJsonSchemaFromJsonNodeAutomaticVersion(JsonNode jsonNode) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(jsonNode)); - return factory.getSchema(jsonNode); + @Test + void schemaFromJsonNode() throws JsonMappingException, JsonProcessingException { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); + JsonNode schemaNode = JsonMapperFactory.getInstance().readTree( + "{\"$schema\": \"http://json-schema.org/draft-06/schema#\", \"properties\": { \"id\": {\"type\": \"number\"}}}"); + /* + * This should be cached for performance. + * + * Loading from a JsonNode is not recommended as there is no base IRI to use for + * resolving relative $ref. + * + * Note that the V202012 from the factory is the default version if $schema is not + * specified. As $schema is specified in the data, V6 is used. + */ + JsonSchema schemaFromNode = factory.getSchema(schemaNode); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromNode.initializeValidators(); + Set errors = schemaFromNode.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertEquals(1, errors.size()); } - } ``` -And the following is one of the test cases in one of the test classes that extend from the above base class. As you can see, it constructs JsonSchema and JsonNode from String. - -```java -class Sample extends BaseJsonSchemaValidatorTest { - - void test() { - JsonSchema schema = getJsonSchemaFromStringContent("{\"enum\":[1, 2, 3, 4],\"enumErrorCode\":\"Not in the list\"}"); - JsonNode node = getJsonNodeFromStringContent("7"); - Set errors = schema.validate(node); - assertThat(errors.size(), is(1)); - - // With automatic version detection - JsonNode schemaNode = getJsonNodeFromStringContent( - "{\"$schema\": \"http://json-schema.org/draft-06/schema#\", \"properties\": { \"id\": {\"type\": \"number\"}}}"); - JsonSchema schema = getJsonSchemaFromJsonNodeAutomaticVersion(schemaNode); - - schema.initializeValidators(); // by default all schemas are loaded lazily. You can load them eagerly via - // initializeValidators() - - JsonNode node = getJsonNodeFromStringContent("{\"id\": \"2\"}"); - Set errors = schema.validate(node); - assertThat(errors.size(), is(1)); - } - -} - -``` diff --git a/doc/schema-map.md b/doc/schema-map.md deleted file mode 100644 index f96bdaa34..000000000 --- a/doc/schema-map.md +++ /dev/null @@ -1,24 +0,0 @@ -While working with JSON schema validation, we have to use external references sometimes. However, there are two issues to have references to schemas on the Internet. - -* Some applications are running inside a corporate network without Internet access. -* Some of the Internet resources are not reliable - -One solution is to change all the external reference to internal in JSON schemas, but this is error-prone and hard to maintain in a long run. - -A smart solution is to map the external references to internal ones in a configuration file. This allows us to use the resources as they are without any modification. In the JSON schema specification, it is not allowed to use local filesystem resource directly. With the mapping, we can use the local resources without worrying about breaking the specification as the references are still in URL format in schemas. In addition, the mapped URL can be a different external URL, or embbeded within a JAR file with a lot more flexibility. - -Note that when using a mapping, the local copy is always used, and the external reference is not queried. - -### Usage - -Basically, you can specify a mapping in the builder. For more details, please take a look at the test cases and the [PR](https://github.com/networknt/json-schema-validator/pull/125). - - -### Real Example - -https://github.com/JMRI/JMRI/blob/master/java/src/jmri/server/json/schema-map.json - -In case you provide the schema through an InputStream or a String to resolve $ref with URN (relative path), you need to provide the URNFactory to the JsonSchemaFactory.Builder. -URNFactory interface will allow you to resolve URN to URI. - -please take a look at the test cases and the [PR](https://github.com/networknt/json-schema-validator/pull/274). diff --git a/doc/schema-retrieval.md b/doc/schema-retrieval.md new file mode 100644 index 000000000..43842e50a --- /dev/null +++ b/doc/schema-retrieval.md @@ -0,0 +1,158 @@ +# Customizing Schema Retrieval + +A schema can be identified by its schema identifier which is indicated using the `$id` keyword or `id` keyword in earlier drafts. This is an absolute IRI that uniquely identifies the schema and is not necessarily a network locator. A schema need not be downloadable from it's absolute IRI. + +In the event a schema references a schema identifier that is not a subschema resource, for instance defined in the `$defs` keyword or `definitions` keyword. The library will need to be able to retrieve the schema given its schema identifier. + +In the event that the schema does not define a schema identifier using the `$id` keyword, the retrieval IRI will be used as it's schema identifier. + +## Loading Schemas from memory + +Schemas can be loaded through a map. + +```java +String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; +Map schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas))); +``` + +Schemas can be loaded through a function. + +```java +String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; +Map schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); + JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas::get))); +``` + +Schemas can also be loaded in the following manner. + +```java +class RegistryEntry { + private final String schemaData; + + public RegistryEntry(String schemaData) { + this.schemaData = schemaData; + } + + public String getSchemaData() { + return this.schemaData; + } +} + +String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; +Map registry = Collections + .singletonMap("https://www.example.com/integer.json", new RegistryEntry(schemaData)); +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, builder -> builder + .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(registry::get, RegistryEntry::getSchemaData))); +``` + +## Mapping Schema Identifier to Retrieval IRI + +The schema identifier can be mapped to the retrieval IRI by implementing the `SchemaMapper` interface. + +### Configuring Schema Mapper + +```java +class CustomSchemaMapper implements SchemaMapper { + @Override + public AbsoluteIri map(AbsoluteIri absoluteIRI) { + String iri = absoluteIRI.toString(); + if ("https://www.example.com/integer.json".equals(iri)) { + return AbsoluteIri.of("classpath:schemas/integer.json"); + } + return null; + } +} + +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder.schemaMappers(schemaMappers -> schemaMappers.add(new CustomSchemaMapper()))); +``` + +### Configuring Prefix Mappings + +```java +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder + .schemaMappers(schemaMappers -> schemaMappers + .mapPrefix("https://json-schema.org", "classpath:") + .mapPrefix("http://json-schema.org", "classpath:"))); +``` + +### Configuring Mappings + +```java +Map mappings = Collections + .singletonMap("https://www.example.com/integer.json", "classpath:schemas/integer.json"); + +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings))); +``` + +## Customizing Network Schema Retrieval + +The default `UriSchemaLoader` implementation uses JDK connection/socket without handling network exceptions. It works in most of the cases; however, if you want to have a customized implementation, you can do so. One user has his implementation with urirest to handle the timeout. A detailed discussion can be found in this [issue](https://github.com/networknt/json-schema-validator/issues/240) + +### Configuring Custom URI Schema Loader + +The default `UriSchemaLoader` can be overwritten in order to customize its behaviour in regards of authorization or error handling. + +The `SchemaLoader` interface must implemented and the implementation configured on the `JsonSchemaFactory`. + +```java +public class CustomUriSchemaLoader implements SchemaLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(CustomUriSchemaLoader.class); + private final String authorizationToken; + private final HttpClient client; + + public CustomUriSchemaLoader(String authorizationToken) { + this.authorizationToken = authorizationToken; + this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + } + + @Override + public InputStreamSource getSchema(AbsoluteIri absoluteIri) { + String scheme = absoluteIri.getScheme(); + if ("https".equals(scheme) || "http".equals(scheme)) { + URI uri = URI.create(absoluteIri.toString()); + return () -> { + HttpRequest request = HttpRequest.newBuilder().uri(uri).header("Authorization", authorizationToken).build(); + try { + HttpResponse response = this.client.send(request, HttpResponse.BodyHandlers.ofString()); + if ((200 > response.statusCode()) || (response.statusCode() > 299)) { + String errorMessage = String.format("Could not get data from schema endpoint. The following status %d was returned.", response.statusCode()); + LOGGER.error(errorMessage); + } + return new ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + return null; + } +} +``` + +Within the `JsonSchemaFactory` the custom `SchemaLoader` must be configured. + +```java +CustomUriSchemaLoader uriSchemaLoader = new CustomUriSchemaLoader(authorizationToken); + +JsonSchemaFactory schemaFactory = JsonSchemaFactory + .getInstance(VersionFlag.V7, + builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.add(uriSchemaLoader))); +``` diff --git a/doc/specversion.md b/doc/specversion.md index 159dbefdb..0943a9f3c 100644 --- a/doc/specversion.md +++ b/doc/specversion.md @@ -1,4 +1,4 @@ -The library supports V4, V6, V7, and V2019-09 JSON schema specifications. By default, V4 is used for backward compatibility. +The library supports V4, V6, V7, V2019-09 and V2020-12 JSON schema specifications. By default, V4 is used for backward compatibility. ### For Users @@ -13,7 +13,7 @@ or with default configuration JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)); ``` -Please avoid using default getInstance(), which, internally, defaults to the SpecVersion.VersionFlag.V4 as the parameter. This is deprecated. +Please avoid using default `getInstance()`, which, internally, defaults to the `SpecVersion.VersionFlag.V4` as the parameter. This is deprecated. #### To create a draft V6 JsonSchemaFactory @@ -48,6 +48,17 @@ or with default configuration JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)); ``` +#### To create a draft 2020-12 JsonSchemaFactory + +```java +ObjectMapper mapper = new ObjectMapper(); +JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)).objectMapper(mapper).build(); +``` +or with default configuration +```java +JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)); +``` + #### To create a JsonSchemaFactory, automatically detecting schema version ```java @@ -66,7 +77,7 @@ JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersionDe #### SpecVersion -A new class SpecVersion has been introduced to indicate which version of the specification is used when creating the JsonSchemaFactory. The SpecVersion has an enum and two methods to convert a long to an EnumSet or a set of VersionFlags to a long value. +A new class `SpecVersion` has been introduced to indicate which version of the specification is used when creating the `JsonSchemaFactory`. The `SpecVersion` has an enum and two methods to convert a long to an `EnumSet` or a set of `VersionFlags` to a long value. ```java public enum VersionFlag { @@ -74,43 +85,45 @@ public enum VersionFlag { V4(1<<0), V6(1<<1), V7(1<<2), - V201909(1<<3); + V201909(1<<3), + V202012(1<<4); ``` -In the long value, we are using 4 bits now as we are supporting 4 versions at the moment. +In the long value, we are using 5 bits now as we are supporting 5 versions at the moment. -V4 -> 0001 -> 1 -V6 -> 0010 -> 2 -V7 -> 0100 -> 4 -V201909 -> 1000 -> 8 +V4 -> 00001 -> 1 +V6 -> 00010 -> 2 +V7 -> 00100 -> 4 +V201909 -> 01000 -> 8 +V202012 -> 10000 --> 16 If we have a new version added, it should be -V202009 -> 10000 -> 16 +V202209 -> 100000 -> 32 #### ValidatorTypeCode A new field versionCode is added to indicate which version the validator is supported. -For most of the validators, the version code should be 15, which is 1111. This means the validator will be loaded for every version of the specification. +For most of the validators, the version code should be 31, which is 11111. This means the validator will be loaded for every version of the specification. For example. -``` -MAXIMUM("maximum", "1011", new MessageFormat("{0}: must have a maximum value of {1}"), MaximumValidator.class, 15), +```java +MAXIMUM("maximum", "1011", new MessageFormat("{0}: must have a maximum value of {1}"), MaximumValidator.class, 31), ``` -Since if-then-else was introduced in the V7, it only works for V7 and V2019-09 +Since if-then-else was introduced in the V7, it only works for V7, V2019-09 and V2020-12 -``` -IF_THEN_ELSE("if", "1037", null, IfValidator.class, 12), // V7|V201909 1100 +```java +IF_THEN_ELSE("if", "1037", null, IfValidator.class, 28), // V7|V201909|V202012 11100 ``` For exclusiveMaximum, it was introduced from V6 -``` -EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", new MessageFormat("{0}: must have a exclusive maximum value of {1}"), ExclusiveMaximumValidator.class, 14), // V6|V7|V201909 +```java +EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", new MessageFormat("{0}: must have a exclusive maximum value of {1}"), ExclusiveMaximumValidator.class, 30), // V6|V7|V201909|V202012 ``` The getNonFormatKeywords method is updated to accept a SpecVersion.VersionFlag so that only the keywords supported by the specification will be loaded. @@ -129,13 +142,13 @@ public static List getNonFormatKeywords(SpecVersion.VersionFl #### JsonMetaSchema -We have created four different static classes V4, V6, V7, and V201909 to build different JsonMetaSchema instances. +We have created four different static classes V4, V6, V7, V201909 and V202012 to build different `JsonMetaSchema` instances. For the BUILDIN_FORMATS, there is a common section, and each static class has its version-specific BUILDIN_FORMATS section. #### JsonSchemaFactory -The getInstance supports a parameter SpecVersion.VersionFlag to get the right instance of the JsonMetaShema to create the factory. If there is no parameter, then V4 is used by default. +The getInstance supports a parameter `SpecVersion.VersionFlag` to get the right instance of the `JsonMetaShema` to create the factory. If there is no parameter, then V4 is used by default. ```java @Deprecated @@ -144,24 +157,11 @@ public static JsonSchemaFactory getInstance() { } public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag) { - JsonMetaSchema metaSchema = null; - switch (versionFlag) { - case V201909: - metaSchema = JsonMetaSchema.getV201909(); - break; - case V7: - metaSchema = JsonMetaSchema.getV7(); - break; - case V6: - metaSchema = JsonMetaSchema.getV6(); - break; - case V4: - metaSchema = JsonMetaSchema.getV4(); - break; - } + JsonSchemaVersion jsonSchemaVersion = checkVersion(versionFlag); + JsonMetaSchema metaSchema = jsonSchemaVersion.getInstance(); return builder() - .defaultMetaSchemaURI(metaSchema.getUri()) - .addMetaSchema(metaSchema) + .defaultMetaSchemaIri(metaSchema.getIri()) + .metaSchema(metaSchema) .build(); } ``` @@ -177,15 +177,20 @@ public static SpecVersion.VersionFlag detect(JsonNode jsonNode) { if (!jsonNode.has(SCHEMA_TAG)) throw new JsonSchemaException("Schema tag not present"); - String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(jsonNode.get(SCHEMA_TAG).asText()); - if (schemaUri.equals(JsonMetaSchema.getV4().getUri())) + final boolean forceHttps = true; + final boolean removeEmptyFragmentSuffix = true; + + String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(jsonNode.get(SCHEMA_TAG).asText(), forceHttps, removeEmptyFragmentSuffix); + if (schemaUri.equals(JsonMetaSchema.getV4().getIri())) return SpecVersion.VersionFlag.V4; - else if (schemaUri.equals(JsonMetaSchema.getV6().getUri())) + else if (schemaUri.equals(JsonMetaSchema.getV6().getIri())) return SpecVersion.VersionFlag.V6; - else if (schemaUri.equals(JsonMetaSchema.getV7().getUri())) + else if (schemaUri.equals(JsonMetaSchema.getV7().getIri())) return SpecVersion.VersionFlag.V7; - else if (schemaUri.equals(JsonMetaSchema.getV201909().getUri())) + else if (schemaUri.equals(JsonMetaSchema.getV201909().getIri())) return SpecVersion.VersionFlag.V201909; + else if (schemaUri.equals(JsonMetaSchema.getV202012().getIri())) + return SpecVersion.VersionFlag.V202012; else throw new JsonSchemaException("Unrecognizable schema"); } @@ -193,7 +198,7 @@ public static SpecVersion.VersionFlag detect(JsonNode jsonNode) { ### For Testers -In the test resource folder, we have created and copied all draft version's test suite. They are located in draft4, draft6, draft7, and draft2019-09 folder. +In the test resource folder, we have created and copied all draft version's test suite. They are located in draft4, draft6, draft7, draft2019-09 and draft2020-12 folders. The existing JsonSchemaTest has been renamed to V4JsonSchemaTest, and the following test classes are added. @@ -201,6 +206,7 @@ The existing JsonSchemaTest has been renamed to V4JsonSchemaTest, and the follow V6JsonSchemaTest V7JsonSchemaTest V201909JsonSchemaTest +V202012JsonSchemaTest ``` These new test classes are not completed yet, and only some sample test cases are added. diff --git a/doc/upgrading.md b/doc/upgrading.md new file mode 100644 index 000000000..2577cee32 --- /dev/null +++ b/doc/upgrading.md @@ -0,0 +1,302 @@ +## Upgrading to new versions + +This library can contain breaking changes in `minor` version releases. + +This contains information on the notable or breaking changes in each version. + +### 1.4.1 + +#### Schema Validators Config + +The `SchemaValidatorsConfig` constructor has been deprecated. Use the `SchemaValidators.builder` to create an instance instead. `SchemaValidatorConfig` instances are intended to be immutable in future and those created by the builder will throw `UnsupportedOperationException` when setters are called. + +Note that there are differences in defaults from the builder vs the constructor. + +The following builder creates the same values as the constructor previously. + +```java +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder() + .pathType(PathType.LEGACY) + .errorMessageKeyword("message") + .nullableKeywordEnabled(true) + .build(); +``` + +The following configurations were renamed with the old ones deprecated +* `handleNullableField` -> `nullableKeywordEnabled` +* `openAPI3StyleDiscriminators` -> `discriminatorKeywordEnabled` +* `customMessageSupported` -> `errorMessageKeyword` + +The following defaults were changed in the builder vs the constructor +* `pathType` from `PathType.LEGACY` to `PathType.JSON_POINTER` +* `handleNullableField` from `true` to `false` +* `customMessageSupported` from `true` to `false` + +When using the builder custom error messages are not enabled by default and must be enabled by specifying the error message keyword to use ie. "message". + +| Deprecated Code | Replacement +|------------------------------------------------------------------------|---------------------------------------------------------------------- +| `SchemaValidatorsConfig config = new SchemaValidatorsConfig();` | `SchemaValidatorsConfig config = SchemaValidatorsConfig().builder().pathType(PathType.LEGACY).errorMessageKeyword("message").nullableKeywordEnabled(true).build();` +| `config.setEcma262Validator(true);` | `builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance());` +| `config.setHandleNullableField(true);` | `builder.nullableKeywordEnabled(true);` +| `config.setOpenAPI3StyleDiscriminators(true);` | `builder.discriminatorKeywordEnabled(true);` +| `config.setCustomMessageSupported(true);` | `builder.errorMessageKeyword("message");` + +#### Collector Context + +`JsonSchema.validateAndCollect` has been deprecated in favor of explicitly calling `loadCollectors`. + +This also deprecates the related `loadCollectors` configuration in `SchemaValidatorsConfig`. + +This makes the `CollectorContext.loadCollectors()` method public to be explicitly called instead of relying on the `SchemaValidatorsConfig`. + +Proper usage of the `validateAndCollect` method is confusing. It relies on a configuration set in `SchemaValidatorsConfig` that is configured on a per schema basis. It immediately runs `loadCollectors` if set to `true` and will never be able to run `loadCollectors` if set to `false` as the method is not `public`. + +The documentation has been updated to reflect the replacement, which is to explicitly create the `CollectorContext` to be shared and set for each execution. Finally `loadCollectors` can be called a the end if needed. + +```java +CollectorContext collectorContext = new CollectorContext(); +// This adds a custom collect keyword that sets values in the CollectorContext whenever it gets processed +JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()).keyword(new CollectKeyword()).build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); +JsonSchema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); +for (int i = 0; i < 50; i++) { + // The shared CollectorContext is set on the ExecutionContext for every run to aggregate data from all the runs + schema.validate("1", InputFormat.JSON, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); +} +// This is called for Collector implementations to aggregate data +collectorContext.loadCollectors(); +AtomicInteger result = (AtomicInteger) collectorContext.get("collect"); +assertEquals(50, result.get()); +``` + +#### Schema Reference Caching + +Previously when schema `$ref` are encountered, the reference and all the validators it requires will always be cached and stored if needed in the future. This can potentially cause out of memory errors for schemas that use applicators like `allOf`, `anyOf`, `oneOf`. This can be configured by setting the `cacheRefs` option to `false` on `SchemaValidatorsConfig.builder()`. Note that not caching will impact performance and make it slower. + +#### Regular Expressions + +This adds GraalJS as an implementation. The Joni implementation now will throw an `Exception` if illegal escapes are used in the regular expressions. + +The preferred way of configuring the implementation is via setting the `regularExpressionFactory` on `SchemaValidatorsConfig.builder()`. + +#### Fine Grain Debug Logging + +Previously the if debug logging is enabled the validators will log fine grained logs. This now requires setting the `debugEnabled` flag in `ExecutionConfig` as the checks to determine if the logger was enabled was impacting performance. + + +### 1.4.0 + +This contains breaking changes +- to those using the walk functionality +- in how custom meta-schemas are created + +When using the walker with defaults the `default` across a `$ref` are properly resolved and used. + +The behavior for the property listener is now more consistent whether or not validation is enabled. Previously if validation is enabled but the property is `null` the property listener is not called while if validation is not enabled it will be called. Now the property listener will be called in both scenarios. + +The following are the breaking changes to those using the walk functionality. + +`WalkEvent` +| Field | Change | Notes +|--------------------------|--------------|---------- +| `schemaLocation` | Removed | For keywords: `getValidator().getSchemaLocation()`. For items and properties: `getSchema().getSchemaLocation()` +| `evaluationPath` | Removed | For keywords: `getValidator().getEvaluationPath()`. For items and properties: `getSchema().getEvaluationPath()` +| `schemaNode` | Removed | `getSchema().getSchemaNode()` +| `parentSchema` | Removed | `getSchema().getParentSchema()` +| `schema` | New | For keywords this is the parent schema of the validator. For items and properties this is the item or property schema being evaluated. +| `node` | Renamed | `instanceNode` +| `currentJsonSchemaFactory`| Removed | `getSchema().getValidationContext().getJsonSchemaFactory()` +| `validator` | New | The validator indicated by the keyword. + + +The following are the breaking changes in how custom meta-schemas are created. + +`JsonSchemaFactory` +* The following were renamed on `JsonSchemaFactory` builder + * `defaultMetaSchemaURI` -> `defaultMetaSchemaIri` + * `enableUriSchemaCache` -> `enableSchemaCache` +* The builder now accepts a `JsonMetaSchemaFactory` which can be used to restrict the loading of meta-schemas that aren't explicitly defined in the `JsonSchemaFactory`. The `DisallowUnknownJsonMetaSchemaFactory` can be used to only allow explicitly configured meta-schemas. + +`JsonMetaSchema` +* In particular `Version201909` and `Version202012` had most of the keywords moved to their respective vocabularies. +* The following were renamed + * `getUri` -> `getIri` +* The builder now accepts a `vocabularyFactory` to allow for custom vocabularies. +* The builder now accepts a `unknownKeywordFactory`. By default this uses the `UnknownKeywordFactory` implementation that logs a warning and returns a `AnnotationKeyword`. The `DisallowUnknownKeywordFactory` can be used to disallow the use of unknown keywords. +* The implementation of the builder now correctly throws an exception for `$vocabulary` with value of `true` that are not known to the implementation. + +`ValidatorTypeCode` +* `getNonFormatKeywords` has been removed and replaced with `getKeywords`. This now includes the `format` keyword as the `JsonMetaSchema.Builder` now needs to know if the `format` keyword was configured, as it might not be in meta-schemas that don't define the format vocabulary. +* The applicable `VersionCode` for each of the `ValidatorTypeCode` were modified to remove the keywords that are defined in vocabularies for `Version201909` and `Version202012`. + +`Vocabulary` +* This now contains `Keyword` instances instead of the string keyword value as it needs to know the explicit implementation. For instance the implementation for the `items` keyword in Draft 2019-09 and Draft 2020-12 are different. +* The following were renamed + * `getId` -> `getIri` + +### 1.3.1 + +This contains a breaking change in that the results from `failFast` are no longer thrown as an exception. The single result is instead returned normally in the output. This was partially done to distinguish the fail fast result from true exceptions such as when references could not be resolved. + +* Annotation collection and reporting has been implemented +* Keywords have been refactored to use annotations for evaluation to improve performance and meet functional requirements +* The list and hierarchical output formats have been implemented as per the [Specification for Machine-Readable Output for JSON Schema Validation and Annotation](https://github.com/json-schema-org/json-schema-spec/blob/main/output/jsonschema-validation-output-machines.md). +* The fail fast evaluation processing has been redesigned and fixed. This currently passes the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) with fail fast enabled. Previously contains and union type may cause incorrect results. +* This also contains fixes for regressions introduced in 1.3.0 + +The following keywords were refactored to improve performance and meet the functional requirements. + +In particular this converts the `unevaluatedItems` and `unevaluatedProperties` validators to use annotations to perform the evaluation instead of the current mechanism which affects performance. This also refactors `$recursiveRef` to not rely on that same mechanism. + +* `unevaluatedProperties` +* `unevaluatedItems` +* `properties` +* `patternProperties` +* `items` / `additionalItems` +* `prefixItems` / `items` +* `contains` +* `$recursiveRef` + +This also fixes the issue where the `unevaluatedItems` keyword does not take into account the `contains` keyword when performing the evaluation. + +This also fixes cases where `anyOf` short-circuits to not short-circuit the evaluation if a adjacent `unevaluatedProperties` or `unevaluatedItems` keyword exists. + +This should fix most of the remaining functional and performance issues. + +#### Functional + +| Implementations | Overall | DRAFT_03 | DRAFT_04 | DRAFT_06 | DRAFT_07 | DRAFT_2019_09 | DRAFT_2020_12 | +|-----------------|-------------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------------| +| NetworkNt | pass: r:4703 (100.0%) o:2369 (100.0%)
fail: r:0 (0.0%) o:1 (0.0%) | | pass: r:600 (100.0%) o:251 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:796 (100.0%) o:318 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:880 (100.0%) o:541 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1201 (100.0%) o:625 (100.0%)
fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1226 (100.0%) o:634 (99.8%)
fail: r:0 (0.0%) o:1 (0.2%) | + +#### Performance + +##### NetworkNT 1.3.1 + +``` +Benchmark Mode Cnt Score Error Units +NetworkntBenchmark.testValidate thrpt 10 6776.693 ± 115.309 ops/s +NetworkntBenchmark.testValidate:·gc.alloc.rate thrpt 10 971.191 ± 16.420 MB/sec +NetworkntBenchmark.testValidate:·gc.alloc.rate.norm thrpt 10 165318.816 ± 0.459 B/op +NetworkntBenchmark.testValidate:·gc.churn.G1_Eden_Space thrpt 10 968.894 ± 51.234 MB/sec +NetworkntBenchmark.testValidate:·gc.churn.G1_Eden_Space.norm thrpt 10 164933.962 ± 8636.203 B/op +NetworkntBenchmark.testValidate:·gc.churn.G1_Survivor_Space thrpt 10 0.002 ± 0.001 MB/sec +NetworkntBenchmark.testValidate:·gc.churn.G1_Survivor_Space.norm thrpt 10 0.274 ± 0.218 B/op +NetworkntBenchmark.testValidate:·gc.count thrpt 10 89.000 counts +NetworkntBenchmark.testValidate:·gc.time thrpt 10 99.000 ms +``` + +###### Everit 1.14.1 + +``` +Benchmark Mode Cnt Score Error Units +EveritBenchmark.testValidate thrpt 10 3719.192 ± 125.592 ops/s +EveritBenchmark.testValidate:·gc.alloc.rate thrpt 10 1448.208 ± 74.746 MB/sec +EveritBenchmark.testValidate:·gc.alloc.rate.norm thrpt 10 449621.927 ± 7400.825 B/op +EveritBenchmark.testValidate:·gc.churn.G1_Eden_Space thrpt 10 1446.397 ± 79.919 MB/sec +EveritBenchmark.testValidate:·gc.churn.G1_Eden_Space.norm thrpt 10 449159.799 ± 18614.931 B/op +EveritBenchmark.testValidate:·gc.churn.G1_Survivor_Space thrpt 10 0.001 ± 0.001 MB/sec +EveritBenchmark.testValidate:·gc.churn.G1_Survivor_Space.norm thrpt 10 0.364 ± 0.391 B/op +EveritBenchmark.testValidate:·gc.count thrpt 10 133.000 counts +EveritBenchmark.testValidate:·gc.time thrpt 10 148.000 ms +``` + +### 1.3.0 + +This adds support for Draft 2020-12 + +This adds support for the following keywords +* `$dynamicRef` +* `$dynamicAnchor` +* `$vocabulary` + +This refactors the schema retrieval codes as the ID is based on IRI and not URI. + +Note that Java does not support IRIs. See https://cr.openjdk.org/%7Edfuchs/writeups/updating-uri/ for details. + +The following are removed and replaced by `SchemaLoader` and `SchemaMapper`. +* `URIFactory` - No replacement. The resolve logic is in `AbsoluteIRI`. +* `URISchemeFactory` - No replacement as `URIFactory` isn't required anymore. +* `URISchemeFetcher` - No replacement. The `SchemaLoaders` are iterated and called. +* `URITranslator` - Replaced by `SchemaMapper`. +* `URLFactory` - No replacement as `URIFactory` isn't required anymore. +* `URLFetcher` - Replaced by `UriSchemaLoader`. +* `URNURIFactory` - No replacement as `URIFactory` isn't required anymore. + +The `SchemaLoader` and `SchemaMapper` are configured in the `JsonSchemaFactory.Builder`. See [Customizing Schema Retrieval](schema-retrieval.md). + +As per the specification. The `format` keyword since Draft 2019-09 no longer generates assertions by default. + +This can be changed by using a custom meta schema with the relevant `$vocabulary` or by setting the execution configuration to enable format assertions. + +### 1.2.0 + +The following are a summary of the changes +* Paths are now specified using the `JsonNodePath`. The paths are `instanceLocation`, `schemaLocation` and `evaluationPath`. The meaning of these paths are as defined in the [specification](https://github.com/json-schema-org/json-schema-spec/blob/main/output/jsonschema-validation-output-machines.md). +* Schema Location comprises an absolute IRI component and a fragment that is a `JsonNodePath` that is typically a JSON pointer +* Rename `at` to `instanceLocation`. Note that for the `required` validator the error message `instanceLocation` does not point to the missing property to be consistent with the [specification](https://json-schema.org/draft/2020-12/json-schema-core#section-12.4.2). The `ValidationMessage` now contains a `property` attribute if this is required. +* Rename `schemaPath` to `schemaLocation`. This should generally be an absolute IRI with a fragment particularly in later drafts. +* Add `evaluationPath` + +`JsonValidator` +* Now contains `getSchemaLocation` and `getEvaluationPath` in the interface +* Implementations now need a constructor that takes in `schemaLocation` and `evaluationPath` +* The `validate` method uses `JsonNodePath` for the `instanceLocation` +* The `validate` method with just the `rootNode` has been removed + +`JsonSchemaWalker` +* The `walk` method uses `JsonNodePath` for the `instanceLocation` + +`WalkEvent` +* Rename `at` to `instanceLocation` +* Rename `schemaPath` to `schemaLocation` +* Add `evaluationPath` +* Rename `keyWordName` to `keyword` + +`WalkListenerRunner` +* Rename `at` to `instanceLocation` +* Rename `schemaPath` to `schemaLocation` +* Add `evaluationPath` + +`BaseJsonValidator` +* The `atPath` methods are removed. Use `JsonNodePath.append` to get the path of the child +* The `buildValidationMessage` methods are removed. Use the `message` builder method instead. + +`CollectorContext` +* The `evaluatedProperties` and `evaluatedItems` are now `Collection` + +`JsonSchema` +* The validator keys are now using `evaluationPath` instead of `schemaPath` +* The `@deprecated` constructor methods have been removed + +`ValidatorTypeCode` +* The `customMessage` has been removed. This made the `ValidatorTypeCode` mutable if the feature was used as the enum is a shared instance. The logic for determining the `customMessage` has been moved to the validator. +* The creation of `newValidator` instances now uses a functional interface instead of reflection. + +`ValidatorState` +* The `ValidatorState` is now a property of the `ExecutionContext`. This change is largely to improve performance. The `CollectorContext.get` method is particularly slow for this use case. + +### 1.1.0 + +Removes use of `ThreadLocal` to store context and explicitly passes the context as a parameter where needed. + +The following are the main API changes, typically to accept an `ExecutionContext` as a parameter + +* `com.networknt.schema.JsonSchema` +* `com.networknt.schema.JsonValidator` +* `com.networknt.schema.Format` +* `com.networknt.schema.walk.JsonSchemaWalker` +* `com.networknt.schema.walk.WalkEvent` + +`JsonSchema` was modified to optionally accept an `ExecutionContext` for the `validate`, `validateAndCollect` and `walk` methods. For methods where no `ExecutionContext` is supplied, one is created for each run in the `createExecutionContext` method in `JsonSchema`. + +`ValidationResult` was modified to store the `ExecutionContext` of the run which is also a means of reusing the context, by passing this context information from the `ValidationResult` to following runs. + +### 1.0.82 + +Up to version [1.0.81](https://github.com/networknt/json-schema-validator/blob/1.0.81/pom.xml#L99), the dependency `org.apache.commons:commons-lang3` was included as a runtime dependency. Starting with [1.0.82](https://github.com/networknt/json-schema-validator/releases/tag/1.0.82) it is not required anymore. \ No newline at end of file diff --git a/doc/validators.md b/doc/validators.md index 0173c321c..e607ee252 100644 --- a/doc/validators.md +++ b/doc/validators.md @@ -11,10 +11,10 @@ The `if`, `then` and `else` keywords allow the application of a subschema based If `if` is valid, `then` must also be valid (and `else` is ignored.) If `if` is invalid, `else` must also be valid (and `then` is ignored). -For usage, please refer to the test cases at https://github.com/networknt/json-schema-validator/blob/master/src/test/resources/draft7/if-then-else.json +For usage, please refer to the test cases at https://github.com/networknt/json-schema-validator/blob/master/src/test/suite/tests/draft7/if-then-else.json ### Custom Validators -```` +````java @Bean public JsonSchemaFactory mySchemaFactory() { // base on JsonMetaSchema.V201909 copy code below @@ -24,10 +24,10 @@ public JsonSchemaFactory mySchemaFactory() { JsonMetaSchema myJsonMetaSchema = new JsonMetaSchema.Builder(URI) .idKeyword(ID) - .addFormats(BUILTIN_FORMATS) - .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V201909)) + .formats(BUILTIN_FORMATS) + .keywords(ValidatorTypeCode.getFormatKeywords(SpecVersion.VersionFlag.V201909)) // keywords that may validly exist, but have no validation aspect to them - .addKeywords(Arrays.asList( + .keywords(Arrays.asList( new NonValidationKeyword("$schema"), new NonValidationKeyword("$id"), new NonValidationKeyword("title"), @@ -37,11 +37,11 @@ public JsonSchemaFactory mySchemaFactory() { new NonValidationKeyword("$defs") // newly added in 2018-09 release. )) // add your custom keyword - .addKeyword(new GroovyKeyword()) + .keyword(new GroovyKeyword()) .build(); - return new JsonSchemaFactory.Builder().defaultMetaSchemaURI(myJsonMetaSchema.getUri()) - .addMetaSchema(myJsonMetaSchema) + return new JsonSchemaFactory.Builder().defaultMetaSchemaIri(myJsonMetaSchema.getIri()) + .metaSchema(myJsonMetaSchema) .build(); } @@ -69,7 +69,7 @@ public class GroovyKeyword extends AbstractKeyword { } ```` You can use GroovyKeyword like below: -```` +````json { "type": "object", "properties": { @@ -87,7 +87,7 @@ In this library, if the format keyword is "email", "uuid", "date", "date-time", If you want to override this behavior, do as below. -``` +```java public JsonSchemaFactory mySchemaFactory() { // base on JsonMetaSchema.V201909 copy code below String URI = "https://json-schema.org/draft/2019-09/schema"; @@ -96,11 +96,11 @@ public JsonSchemaFactory mySchemaFactory() { JsonMetaSchema overrideEmailValidatorMetaSchema = new JsonMetaSchema.Builder(URI) .idKeyword(ID) // Override EmailValidator - .addFormat(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$")) + .format(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$")) .build(); - return new JsonSchemaFactory.Builder().defaultMetaSchemaURI(overrideEmailValidatorMetaSchema.getUri()) - .addMetaSchema(overrideEmailValidatorMetaSchema) + return new JsonSchemaFactory.Builder().defaultMetaSchemaIri(overrideEmailValidatorMetaSchema.getIri()) + .metaSchema(overrideEmailValidatorMetaSchema) .build(); } -``` \ No newline at end of file +``` diff --git a/doc/walkers.md b/doc/walkers.md index 99e376679..8cbc6c2f6 100644 --- a/doc/walkers.md +++ b/doc/walkers.md @@ -17,18 +17,20 @@ public interface JsonSchemaWalker { * cutting concerns like logging or instrumentation. This method also performs * the validation if {@code shouldValidateSchema} is set to true.
*
- * {@link BaseJsonValidator#walk(JsonNode, JsonNode, String, boolean)} provides - * a default implementation of this method. However keywords that parse + * {@link BaseJsonValidator#walk(ExecutionContext, JsonNode, JsonNode, JsonNodePath, boolean)} provides + * a default implementation of this method. However validators that parse * sub-schemas should override this method to call walk method on those - * subschemas. + * sub-schemas. * + * @param executionContext ExecutionContext * @param node JsonNode * @param rootNode JsonNode - * @param at String + * @param instanceLocation JsonNodePath * @param shouldValidateSchema boolean * @return a set of validation messages if shouldValidateSchema is true. */ - Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema); + Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation, boolean shouldValidateSchema); } ``` @@ -36,64 +38,61 @@ public interface JsonSchemaWalker { The JSONValidator interface extends this new interface thus allowing all the validator's defined in library to implement this new interface. BaseJsonValidator class provides a default implementation of the walk method. In this case the walk method does nothing but validating based on shouldValidateSchema parameter. ```java -/** + /** * This is default implementation of walk method. Its job is to call the * validate method if shouldValidateSchema is enabled. */ @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages = validate(node, rootNode, at); - } - return validationMessages; + default Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation, boolean shouldValidateSchema) { + return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation) + : Collections.emptySet(); } - ``` A new walk method added to the JSONSchema class allows us to walk through the JSONSchema. ```java - public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { - // Create the collector context object. - CollectorContext collectorContext = new CollectorContext(); - // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); - Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); - // Load all the data from collectors into the context. - collectorContext.loadCollectors(); - // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, collectorContext); - return validationResult; + public ValidationResult walk(JsonNode node, boolean validate) { + return walk(createExecutionContext(), node, validate); } - + @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); + public Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation, boolean shouldValidateSchema) { + Set errors = new LinkedHashSet<>(); // Walk through all the JSONWalker's. - for (Entry entry : validators.entrySet()) { - JsonWalker jsonWalker = entry.getValue(); - String schemaPathWithKeyword = entry.getKey(); + for (JsonValidator validator : getValidators()) { + JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath(); try { - // Call all the pre-walk listeners. If all the pre-walk listeners return true - // then continue to walk method. - if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema)) { - validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); + // Call all the pre-walk listeners. If at least one of the pre walk listeners + // returns SKIP, then skip the walk. + if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, + evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, + this, validator)) { + Set results = null; + try { + results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + if (results != null && !results.isEmpty()) { + errors.addAll(results); + } + } } } finally { // Call all the post-walk listeners. - keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema, validationMessages); + this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, + evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, + this, validator, errors); } } - return validationMessages; + return errors; } ``` Following code snippet shows how to call the walk method on a JsonSchema instance. -``` -ValidationResult result = jsonSchema.walk(data,false); +```java +ValidationResult result = jsonSchema.walk(data, false); ``` @@ -119,7 +118,7 @@ private static class PropertiesKeywordListener implements JsonSchemaWalkListener @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); if (schemaNode.get("title").textValue().equals("Property3")) { return WalkFlow.SKIP; } @@ -137,15 +136,15 @@ If the onWalkStart method returns WalkFlow.SKIP, the actual walk method executio Walk listeners can be added by using the SchemaValidatorsConfig class. ```java -SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), +SchemaValidatorsConfig.Builder schemaValidatorsConfig = SchemaValidatorsConfig.builder(); + schemaValidatorsConfig.keywordWalkListener(new AllKeywordListener()); + schemaValidatorsConfig.keywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); + schemaValidatorsConfig.keywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new PropertiesKeywordListener()); final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema) .build(); -this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); +this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig.build()); ``` @@ -154,12 +153,12 @@ There are two kinds of walk listeners, keyword walk listeners and property walk Both property walk listeners and keyword walk listener can be modeled by using the same WalkListener interface. Following is an example of how to add a property walk listener. ```java -SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); -schemaValidatorsConfig.addPropertyWalkListener(new ExamplePropertyWalkListener()); +SchemaValidatorsConfig.Builder schemaValidatorsConfig = SchemaValidatorsConfig.builder(); +schemaValidatorsConfig.propertyWalkListener(new ExamplePropertyWalkListener()); final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema) .build(); -this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); +this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig.build()); ``` @@ -173,15 +172,15 @@ Following snippet shows the details captured by WalkEvent instance. ```java public class WalkEvent { - - private String schemaPath; - private JsonNode schemaNode; - private JsonSchema parentSchema; - private String keyWordName; - private JsonNode node; + private ExecutionContext executionContext; + private JsonSchema schema; + private String keyword; private JsonNode rootNode; - private String at; - + private JsonNode instanceNode; + private JsonNodePath instanceLocation; + private JsonValidator validator; + ... +} ``` ### Sample Flow @@ -192,7 +191,7 @@ Given an example schema as shown, if we write a property listener, the walk flow { "title": "Sample Schema", - "definitions" : { + "definitions" : { "address" :{ "street-address": { "title": "Street Address", @@ -279,9 +278,9 @@ But if we apply defaults while walking, then required validation passes, and the ```java JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)); - JsonSchema jsonSchema = schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema.json"), schemaValidatorsConfig); + SchemaValidatorsConfig.Builder schemaValidatorsConfig = SchemaValidatorsConfig.builder(); + schemaValidatorsConfig.applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)); + JsonSchema jsonSchema = schemaFactory.getSchema(SchemaLocation.of("classpath:schema.json"), schemaValidatorsConfig.build()); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json")); ValidationResult result = jsonSchema.walk(inputNode, true); diff --git a/doc/yaml-line-numbers.md b/doc/yaml-line-numbers.md index 0fa48d399..7cb8b8959 100644 --- a/doc/yaml-line-numbers.md +++ b/doc/yaml-line-numbers.md @@ -8,7 +8,7 @@ A great feature of json-schema-validator is it's ability to validate YAML docume One solution is to use a custom [JsonNodeFactory](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/node/JsonNodeFactory.html) that returns custom JsonNode objects which are created during initial parsing, and which record the original YAML locations that were being parsed at the time they were created. The example below shows this -``` +```java public static class MyNodeFactory extends JsonNodeFactory { YAMLParser yp; @@ -69,7 +69,7 @@ We can be as simple or fancy as we like in the `JsonNode` subclassses, but basic Those could be the same thing of course, but in our case we separated them as shown in the following example -``` +```java public interface LocationProvider { LocationDetails getLocationDetails(); @@ -193,7 +193,7 @@ Those could be the same thing of course, but in our case we separated them as sh With the pieces we now have, we just need to tell the YAML library to make of use them, which involves a minor and simple modification to the normal sequence of processing. -``` +```java this.yamlFactory = new YAMLFactory(); try (YAMLParser yp = yamlFactory.createParser(f);) @@ -235,7 +235,7 @@ Some notes on what is happening here: Having got everything prepared, actually getting the line locations is rather easy -``` +```java void processJsonItems(JsonNode item) { Iterator> iter = item.fields(); @@ -266,18 +266,23 @@ There is still a problem though, what if the validation against the schema fails Any failures validation against the schema come back in the form of a set of `ValidationMessage` objects. But these also do not contain original YAML source line information, and there's no easy way to inject it as we did for Scenario 1. Luckily though, there is a trick we can use here! -Within the `ValidationMessage` object is something called the 'path' of the error, which we can access with the `getPath()` method. The syntax of this path is not exactly the same as a regular [JsonPointer](https://fasterxml.github.io/jackson-core/javadoc/2.10/com/fasterxml/jackson/core/JsonPointer.html) object, but it is sufficiently close as to be convertible. And, once converted, we can use that pointer for locating the appropriate `JsonNode`. The following couple of methods can be used to automate this process +Within the `ValidationMessage` object is something called the 'path' of the error, which we can access with the `getPath()` method. The syntax of this path by default is close to being [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/), but can be set explicitly to be +either [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) or [JSONPointer](https://www.rfc-editor.org/rfc/rfc6901.html) expressions. In our case as we already use [Jackson](https://github.com/FasterXML/jackson) which supports node lookups based on JSONPointer expressions, +we will set the path expressions to be JSONPointers. This is achieved by configuring the reported path type through the `SchemaValidatorsConfig` before we read our schema: +```java + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build(); + JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config); ``` + +Having set paths to be JSONPointer expressions we can use those pointers for locating the appropriate `JsonNode` instances. The following couple of methods illustrate this process: + +```java JsonNode findJsonNode(ValidationMessage msg, JsonNode rootNode) { - // munge the ValidationMessage path - String pathStr = StringUtils.replace(msg.getPath(), "$.", "/", 1); - pathStr = StringUtils.replace(pathStr, ".", "/"); - pathStr = StringUtils.replace(pathStr, "[", "/"); - pathStr = StringUtils.replace(pathStr, "]", ""); // array closure superfluous - JsonPointer pathPtr = JsonPointer.valueOf(pathStr); - // Now see if we can find the node + // Construct the JSONPointer. + JsonPointer pathPtr = JsonPointer.valueOf(msg.getPath()); + // Now see if we can find the node. JsonNode node = rootNode.at(pathPtr); return node; } diff --git a/doc/yaml.md b/doc/yaml.md index 818acbcea..060b9bc8d 100644 --- a/doc/yaml.md +++ b/doc/yaml.md @@ -1,25 +1,44 @@ -One of the features of this library is to validate the YAML file in addition to the JSON. In fact, the main use case for this library is to be part of the light-4j framework to validate the request/response at runtime against the OpenAPI specification file openapi.yaml. If you are not using light-4j, you need to load the YAML with https://github.com/FasterXML/jackson-dataformats-text first, and then everything is the same as JSON. +One of the features of this library is to validate the YAML file in addition to the JSON. In fact, the main use case for this library is to be part of the light-4j framework to validate the request/response at runtime against the OpenAPI specification file openapi.yaml. ### Usage -Add the dependency +By default including the library would also include the `jackson-dataformat-yaml` unless explicitly excluded. -``` +```xml com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.10.1 + 2.17.1 ``` -and create object mapper using yaml factory i.e `ObjectMapper objMapper =new ObjectMapper(new YAMLFactory());` +By default the object mapper from `YamlMapperFactory.getInstance()` will be used but this can configured by using a `JsonNodeReader`. -#### Example +```java +ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); +JsonNodeReader jsonNodeReader = JsonNodeReader.builder().yamlMapper(yamlMapper).build(); ``` -JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build(); /* Using draft-07. You can choose anyother draft.*/ -JsonSchema schema = factory.getSchema(YamlOperations.class.getClassLoader().getResourceAsStream("your-schema.json")); -JsonNode jsonNode = mapper.readTree(YamlOperations.class.getClassLoader().getResourceAsStream("your-file.yaml")); -Set validateMsg = schema.validate(jsonNode); +#### Example +```java +String schemaData = "---\r\n" + + "\"$id\": 'https://schema/myschema'\r\n" + + "properties:\r\n" + + " startDate:\r\n" + + " format: 'date'\r\n" + + " minLength: 6\r\n" + + ""; +String inputData = "---\r\n" + + "startDate: '1'\r\n" + + ""; +ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); +JsonNodeReader jsonNodeReader = JsonNodeReader.builder().yamlMapper(yamlMapper).build(); +JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, + builder -> builder.jsonNodeReader(jsonNodeReader).build()); +SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build(); +JsonSchema schema = factory.getSchema(schemaData, InputFormat.YAML, config); +Set messages = schema.validate(inputData, InputFormat.YAML, executionContext -> { + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true); +}); ``` diff --git a/pom.xml b/pom.xml index c0c1386b4..4a74a0e28 100644 --- a/pom.xml +++ b/pom.xml @@ -1,3 +1,4 @@ + - 4.0.0 - com.networknt - json-schema-validator - 1.0.67 - bundle - A json schema validator that supports draft v4, v6, v7 and v2019-09 - https://github.com/networknt/json-schema-validator - JsonSchemaValidator - - - stevehu - Steve Hu - stevehu@gmail.com - - - - github - https://github.com/networknt/json-schema-validator/issues - - - - Apache License Version 2.0 - http://repository.jboss.org/licenses/apache-2.0.txt - repo - - - - scm:git://github.com:networknt/json-schema-validator.git - scm:git://github.com:networknt/json-schema-validator.git - https://github.com:networknt/json-schema-validator.git - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - 1.8 - 1.8 - UTF-8 - 2.12.1 - 1.7.30 - 3.7 - 2.1.41 - 1.2.7 - 5.7.2 - 2.7.21 - 2.2 - 2.2.14.Final - 1.5.1 - - - - com.fasterxml.jackson.core - jackson-databind - ${version.jackson} - - - org.slf4j - slf4j-api - ${version.slf4j} - - - org.apache.commons - commons-lang3 - ${version.common-lang3} - - - org.jruby.joni - joni - true - ${version.joni} - - - com.ethlo.time - itu - ${version.itu} - - - ch.qos.logback - logback-classic - ${version.logback} - test - - - org.junit.jupiter - junit-jupiter-engine - ${version.junit} - test - - - org.junit.jupiter - junit-jupiter-params - ${version.junit} - test - - - org.mockito - mockito-core - ${version.mockito} - test - - - org.hamcrest - hamcrest - ${version.hamcrest} - test - - - io.undertow - undertow-core - ${version.undertow} - test - - - - ${basedir}/src/main/java - ${basedir}/src/test/java - - - false - ${basedir}/src/main/resources - - **/* - - - - - - false - ${basedir}/src/test/resources - - **/* - - - + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + > + 4.0.0 + + com.networknt + json-schema-validator + 2.0.0-SNAPSHOT + bundle + JsonSchemaValidator + A json schema validator that supports draft v4, v6, v7, v2019-09 and v2020-12 + https://github.com/networknt/json-schema-validator + + + + Apache License Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + stevehu + Steve Hu + stevehu@gmail.com + + + + + scm:https://github.com/networknt/json-schema-validator.git + scm:https://github.com/networknt/json-schema-validator.git + https://github.com/networknt/json-schema-validator.git + + + + github + https://github.com/networknt/json-schema-validator/issues + + + + + central + https://central.sonatype.com/service/local/staging/deploy/maven2/ + + + central + https://central.sonatype.com/repository/maven-snapshots/ + + + + + 1.8 + 1.8 + UTF-8 + + 1.14.0 + 2.18.3 + 2.2.6 + 1.3.14 + 2.0.17 + 21.3.10 + 3.0 + 5.11.3 + 0.8.12 + 3.13.0 + 5.1.9 + 3.6.3 + 3.2.1 + 1.3.0 + 3.5.1 + 1.2.2.Final + 1.7.0 + 3.2.7 + + 1.37 + -Duser.language=en -Duser.region=GB + + + + + + ch.qos.logback + logback-classic + ${version.logback} + test + + + + + com.ethlo.time + itu + ${version.itu} + + + + com.fasterxml.jackson.core + jackson-databind + ${version.jackson} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${version.jackson} + + + + org.hamcrest + hamcrest + ${version.hamcrest} + test + + + + + + org.jruby.joni + joni + ${version.joni} + true + + + + + + org.graalvm.js + js + ${version.graaljs} + true + + + + + + org.graalvm.sdk + graal-sdk + ${version.graaljs} + true + + + + org.junit.jupiter + junit-jupiter-engine + ${version.junit} + test + + + + org.junit.jupiter + junit-jupiter-params + ${version.junit} + test + + + + org.slf4j + slf4j-api + ${version.slf4j} + + + + org.openjdk.jmh + jmh-core + ${version.jmh} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${version.jmh} + test + + + + + + + false + ${basedir}/src/main/resources + + **/* + + + + + + + false + ${basedir}/src/test/resources + + **/* + + + + ${project.basedir}/src/test/suite + + + + + + + org.apache.felix + maven-bundle-plugin + ${version.maven-bundle-plugin} + true + + + + org.jcodings;resolution:=optional, + org.jcodings.specific;resolution:=optional, + org.joni;resolution:=optional, + org.joni.exception;resolution:=optional, + * + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${version.maven-source-plugin} + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.maven-javadoc-plugin} + + + attach-javadocs + + jar + + + 8 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.maven-compiler-plugin} + + ${java.version} + ${java.version} + ${java.testversion} + ${java.testversion} + + + + test-compile + + testCompile + + process-test-sources + + ${java.testversion} + ${java.testversion} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.maven-surefire-plugin} + + plain + + true + + + true + true + true + + + true + true + + + + + me.fabriciorby + maven-surefire-junit5-tree-reporter + ${version.maven-surefire-junit5-tree-reporter} + + + + + + org.jacoco + jacoco-maven-plugin + ${version.jacoco-maven-plugin} + + + + com/networknt/org/apache/commons/validator/** + + + + + + + pre-unit-test + + prepare-agent + + + + + + post-unit-test + + report + + test + + + + + + + ${basedir}/src/main/java + ${basedir}/src/test/java + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${version.maven-surefire-plugin} + + + + + + + + release-sign-artifacts + + + performRelease + true + + + - - org.apache.felix - maven-bundle-plugin - 4.2.1 - true - - - - org.jcodings;resolution:=optional, - org.jcodings.specific;resolution:=optional, - org.joni;resolution:=optional, - org.joni.exception;resolution:=optional, - * - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - 8 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 + + org.apache.maven.plugins + maven-gpg-plugin + ${version.maven-gpg} + + + sign-artifacts + + sign + + verify + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 + true + + central + true + + + + + + + + java-module + + [9,) + + + + + org.moditect + moditect-maven-plugin + ${version.moditect-maven-plugin} + + + add-module-infos + + add-module-info + + package - ${java.version} - ${java.version} - ${java.testversion} - ${java.testversion} + 9 + true + + + com.networknt.schema + + + !com.networknt.org*; + *; + + + static com.ethlo.time; + static com.fasterxml.jackson.dataformat.yaml; + static org.jruby.jcodings; + static org.jruby.joni; + static org.graalvm.sdk; + *; + + + true + + + + --multi-release=9 + - - - test-compile - process-test-sources - - testCompile - - - ${java.testversion} - ${java.testversion} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - - - org.jacoco - jacoco-maven-plugin - 0.8.6 - - - pre-unit-test - - prepare-agent - - - ${project.build.directory}/coverage-reports/jacoco-ut.exec - - - - post-unit-test - test - - report - - - ${project.build.directory}/coverage-reports/jacoco-ut.exec - ${project.reporting.outputDirectory}/jacoco-ut - - - - - - pre-integration-test - pre-integration-test - - prepare-agent - - - ${project.build.directory}/coverage-reports/jacoco-it.exec - - - - post-integration-test - post-integration-test - - report - - - ${project.build.directory}/coverage-reports/jacoco-it.exec - ${project.reporting.outputDirectory}/jacoco-it - - - - - - + + + - - + + + + benchmark + + true + + + benchmarks - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.19.1 - + + maven-assembly-plugin + 3.7.1 + + + src/test/assembly/assembly.xml + + + + + make-assembly + package + + single + + + + + org.openjdk.jmh.Main + + + false + + + + - - - - dev - - - - org.jacoco - jacoco-maven-plugin - 0.8.6 - - - - merge-results - verify - - merge - - - - - ${project.build.directory}/coverage-reports - - *.exec - - - - ${project.build.directory}/coverage-reports/aggregate.exec - - - - post-merge-report - verify - - report - - - ${project.build.directory}/coverage-reports/aggregate.exec - ${project.reporting.outputDirectory}/jacoco-aggregate - - - - - - - - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - - - + + + + diff --git a/src/main/java/com/networknt/org/apache/commons/validator/routines/DomainValidator.java b/src/main/java/com/networknt/org/apache/commons/validator/routines/DomainValidator.java new file mode 100644 index 000000000..4c9550ba0 --- /dev/null +++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/DomainValidator.java @@ -0,0 +1,2318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.net.IDN; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + *

Domain name validation routines.

+ * + *

+ * This validator provides methods for validating Internet domain names + * and top-level domains. + *

+ * + *

Domain names are evaluated according + * to the standards RFC1034, + * section 3, and RFC1123, + * section 2.1. No accommodation is provided for the specialized needs of + * other applications; if the domain name has been URL-encoded, for example, + * validation will fail even though the equivalent plaintext version of the + * same name would have passed. + *

+ * + *

+ * Validation is also provided for top-level domains (TLDs) as defined and + * maintained by the Internet Assigned Numbers Authority (IANA): + *

+ * + *
    + *
  • {@link #isValidInfrastructureTld} - validates infrastructure TLDs + * (.arpa, etc.)
  • + *
  • {@link #isValidGenericTld} - validates generic TLDs + * (.com, .org, etc.)
  • + *
  • {@link #isValidCountryCodeTld} - validates country code TLDs + * (.us, .uk, .cn, etc.)
  • + *
+ * + *

+ * (NOTE: This class does not provide IP address lookup for domain names or + * methods to ensure that a given domain name matches a specific IP; see + * {@link java.net.InetAddress} for that functionality.) + *

+ * + * @since 1.4 + */ +public class DomainValidator implements Serializable { + + /** + * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])} + * to determine which override array to update / fetch + * @since 1.5.0 + * @since 1.5.1 made public and added read-only array references + */ + public enum ArrayType { + /** Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs */ + GENERIC_PLUS, + /** Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs */ + GENERIC_MINUS, + /** Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country code TLDs */ + COUNTRY_CODE_PLUS, + /** Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country code TLDs */ + COUNTRY_CODE_MINUS, + /** Gets a copy of the generic TLDS table */ + GENERIC_RO, + /** Gets a copy of the country code table */ + COUNTRY_CODE_RO, + /** Gets a copy of the infrastructure table */ + INFRASTRUCTURE_RO, + /** Gets a copy of the local table */ + LOCAL_RO, + /** + * Update (or get a copy of) the LOCAL_TLDS_PLUS table containing additional local TLDs + * @since 1.7 + */ + LOCAL_PLUS, + /** + * Update (or get a copy of) the LOCAL_TLDS_MINUS table containing deleted local TLDs + * @since 1.7 + */ + LOCAL_MINUS + } + + private static class IDNBUGHOLDER { + private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot(); + private static boolean keepsTrailingDot() { + final String input = "a."; // must be a valid name + return input.equals(IDN.toASCII(input)); + } + } + + /** + * Used to specify overrides when creating a new class. + * @since 1.7 + */ + public static class Item { + final ArrayType type; + final String[] values; + + /** + * Constructs a new instance. + * @param type ArrayType, e.g. GENERIC_PLUS, LOCAL_PLUS + * @param values array of TLDs. Will be lower-cased and sorted + */ + public Item(final ArrayType type, final String... values) { + this.type = type; + this.values = values; // no need to copy here + } + } + + // Regular expression strings for hostnames (derived from RFC2396 and RFC 1123) + + private static class LazyHolder { // IODH + + /** + * Singleton instance of this validator, which + * doesn't consider local addresses as valid. + */ + private static final DomainValidator DOMAIN_VALIDATOR = new DomainValidator(false); + + /** + * Singleton instance of this validator, which does + * consider local addresses valid. + */ + private static final DomainValidator DOMAIN_VALIDATOR_WITH_LOCAL = new DomainValidator(true); + + } + + /** Maximum allowable length ({@value}) of a domain name */ + private static final int MAX_DOMAIN_LENGTH = 253; + + private static final String[] EMPTY_STRING_ARRAY = {}; + + private static final long serialVersionUID = -4407125112880174009L; + + // RFC2396: domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // Max 63 characters + private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; + + // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum + // Max 63 characters + private static final String TOP_LABEL_REGEX = "\\p{Alpha}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; + + /** + * The above instances must only be returned via the getInstance() methods. + * This is to ensure that the override data arrays are properly protected. + */ + + // RFC2396 hostname = *( domainlabel "." ) toplabel [ "." ] + // Note that the regex currently requires both a domain label and a top level label, whereas + // the RFC does not. This is because the regex is used to detect if a TLD is present. + // If the match fails, input is checked against DOMAIN_LABEL_REGEX (hostnameRegex) + // RFC1123 sec 2.1 allows hostnames to start with a digit + private static final String DOMAIN_NAME_REGEX = + "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$"; + private static final String UNEXPECTED_ENUM_VALUE = "Unexpected enum value: "; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] INFRASTRUCTURE_TLDS = { + "arpa", // internet infrastructure + }; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] GENERIC_TLDS = { + // Taken from Version 2023011200, Last Updated Thu Jan 12 07:07:01 2023 UTC + "aaa", // aaa American Automobile Association, Inc. + "aarp", // aarp AARP + "abarth", // abarth Fiat Chrysler Automobiles N.V. + "abb", // abb ABB Ltd + "abbott", // abbott Abbott Laboratories, Inc. + "abbvie", // abbvie AbbVie Inc. + "abc", // abc Disney Enterprises, Inc. + "able", // able Able Inc. + "abogado", // abogado Top Level Domain Holdings Limited + "abudhabi", // abudhabi Abu Dhabi Systems and Information Centre + "academy", // academy Half Oaks, LLC + "accenture", // accenture Accenture plc + "accountant", // accountant dot Accountant Limited + "accountants", // accountants Knob Town, LLC + "aco", // aco ACO Severin Ahlmann GmbH & Co. KG +// "active", // active The Active Network, Inc + "actor", // actor United TLD Holdco Ltd. +// "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC) + "ads", // ads Charleston Road Registry Inc. + "adult", // adult ICM Registry AD LLC + "aeg", // aeg Aktiebolaget Electrolux + "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC USA) + "aetna", // aetna Aetna Life Insurance Company +// "afamilycompany", // afamilycompany Johnson Shareholdings, Inc. + "afl", // afl Australian Football League + "africa", // africa ZA Central Registry NPC trading as Registry.Africa + "agakhan", // agakhan Fondation Aga Khan (Aga Khan Foundation) + "agency", // agency Steel Falls, LLC + "aig", // aig American International Group, Inc. +// "aigo", // aigo aigo Digital Technology Co,Ltd. [Not assigned as of Jul 25] + "airbus", // airbus Airbus S.A.S. + "airforce", // airforce United TLD Holdco Ltd. + "airtel", // airtel Bharti Airtel Limited + "akdn", // akdn Fondation Aga Khan (Aga Khan Foundation) + "alfaromeo", // alfaromeo Fiat Chrysler Automobiles N.V. + "alibaba", // alibaba Alibaba Group Holding Limited + "alipay", // alipay Alibaba Group Holding Limited + "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft + "allstate", // allstate Allstate Fire and Casualty Insurance Company + "ally", // ally Ally Financial Inc. + "alsace", // alsace REGION D ALSACE + "alstom", // alstom ALSTOM + "amazon", // amazon Amazon Registry Services, Inc. + "americanexpress", // americanexpress American Express Travel Related Services Company, Inc. + "americanfamily", // americanfamily AmFam, Inc. + "amex", // amex American Express Travel Related Services Company, Inc. + "amfam", // amfam AmFam, Inc. + "amica", // amica Amica Mutual Insurance Company + "amsterdam", // amsterdam Gemeente Amsterdam + "analytics", // analytics Campus IP LLC + "android", // android Charleston Road Registry Inc. + "anquan", // anquan QIHOO 360 TECHNOLOGY CO. LTD. + "anz", // anz Australia and New Zealand Banking Group Limited + "aol", // aol AOL Inc. + "apartments", // apartments June Maple, LLC + "app", // app Charleston Road Registry Inc. + "apple", // apple Apple Inc. + "aquarelle", // aquarelle Aquarelle.com + "arab", // arab League of Arab States + "aramco", // aramco Aramco Services Company + "archi", // archi STARTING DOT LIMITED + "army", // army United TLD Holdco Ltd. + "art", // art UK Creative Ideas Limited + "arte", // arte Association Relative à la Télévision Européenne G.E.I.E. + "asda", // asda Wal-Mart Stores, Inc. + "asia", // asia DotAsia Organisation Ltd. + "associates", // associates Baxter Hill, LLC + "athleta", // athleta The Gap, Inc. + "attorney", // attorney United TLD Holdco, Ltd + "auction", // auction United TLD HoldCo, Ltd. + "audi", // audi AUDI Aktiengesellschaft + "audible", // audible Amazon Registry Services, Inc. + "audio", // audio Uniregistry, Corp. + "auspost", // auspost Australian Postal Corporation + "author", // author Amazon Registry Services, Inc. + "auto", // auto Uniregistry, Corp. + "autos", // autos DERAutos, LLC + "avianca", // avianca Aerovias del Continente Americano S.A. Avianca + "aws", // aws Amazon Registry Services, Inc. + "axa", // axa AXA SA + "azure", // azure Microsoft Corporation + "baby", // baby Johnson & Johnson Services, Inc. + "baidu", // baidu Baidu, Inc. + "banamex", // banamex Citigroup Inc. + "bananarepublic", // bananarepublic The Gap, Inc. + "band", // band United TLD Holdco, Ltd + "bank", // bank fTLD Registry Services, LLC + "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable + "barcelona", // barcelona Municipi de Barcelona + "barclaycard", // barclaycard Barclays Bank PLC + "barclays", // barclays Barclays Bank PLC + "barefoot", // barefoot Gallo Vineyards, Inc. + "bargains", // bargains Half Hallow, LLC + "baseball", // baseball MLB Advanced Media DH, LLC + "basketball", // basketball Fédération Internationale de Basketball (FIBA) + "bauhaus", // bauhaus Werkhaus GmbH + "bayern", // bayern Bayern Connect GmbH + "bbc", // bbc British Broadcasting Corporation + "bbt", // bbt BB&T Corporation + "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A. + "bcg", // bcg The Boston Consulting Group, Inc. + "bcn", // bcn Municipi de Barcelona + "beats", // beats Beats Electronics, LLC + "beauty", // beauty L'Oréal + "beer", // beer Top Level Domain Holdings Limited + "bentley", // bentley Bentley Motors Limited + "berlin", // berlin dotBERLIN GmbH & Co. KG + "best", // best BestTLD Pty Ltd + "bestbuy", // bestbuy BBY Solutions, Inc. + "bet", // bet Afilias plc + "bharti", // bharti Bharti Enterprises (Holding) Private Limited + "bible", // bible American Bible Society + "bid", // bid dot Bid Limited + "bike", // bike Grand Hollow, LLC + "bing", // bing Microsoft Corporation + "bingo", // bingo Sand Cedar, LLC + "bio", // bio STARTING DOT LIMITED + "biz", // biz Neustar, Inc. + "black", // black Afilias Limited + "blackfriday", // blackfriday Uniregistry, Corp. +// "blanco", // blanco BLANCO GmbH + Co KG + "blockbuster", // blockbuster Dish DBS Corporation + "blog", // blog Knock Knock WHOIS There, LLC + "bloomberg", // bloomberg Bloomberg IP Holdings LLC + "blue", // blue Afilias Limited + "bms", // bms Bristol-Myers Squibb Company + "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft +// "bnl", // bnl Banca Nazionale del Lavoro + "bnpparibas", // bnpparibas BNP Paribas + "boats", // boats DERBoats, LLC + "boehringer", // boehringer Boehringer Ingelheim International GmbH + "bofa", // bofa NMS Services, Inc. + "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "bond", // bond Bond University Limited + "boo", // boo Charleston Road Registry Inc. + "book", // book Amazon Registry Services, Inc. + "booking", // booking Booking.com B.V. +// "boots", // boots THE BOOTS COMPANY PLC + "bosch", // bosch Robert Bosch GMBH + "bostik", // bostik Bostik SA + "boston", // boston Boston TLD Management, LLC + "bot", // bot Amazon Registry Services, Inc. + "boutique", // boutique Over Galley, LLC + "box", // box NS1 Limited + "bradesco", // bradesco Banco Bradesco S.A. + "bridgestone", // bridgestone Bridgestone Corporation + "broadway", // broadway Celebrate Broadway, Inc. + "broker", // broker DOTBROKER REGISTRY LTD + "brother", // brother Brother Industries, Ltd. + "brussels", // brussels DNS.be vzw +// "budapest", // budapest Top Level Domain Holdings Limited +// "bugatti", // bugatti Bugatti International SA + "build", // build Plan Bee LLC + "builders", // builders Atomic Madison, LLC + "business", // business Spring Cross, LLC + "buy", // buy Amazon Registry Services, INC + "buzz", // buzz DOTSTRATEGY CO. + "bzh", // bzh Association www.bzh + "cab", // cab Half Sunset, LLC + "cafe", // cafe Pioneer Canyon, LLC + "cal", // cal Charleston Road Registry Inc. + "call", // call Amazon Registry Services, Inc. + "calvinklein", // calvinklein PVH gTLD Holdings LLC + "cam", // cam AC Webconnecting Holding B.V. + "camera", // camera Atomic Maple, LLC + "camp", // camp Delta Dynamite, LLC +// "cancerresearch", // cancerresearch Australian Cancer Research Foundation + "canon", // canon Canon Inc. + "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry + "capital", // capital Delta Mill, LLC + "capitalone", // capitalone Capital One Financial Corporation + "car", // car Cars Registry Limited + "caravan", // caravan Caravan International, Inc. + "cards", // cards Foggy Hollow, LLC + "care", // care Goose Cross, LLC + "career", // career dotCareer LLC + "careers", // careers Wild Corner, LLC + "cars", // cars Uniregistry, Corp. +// "cartier", // cartier Richemont DNS Inc. + "casa", // casa Top Level Domain Holdings Limited + "case", // case CNH Industrial N.V. +// "caseih", // caseih CNH Industrial N.V. + "cash", // cash Delta Lake, LLC + "casino", // casino Binky Sky, LLC + "cat", // cat Fundacio puntCAT + "catering", // catering New Falls. LLC + "catholic", // catholic Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "cba", // cba COMMONWEALTH BANK OF AUSTRALIA + "cbn", // cbn The Christian Broadcasting Network, Inc. + "cbre", // cbre CBRE, Inc. + "cbs", // cbs CBS Domains Inc. +// "ceb", // ceb The Corporate Executive Board Company + "center", // center Tin Mill, LLC + "ceo", // ceo CEOTLD Pty Ltd + "cern", // cern European Organization for Nuclear Research ("CERN") + "cfa", // cfa CFA Institute + "cfd", // cfd DOTCFD REGISTRY LTD + "chanel", // chanel Chanel International B.V. + "channel", // channel Charleston Road Registry Inc. + "charity", // charity Corn Lake, LLC + "chase", // chase JPMorgan Chase & Co. + "chat", // chat Sand Fields, LLC + "cheap", // cheap Sand Cover, LLC + "chintai", // chintai CHINTAI Corporation +// "chloe", // chloe Richemont DNS Inc. (Not assigned) + "christmas", // christmas Uniregistry, Corp. + "chrome", // chrome Charleston Road Registry Inc. +// "chrysler", // chrysler FCA US LLC. + "church", // church Holly Fileds, LLC + "cipriani", // cipriani Hotel Cipriani Srl + "circle", // circle Amazon Registry Services, Inc. + "cisco", // cisco Cisco Technology, Inc. + "citadel", // citadel Citadel Domain LLC + "citi", // citi Citigroup Inc. + "citic", // citic CITIC Group Corporation + "city", // city Snow Sky, LLC + "cityeats", // cityeats Lifestyle Domain Holdings, Inc. + "claims", // claims Black Corner, LLC + "cleaning", // cleaning Fox Shadow, LLC + "click", // click Uniregistry, Corp. + "clinic", // clinic Goose Park, LLC + "clinique", // clinique The Estée Lauder Companies Inc. + "clothing", // clothing Steel Lake, LLC + "cloud", // cloud ARUBA S.p.A. + "club", // club .CLUB DOMAINS, LLC + "clubmed", // clubmed Club Méditerranée S.A. + "coach", // coach Koko Island, LLC + "codes", // codes Puff Willow, LLC + "coffee", // coffee Trixy Cover, LLC + "college", // college XYZ.COM LLC + "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH + "com", // com VeriSign Global Registry Services + "comcast", // comcast Comcast IP Holdings I, LLC + "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA + "community", // community Fox Orchard, LLC + "company", // company Silver Avenue, LLC + "compare", // compare iSelect Ltd + "computer", // computer Pine Mill, LLC + "comsec", // comsec VeriSign, Inc. + "condos", // condos Pine House, LLC + "construction", // construction Fox Dynamite, LLC + "consulting", // consulting United TLD Holdco, LTD. + "contact", // contact Top Level Spectrum, Inc. + "contractors", // contractors Magic Woods, LLC + "cooking", // cooking Top Level Domain Holdings Limited + "cookingchannel", // cookingchannel Lifestyle Domain Holdings, Inc. + "cool", // cool Koko Lake, LLC + "coop", // coop DotCooperation LLC + "corsica", // corsica Collectivité Territoriale de Corse + "country", // country Top Level Domain Holdings Limited + "coupon", // coupon Amazon Registry Services, Inc. + "coupons", // coupons Black Island, LLC + "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD + "cpa", // cpa American Institute of Certified Public Accountants + "credit", // credit Snow Shadow, LLC + "creditcard", // creditcard Binky Frostbite, LLC + "creditunion", // creditunion CUNA Performance Resources, LLC + "cricket", // cricket dot Cricket Limited + "crown", // crown Crown Equipment Corporation + "crs", // crs Federated Co-operatives Limited + "cruise", // cruise Viking River Cruises (Bermuda) Ltd. + "cruises", // cruises Spring Way, LLC +// "csc", // csc Alliance-One Services, Inc. + "cuisinella", // cuisinella SALM S.A.S. + "cymru", // cymru Nominet UK + "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd. + "dabur", // dabur Dabur India Limited + "dad", // dad Charleston Road Registry Inc. + "dance", // dance United TLD Holdco Ltd. + "data", // data Dish DBS Corporation + "date", // date dot Date Limited + "dating", // dating Pine Fest, LLC + "datsun", // datsun NISSAN MOTOR CO., LTD. + "day", // day Charleston Road Registry Inc. + "dclk", // dclk Charleston Road Registry Inc. + "dds", // dds Minds + Machines Group Limited + "deal", // deal Amazon Registry Services, Inc. + "dealer", // dealer Dealer Dot Com, Inc. + "deals", // deals Sand Sunset, LLC + "degree", // degree United TLD Holdco, Ltd + "delivery", // delivery Steel Station, LLC + "dell", // dell Dell Inc. + "deloitte", // deloitte Deloitte Touche Tohmatsu + "delta", // delta Delta Air Lines, Inc. + "democrat", // democrat United TLD Holdco Ltd. + "dental", // dental Tin Birch, LLC + "dentist", // dentist United TLD Holdco, Ltd + "desi", // desi Desi Networks LLC + "design", // design Top Level Design, LLC + "dev", // dev Charleston Road Registry Inc. + "dhl", // dhl Deutsche Post AG + "diamonds", // diamonds John Edge, LLC + "diet", // diet Uniregistry, Corp. + "digital", // digital Dash Park, LLC + "direct", // direct Half Trail, LLC + "directory", // directory Extra Madison, LLC + "discount", // discount Holly Hill, LLC + "discover", // discover Discover Financial Services + "dish", // dish Dish DBS Corporation + "diy", // diy Lifestyle Domain Holdings, Inc. + "dnp", // dnp Dai Nippon Printing Co., Ltd. + "docs", // docs Charleston Road Registry Inc. + "doctor", // doctor Brice Trail, LLC +// "dodge", // dodge FCA US LLC. + "dog", // dog Koko Mill, LLC +// "doha", // doha Communications Regulatory Authority (CRA) + "domains", // domains Sugar Cross, LLC +// "doosan", // doosan Doosan Corporation (retired) + "dot", // dot Dish DBS Corporation + "download", // download dot Support Limited + "drive", // drive Charleston Road Registry Inc. + "dtv", // dtv Dish DBS Corporation + "dubai", // dubai Dubai Smart Government Department +// "duck", // duck Johnson Shareholdings, Inc. + "dunlop", // dunlop The Goodyear Tire & Rubber Company +// "duns", // duns The Dun & Bradstreet Corporation + "dupont", // dupont E. I. du Pont de Nemours and Company + "durban", // durban ZA Central Registry NPC trading as ZA Central Registry + "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG + "dvr", // dvr Hughes Satellite Systems Corporation + "earth", // earth Interlink Co., Ltd. + "eat", // eat Charleston Road Registry Inc. + "eco", // eco Big Room Inc. + "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V. + "edu", // edu EDUCAUSE + "education", // education Brice Way, LLC + "email", // email Spring Madison, LLC + "emerck", // emerck Merck KGaA + "energy", // energy Binky Birch, LLC + "engineer", // engineer United TLD Holdco Ltd. + "engineering", // engineering Romeo Canyon + "enterprises", // enterprises Snow Oaks, LLC +// "epost", // epost Deutsche Post AG + "epson", // epson Seiko Epson Corporation + "equipment", // equipment Corn Station, LLC + "ericsson", // ericsson Telefonaktiebolaget L M Ericsson + "erni", // erni ERNI Group Holding AG + "esq", // esq Charleston Road Registry Inc. + "estate", // estate Trixy Park, LLC + // "esurance", // esurance Esurance Insurance Company (not assigned as at Version 2020062100) + "etisalat", // etisalat Emirates Telecommunic + "eurovision", // eurovision European Broadcasting Union (EBU) + "eus", // eus Puntueus Fundazioa + "events", // events Pioneer Maple, LLC +// "everbank", // everbank EverBank + "exchange", // exchange Spring Falls, LLC + "expert", // expert Magic Pass, LLC + "exposed", // exposed Victor Beach, LLC + "express", // express Sea Sunset, LLC + "extraspace", // extraspace Extra Space Storage LLC + "fage", // fage Fage International S.A. + "fail", // fail Atomic Pipe, LLC + "fairwinds", // fairwinds FairWinds Partners, LLC + "faith", // faith dot Faith Limited + "family", // family United TLD Holdco Ltd. + "fan", // fan Asiamix Digital Ltd + "fans", // fans Asiamix Digital Limited + "farm", // farm Just Maple, LLC + "farmers", // farmers Farmers Insurance Exchange + "fashion", // fashion Top Level Domain Holdings Limited + "fast", // fast Amazon Registry Services, Inc. + "fedex", // fedex Federal Express Corporation + "feedback", // feedback Top Level Spectrum, Inc. + "ferrari", // ferrari Fiat Chrysler Automobiles N.V. + "ferrero", // ferrero Ferrero Trading Lux S.A. + "fiat", // fiat Fiat Chrysler Automobiles N.V. + "fidelity", // fidelity Fidelity Brokerage Services LLC + "fido", // fido Rogers Communications Canada Inc. + "film", // film Motion Picture Domain Registry Pty Ltd + "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "finance", // finance Cotton Cypress, LLC + "financial", // financial Just Cover, LLC + "fire", // fire Amazon Registry Services, Inc. + "firestone", // firestone Bridgestone Corporation + "firmdale", // firmdale Firmdale Holdings Limited + "fish", // fish Fox Woods, LLC + "fishing", // fishing Top Level Domain Holdings Limited + "fit", // fit Minds + Machines Group Limited + "fitness", // fitness Brice Orchard, LLC + "flickr", // flickr Yahoo! Domain Services Inc. + "flights", // flights Fox Station, LLC + "flir", // flir FLIR Systems, Inc. + "florist", // florist Half Cypress, LLC + "flowers", // flowers Uniregistry, Corp. +// "flsmidth", // flsmidth FLSmidth A/S retired 2016-07-22 + "fly", // fly Charleston Road Registry Inc. + "foo", // foo Charleston Road Registry Inc. + "food", // food Lifestyle Domain Holdings, Inc. + "foodnetwork", // foodnetwork Lifestyle Domain Holdings, Inc. + "football", // football Foggy Farms, LLC + "ford", // ford Ford Motor Company + "forex", // forex DOTFOREX REGISTRY LTD + "forsale", // forsale United TLD Holdco, LLC + "forum", // forum Fegistry, LLC + "foundation", // foundation John Dale, LLC + "fox", // fox FOX Registry, LLC + "free", // free Amazon Registry Services, Inc. + "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH + "frl", // frl FRLregistry B.V. + "frogans", // frogans OP3FT + "frontdoor", // frontdoor Lifestyle Domain Holdings, Inc. + "frontier", // frontier Frontier Communications Corporation + "ftr", // ftr Frontier Communications Corporation + "fujitsu", // fujitsu Fujitsu Limited +// "fujixerox", // fujixerox Xerox DNHC LLC + "fun", // fun DotSpace, Inc. + "fund", // fund John Castle, LLC + "furniture", // furniture Lone Fields, LLC + "futbol", // futbol United TLD Holdco, Ltd. + "fyi", // fyi Silver Tigers, LLC + "gal", // gal Asociación puntoGAL + "gallery", // gallery Sugar House, LLC + "gallo", // gallo Gallo Vineyards, Inc. + "gallup", // gallup Gallup, Inc. + "game", // game Uniregistry, Corp. + "games", // games United TLD Holdco Ltd. + "gap", // gap The Gap, Inc. + "garden", // garden Top Level Domain Holdings Limited + "gay", // gay Top Level Design, LLC + "gbiz", // gbiz Charleston Road Registry Inc. + "gdn", // gdn Joint Stock Company "Navigation-information systems" + "gea", // gea GEA Group Aktiengesellschaft + "gent", // gent COMBELL GROUP NV/SA + "genting", // genting Resorts World Inc. Pte. Ltd. + "george", // george Wal-Mart Stores, Inc. + "ggee", // ggee GMO Internet, Inc. + "gift", // gift Uniregistry, Corp. + "gifts", // gifts Goose Sky, LLC + "gives", // gives United TLD Holdco Ltd. + "giving", // giving Giving Limited +// "glade", // glade Johnson Shareholdings, Inc. + "glass", // glass Black Cover, LLC + "gle", // gle Charleston Road Registry Inc. + "global", // global Dot Global Domain Registry Limited + "globo", // globo Globo Comunicação e Participações S.A + "gmail", // gmail Charleston Road Registry Inc. + "gmbh", // gmbh Extra Dynamite, LLC + "gmo", // gmo GMO Internet, Inc. + "gmx", // gmx 1&1 Mail & Media GmbH + "godaddy", // godaddy Go Daddy East, LLC + "gold", // gold June Edge, LLC + "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD. + "golf", // golf Lone Falls, LLC + "goo", // goo NTT Resonant Inc. +// "goodhands", // goodhands Allstate Fire and Casualty Insurance Company + "goodyear", // goodyear The Goodyear Tire & Rubber Company + "goog", // goog Charleston Road Registry Inc. + "google", // google Charleston Road Registry Inc. + "gop", // gop Republican State Leadership Committee, Inc. + "got", // got Amazon Registry Services, Inc. + "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain Registration) + "grainger", // grainger Grainger Registry Services, LLC + "graphics", // graphics Over Madison, LLC + "gratis", // gratis Pioneer Tigers, LLC + "green", // green Afilias Limited + "gripe", // gripe Corn Sunset, LLC + "grocery", // grocery Wal-Mart Stores, Inc. + "group", // group Romeo Town, LLC + "guardian", // guardian The Guardian Life Insurance Company of America + "gucci", // gucci Guccio Gucci S.p.a. + "guge", // guge Charleston Road Registry Inc. + "guide", // guide Snow Moon, LLC + "guitars", // guitars Uniregistry, Corp. + "guru", // guru Pioneer Cypress, LLC + "hair", // hair L'Oreal + "hamburg", // hamburg Hamburg Top-Level-Domain GmbH + "hangout", // hangout Charleston Road Registry Inc. + "haus", // haus United TLD Holdco, LTD. + "hbo", // hbo HBO Registry Services, Inc. + "hdfc", // hdfc HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED + "hdfcbank", // hdfcbank HDFC Bank Limited + "health", // health DotHealth, LLC + "healthcare", // healthcare Silver Glen, LLC + "help", // help Uniregistry, Corp. + "helsinki", // helsinki City of Helsinki + "here", // here Charleston Road Registry Inc. + "hermes", // hermes Hermes International + "hgtv", // hgtv Lifestyle Domain Holdings, Inc. + "hiphop", // hiphop Uniregistry, Corp. + "hisamitsu", // hisamitsu Hisamitsu Pharmaceutical Co.,Inc. + "hitachi", // hitachi Hitachi, Ltd. + "hiv", // hiv dotHIV gemeinnuetziger e.V. + "hkt", // hkt PCCW-HKT DataCom Services Limited + "hockey", // hockey Half Willow, LLC + "holdings", // holdings John Madison, LLC + "holiday", // holiday Goose Woods, LLC + "homedepot", // homedepot Homer TLC, Inc. + "homegoods", // homegoods The TJX Companies, Inc. + "homes", // homes DERHomes, LLC + "homesense", // homesense The TJX Companies, Inc. + "honda", // honda Honda Motor Co., Ltd. +// "honeywell", // honeywell Honeywell GTLD LLC + "horse", // horse Top Level Domain Holdings Limited + "hospital", // hospital Ruby Pike, LLC + "host", // host DotHost Inc. + "hosting", // hosting Uniregistry, Corp. + "hot", // hot Amazon Registry Services, Inc. + "hoteles", // hoteles Travel Reservations SRL + "hotels", // hotels Booking.com B.V. + "hotmail", // hotmail Microsoft Corporation + "house", // house Sugar Park, LLC + "how", // how Charleston Road Registry Inc. + "hsbc", // hsbc HSBC Holdings PLC +// "htc", // htc HTC corporation (Not assigned) + "hughes", // hughes Hughes Satellite Systems Corporation + "hyatt", // hyatt Hyatt GTLD, L.L.C. + "hyundai", // hyundai Hyundai Motor Company + "ibm", // ibm International Business Machines Corporation + "icbc", // icbc Industrial and Commercial Bank of China Limited + "ice", // ice IntercontinentalExchange, Inc. + "icu", // icu One.com A/S + "ieee", // ieee IEEE Global LLC + "ifm", // ifm ifm electronic gmbh +// "iinet", // iinet Connect West Pty. Ltd. (Retired) + "ikano", // ikano Ikano S.A. + "imamat", // imamat Fondation Aga Khan (Aga Khan Foundation) + "imdb", // imdb Amazon Registry Services, Inc. + "immo", // immo Auburn Bloom, LLC + "immobilien", // immobilien United TLD Holdco Ltd. + "inc", // inc Intercap Holdings Inc. + "industries", // industries Outer House, LLC + "infiniti", // infiniti NISSAN MOTOR CO., LTD. + "info", // info Afilias Limited + "ing", // ing Charleston Road Registry Inc. + "ink", // ink Top Level Design, LLC + "institute", // institute Outer Maple, LLC + "insurance", // insurance fTLD Registry Services LLC + "insure", // insure Pioneer Willow, LLC + "int", // int Internet Assigned Numbers Authority +// "intel", // intel Intel Corporation + "international", // international Wild Way, LLC + "intuit", // intuit Intuit Administrative Services, Inc. + "investments", // investments Holly Glen, LLC + "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A. + "irish", // irish Dot-Irish LLC +// "iselect", // iselect iSelect Ltd + "ismaili", // ismaili Fondation Aga Khan (Aga Khan Foundation) + "ist", // ist Istanbul Metropolitan Municipality + "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S. + "itau", // itau Itau Unibanco Holding S.A. + "itv", // itv ITV Services Limited +// "iveco", // iveco CNH Industrial N.V. +// "iwc", // iwc Richemont DNS Inc. + "jaguar", // jaguar Jaguar Land Rover Ltd + "java", // java Oracle Corporation + "jcb", // jcb JCB Co., Ltd. +// "jcp", // jcp JCP Media, Inc. + "jeep", // jeep FCA US LLC. + "jetzt", // jetzt New TLD Company AB + "jewelry", // jewelry Wild Bloom, LLC + "jio", // jio Affinity Names, Inc. +// "jlc", // jlc Richemont DNS Inc. + "jll", // jll Jones Lang LaSalle Incorporated + "jmp", // jmp Matrix IP LLC + "jnj", // jnj Johnson & Johnson Services, Inc. + "jobs", // jobs Employ Media LLC + "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry + "jot", // jot Amazon Registry Services, Inc. + "joy", // joy Amazon Registry Services, Inc. + "jpmorgan", // jpmorgan JPMorgan Chase & Co. + "jprs", // jprs Japan Registry Services Co., Ltd. + "juegos", // juegos Uniregistry, Corp. + "juniper", // juniper JUNIPER NETWORKS, INC. + "kaufen", // kaufen United TLD Holdco Ltd. + "kddi", // kddi KDDI CORPORATION + "kerryhotels", // kerryhotels Kerry Trading Co. Limited + "kerrylogistics", // kerrylogistics Kerry Trading Co. Limited + "kerryproperties", // kerryproperties Kerry Trading Co. Limited + "kfh", // kfh Kuwait Finance House + "kia", // kia KIA MOTORS CORPORATION + "kids", // kids DotKids Foundation Limited + "kim", // kim Afilias Limited + "kinder", // kinder Ferrero Trading Lux S.A. + "kindle", // kindle Amazon Registry Services, Inc. + "kitchen", // kitchen Just Goodbye, LLC + "kiwi", // kiwi DOT KIWI LIMITED + "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH + "komatsu", // komatsu Komatsu Ltd. + "kosher", // kosher Kosher Marketing Assets LLC + "kpmg", // kpmg KPMG International Cooperative (KPMG International Genossenschaft) + "kpn", // kpn Koninklijke KPN N.V. + "krd", // krd KRG Department of Information Technology + "kred", // kred KredTLD Pty Ltd + "kuokgroup", // kuokgroup Kerry Trading Co. Limited + "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen + "lacaixa", // lacaixa CAIXA D'ESTALVIS I PENSIONS DE BARCELONA +// "ladbrokes", // ladbrokes LADBROKES INTERNATIONAL PLC + "lamborghini", // lamborghini Automobili Lamborghini S.p.A. + "lamer", // lamer The Estée Lauder Companies Inc. + "lancaster", // lancaster LANCASTER + "lancia", // lancia Fiat Chrysler Automobiles N.V. +// "lancome", // lancome L'Oréal + "land", // land Pine Moon, LLC + "landrover", // landrover Jaguar Land Rover Ltd + "lanxess", // lanxess LANXESS Corporation + "lasalle", // lasalle Jones Lang LaSalle Incorporated + "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el Comercio Electrónico + "latino", // latino Dish DBS Corporation + "latrobe", // latrobe La Trobe University + "law", // law Minds + Machines Group Limited + "lawyer", // lawyer United TLD Holdco, Ltd + "lds", // lds IRI Domain Management, LLC + "lease", // lease Victor Trail, LLC + "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc + "lefrak", // lefrak LeFrak Organization, Inc. + "legal", // legal Blue Falls, LLC + "lego", // lego LEGO Juris A/S + "lexus", // lexus TOYOTA MOTOR CORPORATION + "lgbt", // lgbt Afilias Limited +// "liaison", // liaison Liaison Technologies, Incorporated + "lidl", // lidl Schwarz Domains und Services GmbH & Co. KG + "life", // life Trixy Oaks, LLC + "lifeinsurance", // lifeinsurance American Council of Life Insurers + "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc. + "lighting", // lighting John McCook, LLC + "like", // like Amazon Registry Services, Inc. + "lilly", // lilly Eli Lilly and Company + "limited", // limited Big Fest, LLC + "limo", // limo Hidden Frostbite, LLC + "lincoln", // lincoln Ford Motor Company + "linde", // linde Linde Aktiengesellschaft + "link", // link Uniregistry, Corp. + "lipsy", // lipsy Lipsy Ltd + "live", // live United TLD Holdco Ltd. + "living", // living Lifestyle Domain Holdings, Inc. +// "lixil", // lixil LIXIL Group Corporation + "llc", // llc Afilias plc + "llp", // llp Dot Registry LLC + "loan", // loan dot Loan Limited + "loans", // loans June Woods, LLC + "locker", // locker Dish DBS Corporation + "locus", // locus Locus Analytics LLC +// "loft", // loft Annco, Inc. + "lol", // lol Uniregistry, Corp. + "london", // london Dot London Domains Limited + "lotte", // lotte Lotte Holdings Co., Ltd. + "lotto", // lotto Afilias Limited + "love", // love Merchant Law Group LLP + "lpl", // lpl LPL Holdings, Inc. + "lplfinancial", // lplfinancial LPL Holdings, Inc. + "ltd", // ltd Over Corner, LLC + "ltda", // ltda InterNetX Corp. + "lundbeck", // lundbeck H. Lundbeck A/S +// "lupin", // lupin LUPIN LIMITED + "luxe", // luxe Top Level Domain Holdings Limited + "luxury", // luxury Luxury Partners LLC + "macys", // macys Macys, Inc. + "madrid", // madrid Comunidad de Madrid + "maif", // maif Mutuelle Assurance Instituteur France (MAIF) + "maison", // maison Victor Frostbite, LLC + "makeup", // makeup L'Oréal + "man", // man MAN SE + "management", // management John Goodbye, LLC + "mango", // mango PUNTO FA S.L. + "map", // map Charleston Road Registry Inc. + "market", // market Unitied TLD Holdco, Ltd + "marketing", // marketing Fern Pass, LLC + "markets", // markets DOTMARKETS REGISTRY LTD + "marriott", // marriott Marriott Worldwide Corporation + "marshalls", // marshalls The TJX Companies, Inc. + "maserati", // maserati Fiat Chrysler Automobiles N.V. + "mattel", // mattel Mattel Sites, Inc. + "mba", // mba Lone Hollow, LLC +// "mcd", // mcd McDonald’s Corporation (Not assigned) +// "mcdonalds", // mcdonalds McDonald’s Corporation (Not assigned) + "mckinsey", // mckinsey McKinsey Holdings, Inc. + "med", // med Medistry LLC + "media", // media Grand Glen, LLC + "meet", // meet Afilias Limited + "melbourne", // melbourne The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation + "meme", // meme Charleston Road Registry Inc. + "memorial", // memorial Dog Beach, LLC + "men", // men Exclusive Registry Limited + "menu", // menu Wedding TLD2, LLC +// "meo", // meo PT Comunicacoes S.A. + "merckmsd", // merckmsd MSD Registry Holdings, Inc. +// "metlife", // metlife MetLife Services and Solutions, LLC + "miami", // miami Top Level Domain Holdings Limited + "microsoft", // microsoft Microsoft Corporation + "mil", // mil DoD Network Information Center + "mini", // mini Bayerische Motoren Werke Aktiengesellschaft + "mint", // mint Intuit Administrative Services, Inc. + "mit", // mit Massachusetts Institute of Technology + "mitsubishi", // mitsubishi Mitsubishi Corporation + "mlb", // mlb MLB Advanced Media DH, LLC + "mls", // mls The Canadian Real Estate Association + "mma", // mma MMA IARD + "mobi", // mobi Afilias Technologies Limited dba dotMobi + "mobile", // mobile Dish DBS Corporation +// "mobily", // mobily GreenTech Consultancy Company W.L.L. + "moda", // moda United TLD Holdco Ltd. + "moe", // moe Interlink Co., Ltd. + "moi", // moi Amazon Registry Services, Inc. + "mom", // mom Uniregistry, Corp. + "monash", // monash Monash University + "money", // money Outer McCook, LLC + "monster", // monster Monster Worldwide, Inc. +// "montblanc", // montblanc Richemont DNS Inc. (Not assigned) +// "mopar", // mopar FCA US LLC. + "mormon", // mormon IRI Domain Management, LLC ("Applicant") + "mortgage", // mortgage United TLD Holdco, Ltd + "moscow", // moscow Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) + "moto", // moto Motorola Trademark Holdings, LLC + "motorcycles", // motorcycles DERMotorcycles, LLC + "mov", // mov Charleston Road Registry Inc. + "movie", // movie New Frostbite, LLC +// "movistar", // movistar Telefónica S.A. + "msd", // msd MSD Registry Holdings, Inc. + "mtn", // mtn MTN Dubai Limited +// "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation (Retired) + "mtr", // mtr MTR Corporation Limited + "museum", // museum Museum Domain Management Association + "music", // music DotMusic Limited + "mutual", // mutual Northwestern Mutual MU TLD Registry, LLC +// "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française (Retired) + "nab", // nab National Australia Bank Limited +// "nadex", // nadex Nadex Domains, Inc + "nagoya", // nagoya GMO Registry, Inc. + "name", // name VeriSign Information Services, Inc. +// "nationwide", // nationwide Nationwide Mutual Insurance Company + "natura", // natura NATURA COSMÉTICOS S.A. + "navy", // navy United TLD Holdco Ltd. + "nba", // nba NBA REGISTRY, LLC + "nec", // nec NEC Corporation + "net", // net VeriSign Global Registry Services + "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA + "netflix", // netflix Netflix, Inc. + "network", // network Trixy Manor, LLC + "neustar", // neustar NeuStar, Inc. + "new", // new Charleston Road Registry Inc. +// "newholland", // newholland CNH Industrial N.V. + "news", // news United TLD Holdco Ltd. + "next", // next Next plc + "nextdirect", // nextdirect Next plc + "nexus", // nexus Charleston Road Registry Inc. + "nfl", // nfl NFL Reg Ops LLC + "ngo", // ngo Public Interest Registry + "nhk", // nhk Japan Broadcasting Corporation (NHK) + "nico", // nico DWANGO Co., Ltd. + "nike", // nike NIKE, Inc. + "nikon", // nikon NIKON CORPORATION + "ninja", // ninja United TLD Holdco Ltd. + "nissan", // nissan NISSAN MOTOR CO., LTD. + "nissay", // nissay Nippon Life Insurance Company + "nokia", // nokia Nokia Corporation + "northwesternmutual", // northwesternmutual Northwestern Mutual Registry, LLC + "norton", // norton Symantec Corporation + "now", // now Amazon Registry Services, Inc. + "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "nowtv", // nowtv Starbucks (HK) Limited + "nra", // nra NRA Holdings Company, INC. + "nrw", // nrw Minds + Machines GmbH + "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION + "nyc", // nyc The City of New York by and through the New York City Department of Information Technology & Telecommunications + "obi", // obi OBI Group Holding SE & Co. KGaA + "observer", // observer Top Level Spectrum, Inc. +// "off", // off Johnson Shareholdings, Inc. + "office", // office Microsoft Corporation + "okinawa", // okinawa BusinessRalliart inc. + "olayan", // olayan Crescent Holding GmbH + "olayangroup", // olayangroup Crescent Holding GmbH + "oldnavy", // oldnavy The Gap, Inc. + "ollo", // ollo Dish DBS Corporation + "omega", // omega The Swatch Group Ltd + "one", // one One.com A/S + "ong", // ong Public Interest Registry + "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland + "online", // online DotOnline Inc. +// "onyourside", // onyourside Nationwide Mutual Insurance Company + "ooo", // ooo INFIBEAM INCORPORATION LIMITED + "open", // open American Express Travel Related Services Company, Inc. + "oracle", // oracle Oracle Corporation + "orange", // orange Orange Brand Services Limited + "org", // org Public Interest Registry (PIR) + "organic", // organic Afilias Limited +// "orientexpress", // orientexpress Orient Express (retired 2017-04-11) + "origins", // origins The Estée Lauder Companies Inc. + "osaka", // osaka Interlink Co., Ltd. + "otsuka", // otsuka Otsuka Holdings Co., Ltd. + "ott", // ott Dish DBS Corporation + "ovh", // ovh OVH SAS + "page", // page Charleston Road Registry Inc. +// "pamperedchef", // pamperedchef The Pampered Chef, Ltd. (Not assigned) + "panasonic", // panasonic Panasonic Corporation +// "panerai", // panerai Richemont DNS Inc. + "paris", // paris City of Paris + "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "partners", // partners Magic Glen, LLC + "parts", // parts Sea Goodbye, LLC + "party", // party Blue Sky Registry Limited + "passagens", // passagens Travel Reservations SRL + "pay", // pay Amazon Registry Services, Inc. + "pccw", // pccw PCCW Enterprises Limited + "pet", // pet Afilias plc + "pfizer", // pfizer Pfizer Inc. + "pharmacy", // pharmacy National Association of Boards of Pharmacy + "phd", // phd Charleston Road Registry Inc. + "philips", // philips Koninklijke Philips N.V. + "phone", // phone Dish DBS Corporation + "photo", // photo Uniregistry, Corp. + "photography", // photography Sugar Glen, LLC + "photos", // photos Sea Corner, LLC + "physio", // physio PhysBiz Pty Ltd +// "piaget", // piaget Richemont DNS Inc. + "pics", // pics Uniregistry, Corp. + "pictet", // pictet Pictet Europe S.A. + "pictures", // pictures Foggy Sky, LLC + "pid", // pid Top Level Spectrum, Inc. + "pin", // pin Amazon Registry Services, Inc. + "ping", // ping Ping Registry Provider, Inc. + "pink", // pink Afilias Limited + "pioneer", // pioneer Pioneer Corporation + "pizza", // pizza Foggy Moon, LLC + "place", // place Snow Galley, LLC + "play", // play Charleston Road Registry Inc. + "playstation", // playstation Sony Computer Entertainment Inc. + "plumbing", // plumbing Spring Tigers, LLC + "plus", // plus Sugar Mill, LLC + "pnc", // pnc PNC Domain Co., LLC + "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG + "poker", // poker Afilias Domains No. 5 Limited + "politie", // politie Politie Nederland + "porn", // porn ICM Registry PN LLC + "post", // post Universal Postal Union + "pramerica", // pramerica Prudential Financial, Inc. + "praxi", // praxi Praxi S.p.A. + "press", // press DotPress Inc. + "prime", // prime Amazon Registry Services, Inc. + "pro", // pro Registry Services Corporation dba RegistryPro + "prod", // prod Charleston Road Registry Inc. + "productions", // productions Magic Birch, LLC + "prof", // prof Charleston Road Registry Inc. + "progressive", // progressive Progressive Casualty Insurance Company + "promo", // promo Afilias plc + "properties", // properties Big Pass, LLC + "property", // property Uniregistry, Corp. + "protection", // protection XYZ.COM LLC + "pru", // pru Prudential Financial, Inc. + "prudential", // prudential Prudential Financial, Inc. + "pub", // pub United TLD Holdco Ltd. + "pwc", // pwc PricewaterhouseCoopers LLP + "qpon", // qpon dotCOOL, Inc. + "quebec", // quebec PointQuébec Inc + "quest", // quest Quest ION Limited +// "qvc", // qvc QVC, Inc. + "racing", // racing Premier Registry Limited + "radio", // radio European Broadcasting Union (EBU) +// "raid", // raid Johnson Shareholdings, Inc. + "read", // read Amazon Registry Services, Inc. + "realestate", // realestate dotRealEstate LLC + "realtor", // realtor Real Estate Domains LLC + "realty", // realty Fegistry, LLC + "recipes", // recipes Grand Island, LLC + "red", // red Afilias Limited + "redstone", // redstone Redstone Haute Couture Co., Ltd. + "redumbrella", // redumbrella Travelers TLD, LLC + "rehab", // rehab United TLD Holdco Ltd. + "reise", // reise Foggy Way, LLC + "reisen", // reisen New Cypress, LLC + "reit", // reit National Association of Real Estate Investment Trusts, Inc. + "reliance", // reliance Reliance Industries Limited + "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd. + "rent", // rent XYZ.COM LLC + "rentals", // rentals Big Hollow,LLC + "repair", // repair Lone Sunset, LLC + "report", // report Binky Glen, LLC + "republican", // republican United TLD Holdco Ltd. + "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable + "restaurant", // restaurant Snow Avenue, LLC + "review", // review dot Review Limited + "reviews", // reviews United TLD Holdco, Ltd. + "rexroth", // rexroth Robert Bosch GMBH + "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland + "richardli", // richardli Pacific Century Asset Management (HK) Limited + "ricoh", // ricoh Ricoh Company, Ltd. + // "rightathome", // rightathome Johnson Shareholdings, Inc. (retired 2020-07-31) + "ril", // ril Reliance Industries Limited + "rio", // rio Empresa Municipal de Informática SA - IPLANRIO + "rip", // rip United TLD Holdco Ltd. +// "rmit", // rmit Royal Melbourne Institute of Technology + "rocher", // rocher Ferrero Trading Lux S.A. + "rocks", // rocks United TLD Holdco, LTD. + "rodeo", // rodeo Top Level Domain Holdings Limited + "rogers", // rogers Rogers Communications Canada Inc. + "room", // room Amazon Registry Services, Inc. + "rsvp", // rsvp Charleston Road Registry Inc. + "rugby", // rugby World Rugby Strategic Developments Limited + "ruhr", // ruhr regiodot GmbH & Co. KG + "run", // run Snow Park, LLC + "rwe", // rwe RWE AG + "ryukyu", // ryukyu BusinessRalliart inc. + "saarland", // saarland dotSaarland GmbH + "safe", // safe Amazon Registry Services, Inc. + "safety", // safety Safety Registry Services, LLC. + "sakura", // sakura SAKURA Internet Inc. + "sale", // sale United TLD Holdco, Ltd + "salon", // salon Outer Orchard, LLC + "samsclub", // samsclub Wal-Mart Stores, Inc. + "samsung", // samsung SAMSUNG SDS CO., LTD + "sandvik", // sandvik Sandvik AB + "sandvikcoromant", // sandvikcoromant Sandvik AB + "sanofi", // sanofi Sanofi + "sap", // sap SAP AG +// "sapo", // sapo PT Comunicacoes S.A. + "sarl", // sarl Delta Orchard, LLC + "sas", // sas Research IP LLC + "save", // save Amazon Registry Services, Inc. + "saxo", // saxo Saxo Bank A/S + "sbi", // sbi STATE BANK OF INDIA + "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION + "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) + "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") + "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG + "schmidt", // schmidt SALM S.A.S. + "scholarships", // scholarships Scholarships.com, LLC + "school", // school Little Galley, LLC + "schule", // schule Outer Moon, LLC + "schwarz", // schwarz Schwarz Domains und Services GmbH & Co. KG + "science", // science dot Science Limited +// "scjohnson", // scjohnson Johnson Shareholdings, Inc. + // "scor", // scor SCOR SE (not assigned as at Version 2020062100) + "scot", // scot Dot Scot Registry Limited + "search", // search Charleston Road Registry Inc. + "seat", // seat SEAT, S.A. (Sociedad Unipersonal) + "secure", // secure Amazon Registry Services, Inc. + "security", // security XYZ.COM LLC + "seek", // seek Seek Limited + "select", // select iSelect Ltd + "sener", // sener Sener Ingeniería y Sistemas, S.A. + "services", // services Fox Castle, LLC +// "ses", // ses SES + "seven", // seven Seven West Media Ltd + "sew", // sew SEW-EURODRIVE GmbH & Co KG + "sex", // sex ICM Registry SX LLC + "sexy", // sexy Uniregistry, Corp. + "sfr", // sfr Societe Francaise du Radiotelephone - SFR + "shangrila", // shangrila Shangri‐La International Hotel Management Limited + "sharp", // sharp Sharp Corporation + "shaw", // shaw Shaw Cablesystems G.P. + "shell", // shell Shell Information Technology International Inc + "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "shiksha", // shiksha Afilias Limited + "shoes", // shoes Binky Galley, LLC + "shop", // shop GMO Registry, Inc. + "shopping", // shopping Over Keep, LLC + "shouji", // shouji QIHOO 360 TECHNOLOGY CO. LTD. + "show", // show Snow Beach, LLC + "showtime", // showtime CBS Domains Inc. +// "shriram", // shriram Shriram Capital Ltd. + "silk", // silk Amazon Registry Services, Inc. + "sina", // sina Sina Corporation + "singles", // singles Fern Madison, LLC + "site", // site DotSite Inc. + "ski", // ski STARTING DOT LIMITED + "skin", // skin L'Oréal + "sky", // sky Sky International AG + "skype", // skype Microsoft Corporation + "sling", // sling Hughes Satellite Systems Corporation + "smart", // smart Smart Communications, Inc. (SMART) + "smile", // smile Amazon Registry Services, Inc. + "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais) + "soccer", // soccer Foggy Shadow, LLC + "social", // social United TLD Holdco Ltd. + "softbank", // softbank SoftBank Group Corp. + "software", // software United TLD Holdco, Ltd + "sohu", // sohu Sohu.com Limited + "solar", // solar Ruby Town, LLC + "solutions", // solutions Silver Cover, LLC + "song", // song Amazon Registry Services, Inc. + "sony", // sony Sony Corporation + "soy", // soy Charleston Road Registry Inc. + "spa", // spa Asia Spa and Wellness Promotion Council Limited + "space", // space DotSpace Inc. +// "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG + "sport", // sport Global Association of International Sports Federations (GAISF) + "spot", // spot Amazon Registry Services, Inc. +// "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD + "srl", // srl InterNetX Corp. +// "srt", // srt FCA US LLC. + "stada", // stada STADA Arzneimittel AG + "staples", // staples Staples, Inc. + "star", // star Star India Private Limited +// "starhub", // starhub StarHub Limited + "statebank", // statebank STATE BANK OF INDIA + "statefarm", // statefarm State Farm Mutual Automobile Insurance Company +// "statoil", // statoil Statoil ASA + "stc", // stc Saudi Telecom Company + "stcgroup", // stcgroup Saudi Telecom Company + "stockholm", // stockholm Stockholms kommun + "storage", // storage Self Storage Company LLC + "store", // store DotStore Inc. + "stream", // stream dot Stream Limited + "studio", // studio United TLD Holdco Ltd. + "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD + "style", // style Binky Moon, LLC + "sucks", // sucks Vox Populi Registry Ltd. + "supplies", // supplies Atomic Fields, LLC + "supply", // supply Half Falls, LLC + "support", // support Grand Orchard, LLC + "surf", // surf Top Level Domain Holdings Limited + "surgery", // surgery Tin Avenue, LLC + "suzuki", // suzuki SUZUKI MOTOR CORPORATION + "swatch", // swatch The Swatch Group Ltd +// "swiftcover", // swiftcover Swiftcover Insurance Services Limited + "swiss", // swiss Swiss Confederation + "sydney", // sydney State of New South Wales, Department of Premier and Cabinet +// "symantec", // symantec Symantec Corporation [Not assigned as of Jul 25] + "systems", // systems Dash Cypress, LLC + "tab", // tab Tabcorp Holdings Limited + "taipei", // taipei Taipei City Government + "talk", // talk Amazon Registry Services, Inc. + "taobao", // taobao Alibaba Group Holding Limited + "target", // target Target Domain Holdings, LLC + "tatamotors", // tatamotors Tata Motors Ltd + "tatar", // tatar LLC "Coordination Center of Regional Domain of Tatarstan Republic" + "tattoo", // tattoo Uniregistry, Corp. + "tax", // tax Storm Orchard, LLC + "taxi", // taxi Pine Falls, LLC + "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "tdk", // tdk TDK Corporation + "team", // team Atomic Lake, LLC + "tech", // tech Dot Tech LLC + "technology", // technology Auburn Falls, LLC + "tel", // tel Telnic Ltd. +// "telecity", // telecity TelecityGroup International Limited +// "telefonica", // telefonica Telefónica S.A. + "temasek", // temasek Temasek Holdings (Private) Limited + "tennis", // tennis Cotton Bloom, LLC + "teva", // teva Teva Pharmaceutical Industries Limited + "thd", // thd Homer TLC, Inc. + "theater", // theater Blue Tigers, LLC + "theatre", // theatre XYZ.COM LLC + "tiaa", // tiaa Teachers Insurance and Annuity Association of America + "tickets", // tickets Accent Media Limited + "tienda", // tienda Victor Manor, LLC + "tiffany", // tiffany Tiffany and Company + "tips", // tips Corn Willow, LLC + "tires", // tires Dog Edge, LLC + "tirol", // tirol punkt Tirol GmbH + "tjmaxx", // tjmaxx The TJX Companies, Inc. + "tjx", // tjx The TJX Companies, Inc. + "tkmaxx", // tkmaxx The TJX Companies, Inc. + "tmall", // tmall Alibaba Group Holding Limited + "today", // today Pearl Woods, LLC + "tokyo", // tokyo GMO Registry, Inc. + "tools", // tools Pioneer North, LLC + "top", // top Jiangsu Bangning Science & Technology Co.,Ltd. + "toray", // toray Toray Industries, Inc. + "toshiba", // toshiba TOSHIBA Corporation + "total", // total Total SA + "tours", // tours Sugar Station, LLC + "town", // town Koko Moon, LLC + "toyota", // toyota TOYOTA MOTOR CORPORATION + "toys", // toys Pioneer Orchard, LLC + "trade", // trade Elite Registry Limited + "trading", // trading DOTTRADING REGISTRY LTD + "training", // training Wild Willow, LLC + "travel", // travel Tralliance Registry Management Company, LLC. + "travelchannel", // travelchannel Lifestyle Domain Holdings, Inc. + "travelers", // travelers Travelers TLD, LLC + "travelersinsurance", // travelersinsurance Travelers TLD, LLC + "trust", // trust Artemis Internet Inc + "trv", // trv Travelers TLD, LLC + "tube", // tube Latin American Telecom LLC + "tui", // tui TUI AG + "tunes", // tunes Amazon Registry Services, Inc. + "tushu", // tushu Amazon Registry Services, Inc. + "tvs", // tvs T V SUNDRAM IYENGAR & SONS PRIVATE LIMITED + "ubank", // ubank National Australia Bank Limited + "ubs", // ubs UBS AG +// "uconnect", // uconnect FCA US LLC. + "unicom", // unicom China United Network Communications Corporation Limited + "university", // university Little Station, LLC + "uno", // uno Dot Latin LLC + "uol", // uol UBN INTERNET LTDA. + "ups", // ups UPS Market Driver, Inc. + "vacations", // vacations Atomic Tigers, LLC + "vana", // vana Lifestyle Domain Holdings, Inc. + "vanguard", // vanguard The Vanguard Group, Inc. + "vegas", // vegas Dot Vegas, Inc. + "ventures", // ventures Binky Lake, LLC + "verisign", // verisign VeriSign, Inc. + "versicherung", // versicherung dotversicherung-registry GmbH + "vet", // vet United TLD Holdco, Ltd + "viajes", // viajes Black Madison, LLC + "video", // video United TLD Holdco, Ltd + "vig", // vig VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe + "viking", // viking Viking River Cruises (Bermuda) Ltd. + "villas", // villas New Sky, LLC + "vin", // vin Holly Shadow, LLC + "vip", // vip Minds + Machines Group Limited + "virgin", // virgin Virgin Enterprises Limited + "visa", // visa Visa Worldwide Pte. Limited + "vision", // vision Koko Station, LLC +// "vista", // vista Vistaprint Limited +// "vistaprint", // vistaprint Vistaprint Limited + "viva", // viva Saudi Telecom Company + "vivo", // vivo Telefonica Brasil S.A. + "vlaanderen", // vlaanderen DNS.be vzw + "vodka", // vodka Top Level Domain Holdings Limited + "volkswagen", // volkswagen Volkswagen Group of America Inc. + "volvo", // volvo Volvo Holding Sverige Aktiebolag + "vote", // vote Monolith Registry LLC + "voting", // voting Valuetainment Corp. + "voto", // voto Monolith Registry LLC + "voyage", // voyage Ruby House, LLC + "vuelos", // vuelos Travel Reservations SRL + "wales", // wales Nominet UK + "walmart", // walmart Wal-Mart Stores, Inc. + "walter", // walter Sandvik AB + "wang", // wang Zodiac Registry Limited + "wanggou", // wanggou Amazon Registry Services, Inc. +// "warman", // warman Weir Group IP Limited + "watch", // watch Sand Shadow, LLC + "watches", // watches Richemont DNS Inc. + "weather", // weather The Weather Channel, LLC + "weatherchannel", // weatherchannel The Weather Channel, LLC + "webcam", // webcam dot Webcam Limited + "weber", // weber Saint-Gobain Weber SA + "website", // website DotWebsite Inc. + "wed", // wed Atgron, Inc. + "wedding", // wedding Top Level Domain Holdings Limited + "weibo", // weibo Sina Corporation + "weir", // weir Weir Group IP Limited + "whoswho", // whoswho Who's Who Registry + "wien", // wien punkt.wien GmbH + "wiki", // wiki Top Level Design, LLC + "williamhill", // williamhill William Hill Organization Limited + "win", // win First Registry Limited + "windows", // windows Microsoft Corporation + "wine", // wine June Station, LLC + "winners", // winners The TJX Companies, Inc. + "wme", // wme William Morris Endeavor Entertainment, LLC + "wolterskluwer", // wolterskluwer Wolters Kluwer N.V. + "woodside", // woodside Woodside Petroleum Limited + "work", // work Top Level Domain Holdings Limited + "works", // works Little Dynamite, LLC + "world", // world Bitter Fields, LLC + "wow", // wow Amazon Registry Services, Inc. + "wtc", // wtc World Trade Centers Association, Inc. + "wtf", // wtf Hidden Way, LLC + "xbox", // xbox Microsoft Corporation + "xerox", // xerox Xerox DNHC LLC + "xfinity", // xfinity Comcast IP Holdings I, LLC + "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. + "xin", // xin Elegant Leader Limited + "xn--11b4c3d", // कॉम VeriSign Sarl + "xn--1ck2e1b", // セール Amazon Registry Services, Inc. + "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--30rr7y", // 慈善 Excellent First Limited + "xn--3bst00m", // 集团 Eagle Horizon Limited + "xn--3ds443g", // 在线 TLD REGISTRY LIMITED +// "xn--3oq18vl8pn36a", // 大众汽车 Volkswagen (China) Investment Co., Ltd. + "xn--3pxu8k", // 点看 VeriSign Sarl + "xn--42c2d9a", // คอม VeriSign Sarl + "xn--45q11c", // 八卦 Zodiac Scorpio Limited + "xn--4gbrim", // موقع Suhub Electronic Establishment + "xn--55qw42g", // 公益 China Organizational Name Administration Center + "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) + "xn--5su34j936bgsg", // 香格里拉 Shangri‐La International Hotel Management Limited + "xn--5tzm5g", // 网站 Global Website TLD Asia Limited + "xn--6frz82g", // 移动 Afilias Limited + "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited + "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) + "xn--80aqecdr1a", // католик Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--80asehdb", // онлайн CORE Association + "xn--80aswg", // сайт CORE Association + "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited + "xn--90ae", // бг Imena.BG Plc (NAMES.BG Plc) + "xn--9dbq2a", // קום VeriSign Sarl + "xn--9et52u", // 时尚 RISE VICTORY LIMITED + "xn--9krt00a", // 微博 Sina Corporation + "xn--9t4b11yi5a", // 테스트 Test + "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited + "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc. + "xn--c1avg", // орг Public Interest Registry + "xn--c2br7g", // नेट VeriSign Sarl + "xn--cck2b3b", // ストア Amazon Registry Services, Inc. + "xn--cckwcxetd", // アマゾン Amazon Registry Services, Inc. + "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD + "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG LIMITED + "xn--czrs0t", // 商店 Wild Island, LLC + "xn--czru2d", // 商城 Zodiac Aquarius Limited + "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet” + "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc. + "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社 +// "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited + "xn--fct429k", // 家電 Amazon Registry Services, Inc. + "xn--fhbei", // كوم VeriSign Sarl + "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED + "xn--fiq64b", // 中信 CITIC Group Corporation + "xn--fjq720a", // 娱乐 Will Bloom, LLC + "xn--flw351e", // 谷歌 Charleston Road Registry Inc. + "xn--fzys8d69uvgm", // 電訊盈科 PCCW Enterprises Limited + "xn--g2xx48c", // 购物 Minds + Machines Group Limited + "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc. + "xn--gk3at1e", // 通販 Amazon Registry Services, Inc. + "xn--hxt814e", // 网店 Zodiac Libra Limited + "xn--i1b6b1a6a2e", // संगठन Public Interest Registry + "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED + "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) + "xn--j1aef", // ком VeriSign Sarl + "xn--jlq480n2rg", // 亚马逊 Amazon Registry Services, Inc. +// "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation + "xn--jvr189m", // 食品 Amazon Registry Services, Inc. + "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V. +// "xn--kpu716f", // 手表 Richemont DNS Inc. [Not assigned as of Jul 25] + "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd + "xn--mgba3a3ejt", // ارامكو Aramco Services Company + "xn--mgba7c0bbn0a", // العليان Crescent Holding GmbH + "xn--mgbaakc7dvf", // اتصالات Emirates Telecommunications Corporation (trading as Etisalat) + "xn--mgbab2bd", // بازار CORE Association +// "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L. + "xn--mgbca7dzdo", // ابوظبي Abu Dhabi Systems and Information Centre + "xn--mgbi4ecexp", // كاثوليك Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "xn--mk1bu44c", // 닷컴 VeriSign Sarl + "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd. + "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd. + "xn--ngbe9e0a", // بيتك Kuwait Finance House + "xn--ngbrx", // عرب League of Arab States + "xn--nqv7f", // 机构 Public Interest Registry + "xn--nqv7fs00ema", // 组织机构 Public Interest Registry + "xn--nyqy26a", // 健康 Stable Tone Limited + "xn--otu796d", // 招聘 Dot Trademark TLD Holding Company Limited + "xn--p1acf", // рус Rusnames Limited +// "xn--pbt977c", // 珠宝 Richemont DNS Inc. [Not assigned as of Jul 25] + "xn--pssy2u", // 大拿 VeriSign Sarl + "xn--q9jyb4c", // みんな Charleston Road Registry Inc. + "xn--qcka1pmc", // グーグル Charleston Road Registry Inc. + "xn--rhqv96g", // 世界 Stable Tone Limited + "xn--rovu88b", // 書籍 Amazon EU S.à r.l. + "xn--ses554g", // 网址 KNET Co., Ltd + "xn--t60b56a", // 닷넷 VeriSign Sarl + "xn--tckwe", // コム VeriSign Sarl + "xn--tiq49xqyj", // 天主教 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) + "xn--unup4y", // 游戏 Spring Fields, LLC + "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung Aktiengesellschaft DVAG + "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung Aktiengesellschaft DVAG + "xn--vhquv", // 企业 Dash McCook, LLC + "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd. + "xn--w4r85el8fhu5dnra", // 嘉里大酒店 Kerry Trading Co. Limited + "xn--w4rs40l", // 嘉里 Kerry Trading Co. Limited + "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--zfr164b", // 政务 China Organizational Name Administration Center +// "xperia", // xperia Sony Mobile Communications AB + "xxx", // xxx ICM Registry LLC + "xyz", // xyz XYZ.COM LLC + "yachts", // yachts DERYachts, LLC + "yahoo", // yahoo Yahoo! Domain Services Inc. + "yamaxun", // yamaxun Amazon Registry Services, Inc. + "yandex", // yandex YANDEX, LLC + "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD. + "yoga", // yoga Top Level Domain Holdings Limited + "yokohama", // yokohama GMO Registry, Inc. + "you", // you Amazon Registry Services, Inc. + "youtube", // youtube Charleston Road Registry Inc. + "yun", // yun QIHOO 360 TECHNOLOGY CO. LTD. + "zappos", // zappos Amazon Registry Services, Inc. + "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.) + "zero", // zero Amazon Registry Services, Inc. + "zip", // zip Charleston Road Registry Inc. +// "zippo", // zippo Zadco Company + "zone", // zone Outer Falls, LLC + "zuerich", // zuerich Kanton Zürich (Canton of Zurich) +}; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] COUNTRY_CODE_TLDS = { + // Taken from Version 2023011200, Last Updated Thu Jan 12 07:07:01 2023 UTC + "ac", // Ascension Island + "ad", // Andorra + "ae", // United Arab Emirates + "af", // Afghanistan + "ag", // Antigua and Barbuda + "ai", // Anguilla + "al", // Albania + "am", // Armenia +// "an", // Netherlands Antilles (retired) + "ao", // Angola + "aq", // Antarctica + "ar", // Argentina + "as", // American Samoa + "at", // Austria + "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) + "aw", // Aruba + "ax", // Åland + "az", // Azerbaijan + "ba", // Bosnia and Herzegovina + "bb", // Barbados + "bd", // Bangladesh + "be", // Belgium + "bf", // Burkina Faso + "bg", // Bulgaria + "bh", // Bahrain + "bi", // Burundi + "bj", // Benin + "bm", // Bermuda + "bn", // Brunei Darussalam + "bo", // Bolivia + "br", // Brazil + "bs", // Bahamas + "bt", // Bhutan + "bv", // Bouvet Island + "bw", // Botswana + "by", // Belarus + "bz", // Belize + "ca", // Canada + "cc", // Cocos (Keeling) Islands + "cd", // Democratic Republic of the Congo (formerly Zaire) + "cf", // Central African Republic + "cg", // Republic of the Congo + "ch", // Switzerland + "ci", // Côte d'Ivoire + "ck", // Cook Islands + "cl", // Chile + "cm", // Cameroon + "cn", // China, mainland + "co", // Colombia + "cr", // Costa Rica + "cu", // Cuba + "cv", // Cape Verde + "cw", // Curaçao + "cx", // Christmas Island + "cy", // Cyprus + "cz", // Czech Republic + "de", // Germany + "dj", // Djibouti + "dk", // Denmark + "dm", // Dominica + "do", // Dominican Republic + "dz", // Algeria + "ec", // Ecuador + "ee", // Estonia + "eg", // Egypt + "er", // Eritrea + "es", // Spain + "et", // Ethiopia + "eu", // European Union + "fi", // Finland + "fj", // Fiji + "fk", // Falkland Islands + "fm", // Federated States of Micronesia + "fo", // Faroe Islands + "fr", // France + "ga", // Gabon + "gb", // Great Britain (United Kingdom) + "gd", // Grenada + "ge", // Georgia + "gf", // French Guiana + "gg", // Guernsey + "gh", // Ghana + "gi", // Gibraltar + "gl", // Greenland + "gm", // The Gambia + "gn", // Guinea + "gp", // Guadeloupe + "gq", // Equatorial Guinea + "gr", // Greece + "gs", // South Georgia and the South Sandwich Islands + "gt", // Guatemala + "gu", // Guam + "gw", // Guinea-Bissau + "gy", // Guyana + "hk", // Hong Kong + "hm", // Heard Island and McDonald Islands + "hn", // Honduras + "hr", // Croatia (Hrvatska) + "ht", // Haiti + "hu", // Hungary + "id", // Indonesia + "ie", // Ireland (Éire) + "il", // Israel + "im", // Isle of Man + "in", // India + "io", // British Indian Ocean Territory + "iq", // Iraq + "ir", // Iran + "is", // Iceland + "it", // Italy + "je", // Jersey + "jm", // Jamaica + "jo", // Jordan + "jp", // Japan + "ke", // Kenya + "kg", // Kyrgyzstan + "kh", // Cambodia (Khmer) + "ki", // Kiribati + "km", // Comoros + "kn", // Saint Kitts and Nevis + "kp", // North Korea + "kr", // South Korea + "kw", // Kuwait + "ky", // Cayman Islands + "kz", // Kazakhstan + "la", // Laos (currently being marketed as the official domain for Los Angeles) + "lb", // Lebanon + "lc", // Saint Lucia + "li", // Liechtenstein + "lk", // Sri Lanka + "lr", // Liberia + "ls", // Lesotho + "lt", // Lithuania + "lu", // Luxembourg + "lv", // Latvia + "ly", // Libya + "ma", // Morocco + "mc", // Monaco + "md", // Moldova + "me", // Montenegro + "mg", // Madagascar + "mh", // Marshall Islands + "mk", // Republic of Macedonia + "ml", // Mali + "mm", // Myanmar + "mn", // Mongolia + "mo", // Macau + "mp", // Northern Mariana Islands + "mq", // Martinique + "mr", // Mauritania + "ms", // Montserrat + "mt", // Malta + "mu", // Mauritius + "mv", // Maldives + "mw", // Malawi + "mx", // Mexico + "my", // Malaysia + "mz", // Mozambique + "na", // Namibia + "nc", // New Caledonia + "ne", // Niger + "nf", // Norfolk Island + "ng", // Nigeria + "ni", // Nicaragua + "nl", // Netherlands + "no", // Norway + "np", // Nepal + "nr", // Nauru + "nu", // Niue + "nz", // New Zealand + "om", // Oman + "pa", // Panama + "pe", // Peru + "pf", // French Polynesia With Clipperton Island + "pg", // Papua New Guinea + "ph", // Philippines + "pk", // Pakistan + "pl", // Poland + "pm", // Saint-Pierre and Miquelon + "pn", // Pitcairn Islands + "pr", // Puerto Rico + "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) + "pt", // Portugal + "pw", // Palau + "py", // Paraguay + "qa", // Qatar + "re", // Réunion + "ro", // Romania + "rs", // Serbia + "ru", // Russia + "rw", // Rwanda + "sa", // Saudi Arabia + "sb", // Solomon Islands + "sc", // Seychelles + "sd", // Sudan + "se", // Sweden + "sg", // Singapore + "sh", // Saint Helena + "si", // Slovenia + "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) + "sk", // Slovakia + "sl", // Sierra Leone + "sm", // San Marino + "sn", // Senegal + "so", // Somalia + "sr", // Suriname + "ss", // ss National Communication Authority (NCA) + "st", // São Tomé and Príncipe + "su", // Soviet Union (deprecated) + "sv", // El Salvador + "sx", // Sint Maarten + "sy", // Syria + "sz", // Swaziland + "tc", // Turks and Caicos Islands + "td", // Chad + "tf", // French Southern and Antarctic Lands + "tg", // Togo + "th", // Thailand + "tj", // Tajikistan + "tk", // Tokelau + "tl", // East Timor (deprecated old code) + "tm", // Turkmenistan + "tn", // Tunisia + "to", // Tonga +// "tp", // East Timor (Retired) + "tr", // Turkey + "tt", // Trinidad and Tobago + "tv", // Tuvalu + "tw", // Taiwan, Republic of China + "tz", // Tanzania + "ua", // Ukraine + "ug", // Uganda + "uk", // United Kingdom + "us", // United States of America + "uy", // Uruguay + "uz", // Uzbekistan + "va", // Vatican City State + "vc", // Saint Vincent and the Grenadines + "ve", // Venezuela + "vg", // British Virgin Islands + "vi", // U.S. Virgin Islands + "vn", // Vietnam + "vu", // Vanuatu + "wf", // Wallis and Futuna + "ws", // Samoa (formerly Western Samoa) + "xn--2scrj9c", // ಭಾರತ National Internet eXchange of India + "xn--3e0b707e", // 한국 KISA (Korea Internet & Security Agency) + "xn--3hcrj9c", // ଭାରତ National Internet eXchange of India + "xn--45br5cyl", // ভাৰত National Internet eXchange of India + "xn--45brj9c", // ভারত National Internet Exchange of India + "xn--4dbrk0ce", // ישראל The Israel Internet Association (RA) + "xn--54b7fta0cc", // বাংলা Posts and Telecommunications Division + "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan + "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS) + "xn--90ais", // ??? Reliable Software Inc. + "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre (SGNIC) Pte Ltd + "xn--d1alf", // мкд Macedonian Academic Research Network Skopje + "xn--e1a4c", // ею EURid vzw/asbl + "xn--fiqs8s", // 中国 China Internet Network Information Center + "xn--fiqz9s", // 中國 China Internet Network Information Center + "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India + "xn--fzc2c9e2c", // ලංකා LK Domain Registry + "xn--gecrj9c", // ભારત National Internet Exchange of India + "xn--h2breg3eve", // भारतम् National Internet eXchange of India + "xn--h2brj9c", // भारत National Internet Exchange of India + "xn--h2brj9c8c", // भारोत National Internet eXchange of India + "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc. + "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd. + "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC) + "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC) + "xn--l1acc", // мон Datacom Co.,Ltd + "xn--lgbbat1ad8j", // الجزائر CERIST + "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA) + "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM) + "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA) + "xn--mgbah1a3hjkrd", // موريتانيا Université de Nouakchott Al Aasriya + "xn--mgbai9azgqp6j", // پاکستان National Telecommunication Corporation + "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC) + "xn--mgbbh1a", // بارت National Internet eXchange of India + "xn--mgbbh1a71e", // بھارت National Internet Exchange of India + "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des Télécommunications (ANRT) + "xn--mgbcpq6gpa1a", // البحرين Telecommunications Regulatory Authority (TRA) + "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology Commission + "xn--mgbgu82a", // ڀارت National Internet eXchange of India + "xn--mgbpl2fh", // ????? Sudan Internet Society + "xn--mgbtx2b", // عراق Communications and Media Commission (CMC) + "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad + "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT) + "xn--node", // გე Information Technologies Development Center (ITDC) + "xn--o3cw4h", // ไทย Thai Network Information Center Foundation + "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS) + "xn--p1ai", // рф Coordination Center for TLD RU + "xn--pgbs0dh", // تونس Agence Tunisienne d'Internet + "xn--q7ce6a", // ລາວ Lao National Internet Center (LANIC) + "xn--qxa6a", // ευ EURid vzw/asbl + "xn--qxam", // ελ ICS-FORTH GR + "xn--rvc1e0am3e", // ഭാരതം National Internet eXchange of India + "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India + "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA + "xn--wgbl6a", // قطر Communications Regulatory Authority + "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry + "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India + "xn--y9a3aq", // ??? Internet Society + "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd + "xn--ygbi2ammx", // فلسطين Ministry of Telecom & Information Technology (MTIT) + "ye", // Yemen + "yt", // Mayotte + "za", // South Africa + "zm", // Zambia + "zw", // Zimbabwe + }; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static final String[] LOCAL_TLDS = { + "localdomain", // Also widely used as localhost.localdomain + "localhost", // RFC2606 defined + }; + /* + * This field is used to detect whether the getInstance has been called. + * After this, the method updateTLDOverride is not allowed to be called. + * This field does not need to be volatile since it is only accessed from + * synchronized methods. + */ + private static boolean inUse; + /* + * These arrays are mutable. + * They can only be updated by the updateTLDOverride method, and readers must first get an instance + * using the getInstance methods which are all (now) synchronized. + * The only other access is via getTLDEntries which is now synchronized. + */ + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] countryCodeTLDsPlus = EMPTY_STRING_ARRAY; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] genericTLDsPlus = EMPTY_STRING_ARRAY; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] countryCodeTLDsMinus = EMPTY_STRING_ARRAY; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] genericTLDsMinus = EMPTY_STRING_ARRAY; + + // N.B. The constructors are deliberately private to avoid possible problems with unsafe publication. + // It is vital that the static override arrays are not mutable once they have been used in an instance + // The arrays could be copied into the instance variables, however if the static array were changed it could + // result in different settings for the shared default instances + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] localTLDsMinus = EMPTY_STRING_ARRAY; + + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + private static String[] localTLDsPlus = EMPTY_STRING_ARRAY; + + /** + * Check if a sorted array contains the specified key + * + * @param sortedArray the array to search + * @param key the key to find + * @return {@code true} if the array contains the key + */ + private static boolean arrayContains(final String[] sortedArray, final String key) { + return Arrays.binarySearch(sortedArray, key) >= 0; + } + + /** + * Returns the singleton instance of this validator. It + * will not consider local addresses as valid. + * @return the singleton instance of this validator + */ + public static synchronized DomainValidator getInstance() { + inUse = true; + return LazyHolder.DOMAIN_VALIDATOR; + } + + /** + * Returns the singleton instance of this validator, + * with local validation as required. + * @param allowLocal Should local addresses be considered valid? + * @return the singleton instance of this validator + */ + public static synchronized DomainValidator getInstance(final boolean allowLocal) { + inUse = true; + if (allowLocal) { + return LazyHolder.DOMAIN_VALIDATOR_WITH_LOCAL; + } + return LazyHolder.DOMAIN_VALIDATOR; + } + + /** + * Returns a new instance of this validator. + * The user can provide a list of {@link Item} entries which can + * be used to override the generic and country code lists. + * Note that any such entries override values provided by the + * {@link #updateTLDOverride(ArrayType, String[])} method + * If an entry for a particular type is not provided, then + * the class override (if any) is retained. + * + * @param allowLocal Should local addresses be considered valid? + * @param items - array of {@link Item} entries + * @return an instance of this validator + * @since 1.7 + */ + public static synchronized DomainValidator getInstance(final boolean allowLocal, final List items) { + inUse = true; + return new DomainValidator(allowLocal, items); + } + + /** + * Gets a copy of a class level internal array. + * @param table the array type (any of the enum values) + * @return a copy of the array + * @throws IllegalArgumentException if the table type is unexpected (should not happen) + * @since 1.5.1 + */ + public static synchronized String[] getTLDEntries(final ArrayType table) { + final String[] array; + switch (table) { + case COUNTRY_CODE_MINUS: + array = countryCodeTLDsMinus; + break; + case COUNTRY_CODE_PLUS: + array = countryCodeTLDsPlus; + break; + case GENERIC_MINUS: + array = genericTLDsMinus; + break; + case GENERIC_PLUS: + array = genericTLDsPlus; + break; + case LOCAL_MINUS: + array = localTLDsMinus; + break; + case LOCAL_PLUS: + array = localTLDsPlus; + break; + case GENERIC_RO: + array = GENERIC_TLDS; + break; + case COUNTRY_CODE_RO: + array = COUNTRY_CODE_TLDS; + break; + case INFRASTRUCTURE_RO: + array = INFRASTRUCTURE_TLDS; + break; + case LOCAL_RO: + array = LOCAL_TLDS; + break; + default: + throw new IllegalArgumentException(UNEXPECTED_ENUM_VALUE + table); + } + return Arrays.copyOf(array, array.length); // clone the array + } + + /* + * Check if input contains only ASCII + * Treats null as all ASCII + */ + private static boolean isOnlyASCII(final String input) { + if (input == null) { + return true; + } + for (int i = 0; i < input.length(); i++) { + if (input.charAt(i) > 0x7F) { // CHECKSTYLE IGNORE MagicNumber + return false; + } + } + return true; + } + + /** + * Converts potentially Unicode input to punycode. + * If conversion fails, returns the original input. + * + * @param input the string to convert, not null + * @return converted input, or original input if conversion fails + */ + // Needed by UrlValidator + static String unicodeToASCII(final String input) { + if (isOnlyASCII(input)) { // skip possibly expensive processing + return input; + } + try { + final String ascii = IDN.toASCII(input); + if (IDNBUGHOLDER.IDN_TOASCII_PRESERVES_TRAILING_DOTS) { + return ascii; + } + final int length = input.length(); + if (length == 0) { // check there is a last character + return input; + } + // RFC3490 3.1. 1) + // Whenever dots are used as label separators, the following + // characters MUST be recognized as dots: U+002E (full stop), U+3002 + // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 + // (halfwidth ideographic full stop). + final char lastChar = input.charAt(length - 1);// fetch original last char + switch (lastChar) { + case '\u002E': // "." full stop + case '\u3002': // ideographic full stop + case '\uFF0E': // fullwidth full stop + case '\uFF61': // halfwidth ideographic full stop + return ascii + "."; // restore the missing stop + default: + return ascii; + } + } catch (final IllegalArgumentException e) { // input is not valid + return input; + } + } + + /** + * Update one of the TLD override arrays. + * This must only be done at program startup, before any instances are accessed using getInstance. + *

+ * For example: + *

+ * {@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, "apache")} + *

+ * To clear an override array, provide an empty array. + * + * @param table the table to update, see {@link DomainValidator.ArrayType} + * Must be one of the following + *

    + *
  • COUNTRY_CODE_MINUS
  • + *
  • COUNTRY_CODE_PLUS
  • + *
  • GENERIC_MINUS
  • + *
  • GENERIC_PLUS
  • + *
  • LOCAL_MINUS
  • + *
  • LOCAL_PLUS
  • + *
+ * @param tlds the array of TLDs, must not be null + * @throws IllegalStateException if the method is called after getInstance + * @throws IllegalArgumentException if one of the read-only tables is requested + * @since 1.5.0 + */ + public static synchronized void updateTLDOverride(final ArrayType table, final String... tlds) { + if (inUse) { + throw new IllegalStateException("Can only invoke this method before calling getInstance"); + } + final String[] copy = new String[tlds.length]; + // Comparisons are always done with lower-case entries + for (int i = 0; i < tlds.length; i++) { + copy[i] = tlds[i].toLowerCase(Locale.ENGLISH); + } + Arrays.sort(copy); + switch (table) { + case COUNTRY_CODE_MINUS: + countryCodeTLDsMinus = copy; + break; + case COUNTRY_CODE_PLUS: + countryCodeTLDsPlus = copy; + break; + case GENERIC_MINUS: + genericTLDsMinus = copy; + break; + case GENERIC_PLUS: + genericTLDsPlus = copy; + break; + case LOCAL_MINUS: + localTLDsMinus = copy; + break; + case LOCAL_PLUS: + localTLDsPlus = copy; + break; + case COUNTRY_CODE_RO: + case GENERIC_RO: + case INFRASTRUCTURE_RO: + case LOCAL_RO: + throw new IllegalArgumentException("Cannot update the table: " + table); + default: + throw new IllegalArgumentException(UNEXPECTED_ENUM_VALUE + table); + } + } + + /** Whether to allow local overrides. */ + private final boolean allowLocal; + + // --------------------------------------------- + // ----- TLDs defined by IANA + // ----- Authoritative and comprehensive list at: + // ----- https://data.iana.org/TLD/tlds-alpha-by-domain.txt + + // Note that the above list is in UPPER case. + // The code currently converts strings to lower case (as per the tables below) + + // IANA also provide an HTML list at http://www.iana.org/domains/root/db + // Note that this contains several country code entries which are NOT in + // the text file. These all have the "Not assigned" in the "Sponsoring Organisation" column + // For example (as of 2015-01-02): + // .bl country-code Not assigned + // .um country-code Not assigned + + /** + * RegexValidator for matching domains. + */ + private final RegexValidator domainRegex = + new RegexValidator(DOMAIN_NAME_REGEX); + + /** + * RegexValidator for matching a local hostname + */ + // RFC1123 sec 2.1 allows hostnames to start with a digit + private final RegexValidator hostnameRegex = + new RegexValidator(DOMAIN_LABEL_REGEX); + + /** Local override. */ + final String[] myCountryCodeTLDsMinus; + + /** Local override. */ + final String[] myCountryCodeTLDsPlus; + + // Additional arrays to supplement or override the built in ones. + // The PLUS arrays are valid keys, the MINUS arrays are invalid keys + + /** Local override. */ + final String[] myGenericTLDsPlus; + + /** Local override. */ + final String[] myGenericTLDsMinus; + + /** Local override. */ + final String[] myLocalTLDsPlus; + + /** Local override. */ + final String[] myLocalTLDsMinus; + + /* + * It is vital that instances are immutable. This is because the default instances are shared. + */ + + /** + * Private constructor. + */ + private DomainValidator(final boolean allowLocal) { + this.allowLocal = allowLocal; + // link to class overrides + myCountryCodeTLDsMinus = countryCodeTLDsMinus; + myCountryCodeTLDsPlus = countryCodeTLDsPlus; + myGenericTLDsPlus = genericTLDsPlus; + myGenericTLDsMinus = genericTLDsMinus; + myLocalTLDsPlus = localTLDsPlus; + myLocalTLDsMinus = localTLDsMinus; + } + + /** + * Private constructor, allowing local overrides + * @since 1.7 + */ + private DomainValidator(final boolean allowLocal, final List items) { + this.allowLocal = allowLocal; + + // default to class overrides + String[] ccMinus = countryCodeTLDsMinus; + String[] ccPlus = countryCodeTLDsPlus; + String[] genMinus = genericTLDsMinus; + String[] genPlus = genericTLDsPlus; + String[] localMinus = localTLDsMinus; + String[] localPlus = localTLDsPlus; + + // apply the instance overrides + for (final Item item : items) { + final String[] copy = new String[item.values.length]; + // Comparisons are always done with lower-case entries + for (int i = 0; i < item.values.length; i++) { + copy[i] = item.values[i].toLowerCase(Locale.ENGLISH); + } + Arrays.sort(copy); + switch (item.type) { + case COUNTRY_CODE_MINUS: { + ccMinus = copy; + break; + } + case COUNTRY_CODE_PLUS: { + ccPlus = copy; + break; + } + case GENERIC_MINUS: { + genMinus = copy; + break; + } + case GENERIC_PLUS: { + genPlus = copy; + break; + } + case LOCAL_MINUS: { + localMinus = copy; + break; + } + case LOCAL_PLUS: { + localPlus = copy; + break; + } + default: + break; + } + } + + // init the instance overrides + myCountryCodeTLDsMinus = ccMinus; + myCountryCodeTLDsPlus = ccPlus; + myGenericTLDsMinus = genMinus; + myGenericTLDsPlus = genPlus; + myLocalTLDsMinus = localMinus; + myLocalTLDsPlus = localPlus; + } + + private String chompLeadingDot(final String str) { + if (str.startsWith(".")) { + return str.substring(1); + } + return str; + } + + /** + * Gets a copy of an instance level internal array. + * @param table the array type (any of the enum values) + * @return a copy of the array + * @throws IllegalArgumentException if the table type is unexpected, e.g. GENERIC_RO + * @since 1.7 + */ + public String[] getOverrides(final ArrayType table) { + final String[] array; + switch (table) { + case COUNTRY_CODE_MINUS: + array = myCountryCodeTLDsMinus; + break; + case COUNTRY_CODE_PLUS: + array = myCountryCodeTLDsPlus; + break; + case GENERIC_MINUS: + array = myGenericTLDsMinus; + break; + case GENERIC_PLUS: + array = myGenericTLDsPlus; + break; + case LOCAL_MINUS: + array = myLocalTLDsMinus; + break; + case LOCAL_PLUS: + array = myLocalTLDsPlus; + break; + default: + throw new IllegalArgumentException(UNEXPECTED_ENUM_VALUE + table); + } + return Arrays.copyOf(array, array.length); // clone the array + } + + /** + * Does this instance allow local addresses? + * + * @return true if local addresses are allowed. + * @since 1.7 + */ + public boolean isAllowLocal() { + return this.allowLocal; + } + + /** + * Returns true if the specified String parses + * as a valid domain name with a recognized top-level domain. + * The parsing is case-insensitive. + * @param domain the parameter to check for domain name syntax + * @return true if the parameter is a valid domain name + */ + public boolean isValid(String domain) { + if (domain == null) { + return false; + } + domain = unicodeToASCII(domain); + // hosts must be equally reachable via punycode and Unicode + // Unicode is never shorter than punycode, so check punycode + // if domain did not convert, then it will be caught by ASCII + // checks in the regexes below + if (domain.length() > MAX_DOMAIN_LENGTH) { + return false; + } + final String[] groups = domainRegex.match(domain); + if (groups != null && groups.length > 0) { + return isValidTld(groups[0]); + } + return allowLocal && hostnameRegex.isValid(domain); + } + + /** + * Returns true if the specified String matches any + * IANA-defined country code top-level domain. Leading dots are + * ignored if present. The search is case-insensitive. + * @param ccTld the parameter to check for country code TLD status, not null + * @return true if the parameter is a country code TLD + */ + public boolean isValidCountryCodeTld(final String ccTld) { + final String key = chompLeadingDot(unicodeToASCII(ccTld).toLowerCase(Locale.ENGLISH)); + return (arrayContains(COUNTRY_CODE_TLDS, key) || arrayContains(myCountryCodeTLDsPlus, key)) && !arrayContains(myCountryCodeTLDsMinus, key); + } + + // package protected for unit test access + // must agree with isValid() above + final boolean isValidDomainSyntax(String domain) { + if (domain == null) { + return false; + } + domain = unicodeToASCII(domain); + // hosts must be equally reachable via punycode and Unicode + // Unicode is never shorter than punycode, so check punycode + // if domain did not convert, then it will be caught by ASCII + // checks in the regexes below + if (domain.length() > MAX_DOMAIN_LENGTH) { + return false; + } + final String[] groups = domainRegex.match(domain); + return groups != null && groups.length > 0 || hostnameRegex.isValid(domain); + } + /** + * Returns true if the specified String matches any + * IANA-defined generic top-level domain. Leading dots are ignored + * if present. The search is case-insensitive. + * @param gTld the parameter to check for generic TLD status, not null + * @return true if the parameter is a generic TLD + */ + public boolean isValidGenericTld(final String gTld) { + final String key = chompLeadingDot(unicodeToASCII(gTld).toLowerCase(Locale.ENGLISH)); + return (arrayContains(GENERIC_TLDS, key) || arrayContains(myGenericTLDsPlus, key)) && !arrayContains(myGenericTLDsMinus, key); + } + + /** + * Returns true if the specified String matches any + * IANA-defined infrastructure top-level domain. Leading dots are + * ignored if present. The search is case-insensitive. + * @param iTld the parameter to check for infrastructure TLD status, not null + * @return true if the parameter is an infrastructure TLD + */ + public boolean isValidInfrastructureTld(final String iTld) { + final String key = chompLeadingDot(unicodeToASCII(iTld).toLowerCase(Locale.ENGLISH)); + return arrayContains(INFRASTRUCTURE_TLDS, key); + } + + /** + * Returns true if the specified String matches any + * widely used "local" domains (localhost or localdomain). Leading dots are + * ignored if present. The search is case-insensitive. + * @param lTld the parameter to check for local TLD status, not null + * @return true if the parameter is an local TLD + */ + public boolean isValidLocalTld(final String lTld) { + final String key = chompLeadingDot(unicodeToASCII(lTld).toLowerCase(Locale.ENGLISH)); + return (arrayContains(LOCAL_TLDS, key) || arrayContains(myLocalTLDsPlus, key)) + && !arrayContains(myLocalTLDsMinus, key); + } + + /** + * Returns true if the specified String matches any + * IANA-defined top-level domain. Leading dots are ignored if present. + * The search is case-insensitive. + *

+ * If allowLocal is true, the TLD is checked using {@link #isValidLocalTld(String)}. + * The TLD is then checked against {@link #isValidInfrastructureTld(String)}, + * {@link #isValidGenericTld(String)} and {@link #isValidCountryCodeTld(String)} + * @param tld the parameter to check for TLD status, not null + * @return true if the parameter is a TLD + */ + public boolean isValidTld(final String tld) { + if (allowLocal && isValidLocalTld(tld)) { + return true; + } + return isValidInfrastructureTld(tld) + || isValidGenericTld(tld) + || isValidCountryCodeTld(tld); + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/org/apache/commons/validator/routines/EmailValidator.java b/src/main/java/com/networknt/org/apache/commons/validator/routines/EmailValidator.java new file mode 100644 index 000000000..5c86af0f4 --- /dev/null +++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/EmailValidator.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

Perform email validations.

+ *

+ * Based on a script by Sandeep V. Tamhankar + * https://javascript.internet.com + *

+ *

+ * This implementation is not guaranteed to catch all possible errors in an email address. + *

. + * + * @since 1.4 + */ +public class EmailValidator implements Serializable { + + private static final long serialVersionUID = 1705927040799295880L; + + private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; + private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]"; + private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")"; + private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; + + private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; + private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; + private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; + + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); + private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); + + private static final int MAX_USERNAME_LEN = 64; + + /** + * Singleton instance of this class, which + * doesn't consider local addresses as valid. + */ + private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false); + + /** + * Singleton instance of this class, which + * doesn't consider local addresses as valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true); + + /** + * Singleton instance of this class, which does + * consider local addresses valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false); + + /** + * Singleton instance of this class, which does + * consider local addresses valid. + */ + private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true); + + /** + * Returns the Singleton instance of this validator. + * + * @return singleton instance of this validator. + */ + public static EmailValidator getInstance() { + return EMAIL_VALIDATOR; + } + + /** + * Returns the Singleton instance of this validator, + * with local validation as required. + * + * @param allowLocal Should local addresses be considered valid? + * @return singleton instance of this validator + */ + public static EmailValidator getInstance(final boolean allowLocal) { + return getInstance(allowLocal, false); + } + + /** + * Returns the Singleton instance of this validator, + * with local validation as required. + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + * @return singleton instance of this validator + */ + public static EmailValidator getInstance(final boolean allowLocal, final boolean allowTld) { + if (allowLocal) { + if (allowTld) { + return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD; + } + return EMAIL_VALIDATOR_WITH_LOCAL; + } + if (allowTld) { + return EMAIL_VALIDATOR_WITH_TLD; + } + return EMAIL_VALIDATOR; + } + + private final boolean allowTld; + + private final DomainValidator domainValidator; + + /** + * Protected constructor for subclasses to use. + * + * @param allowLocal Should local addresses be considered valid? + */ + protected EmailValidator(final boolean allowLocal) { + this(allowLocal, false); + } + + /** + * Protected constructor for subclasses to use. + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + */ + protected EmailValidator(final boolean allowLocal, final boolean allowTld) { + this.allowTld = allowTld; + this.domainValidator = DomainValidator.getInstance(allowLocal); + } + + /** + * constructor for creating instances with the specified domainValidator + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + * @param domainValidator allow override of the DomainValidator. + * The instance must have the same allowLocal setting. + * @since 1.7 + */ + public EmailValidator(final boolean allowLocal, final boolean allowTld, final DomainValidator domainValidator) { + this.allowTld = allowTld; + if (domainValidator == null) { + throw new IllegalArgumentException("DomainValidator cannot be null"); + } + if (domainValidator.isAllowLocal() != allowLocal) { + throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting"); + } + this.domainValidator = domainValidator; + } + + /** + *

Checks if a field has a valid e-mail address.

+ * + * @param email The value validation is being performed on. A {@code null} + * value is considered invalid. + * @return true if the email address is valid. + */ + public boolean isValid(final String email) { + if (email == null) { + return false; + } + + if (email.endsWith(".")) { // check this first - it's cheap! + return false; + } + + // Check the whole email address structure + final Matcher emailMatcher = EMAIL_PATTERN.matcher(email); + if (!emailMatcher.matches()) { + return false; + } + + if (!isValidUser(emailMatcher.group(1))) { + return false; + } + + return isValidDomain(emailMatcher.group(2)); + } + + /** + * Returns true if the domain component of an email address is valid. + * + * @param domain being validated, may be in IDN format + * @return true if the email address's domain is valid. + */ + protected boolean isValidDomain(final String domain) { + // see if domain is an IP address in brackets + final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); + + if (ipDomainMatcher.matches()) { + final InetAddressValidator inetAddressValidator = + InetAddressValidator.getInstance(); + return inetAddressValidator.isValid(ipDomainMatcher.group(1)); + } + // Domain is symbolic name + if (allowTld) { + return domainValidator.isValid(domain) || !domain.startsWith(".") && domainValidator.isValidTld(domain); + } + return domainValidator.isValid(domain); + } + + /** + * Returns true if the user component of an email address is valid. + * + * @param user being validated + * @return true if the user name is valid. + */ + protected boolean isValidUser(final String user) { + + if (user == null || user.length() > MAX_USERNAME_LEN) { + return false; + } + + return USER_PATTERN.matcher(user).matches(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/org/apache/commons/validator/routines/InetAddressValidator.java b/src/main/java/com/networknt/org/apache/commons/validator/routines/InetAddressValidator.java new file mode 100644 index 000000000..8a27409c3 --- /dev/null +++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/InetAddressValidator.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +/** + *

InetAddress validation and conversion routines (java.net.InetAddress).

+ * + *

This class provides methods to validate a candidate IP address. + * + *

+ * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method. + *

+ * + * @since 1.4 + */ +public class InetAddressValidator implements Serializable { + + private static final int MAX_BYTE = 128; + + private static final int IPV4_MAX_OCTET_VALUE = 255; + + private static final int MAX_UNSIGNED_SHORT = 0xffff; + + private static final int BASE_16 = 16; + + private static final long serialVersionUID = -919201640201914789L; + + private static final String IPV4_REGEX = + "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + + // Max number of hex groups (separated by :) in an IPV6 address + private static final int IPV6_MAX_HEX_GROUPS = 8; + + // Max hex digits in each IPv6 group + private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; + + /** + * Singleton instance of this class. + */ + private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); + + private static final Pattern DIGITS_PATTERN = Pattern.compile("\\d{1,3}"); + + private static final Pattern ID_CHECK_PATTERN = Pattern.compile("[^\\s/%]+"); + /** + * Returns the singleton instance of this validator. + * @return the singleton instance of this validator + */ + public static InetAddressValidator getInstance() { + return VALIDATOR; + } + + /** IPv4 RegexValidator */ + private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); + + /** + * Checks if the specified string is a valid IPv4 or IPv6 address. + * @param inetAddress the string to validate + * @return true if the string validates as an IP address + */ + public boolean isValid(final String inetAddress) { + return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); + } + + /** + * Validates an IPv4 address. Returns true if valid. + * @param inet4Address the IPv4 address to validate + * @return true if the argument contains a valid IPv4 address + */ + public boolean isValidInet4Address(final String inet4Address) { + // verify that address conforms to generic IPv4 format + final String[] groups = ipv4Validator.match(inet4Address); + + if (groups == null) { + return false; + } + + // verify that address subgroups are legal + for (final String ipSegment : groups) { + if (ipSegment == null || ipSegment.isEmpty()) { + return false; + } + + int iIpSegment = 0; + + try { + iIpSegment = Integer.parseInt(ipSegment); + } catch (final NumberFormatException e) { + return false; + } + + if (iIpSegment > IPV4_MAX_OCTET_VALUE) { + return false; + } + + if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { + return false; + } + + } + + return true; + } + + /** + * Validates an IPv6 address. Returns true if valid. + * @param inet6Address the IPv6 address to validate + * @return true if the argument contains a valid IPv6 address + * + * @since 1.4.1 + */ + public boolean isValidInet6Address(String inet6Address) { + String[] parts; + // remove prefix size. This will appear after the zone id (if any) + parts = inet6Address.split("/", -1); + if (parts.length > 2) { + return false; // can only have one prefix specifier + } + if (parts.length == 2) { + if (!DIGITS_PATTERN.matcher(parts[1]).matches()) { + return false; // not a valid number + } + final int bits = Integer.parseInt(parts[1]); // cannot fail because of RE check + if (bits < 0 || bits > MAX_BYTE) { + return false; // out of range + } + } + // remove zone-id + parts = parts[0].split("%", -1); + if (parts.length > 2) { + return false; + } + // The id syntax is implementation independent, but it presumably cannot allow: + // whitespace, '/' or '%' + if (parts.length == 2 && !ID_CHECK_PATTERN.matcher(parts[1]).matches()) { + return false; // invalid id + } + inet6Address = parts[0]; + final boolean containsCompressedZeroes = inet6Address.contains("::"); + if (containsCompressedZeroes && inet6Address.indexOf("::") != inet6Address.lastIndexOf("::")) { + return false; + } + if (inet6Address.startsWith(":") && !inet6Address.startsWith("::") + || inet6Address.endsWith(":") && !inet6Address.endsWith("::")) { + return false; + } + String[] octets = inet6Address.split(":"); + if (containsCompressedZeroes) { + final List octetList = new ArrayList<>(Arrays.asList(octets)); + if (inet6Address.endsWith("::")) { + // String.split() drops ending empty segments + octetList.add(""); + } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { + octetList.remove(0); + } + octets = octetList.toArray(new String[0]); + } + if (octets.length > IPV6_MAX_HEX_GROUPS) { + return false; + } + int validOctets = 0; + int emptyOctets = 0; // consecutive empty chunks + for (int index = 0; index < octets.length; index++) { + final String octet = octets[index]; + if (octet.isEmpty()) { + emptyOctets++; + if (emptyOctets > 1) { + return false; + } + } else { + emptyOctets = 0; + // Is last chunk an IPv4 address? + if (index == octets.length - 1 && octet.contains(".")) { + if (!isValidInet4Address(octet)) { + return false; + } + validOctets += 2; + continue; + } + if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { + return false; + } + int octetInt = 0; + try { + octetInt = Integer.parseInt(octet, BASE_16); + } catch (final NumberFormatException e) { + return false; + } + if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { + return false; + } + } + validOctets++; + } + return validOctets <= IPV6_MAX_HEX_GROUPS && (validOctets >= IPV6_MAX_HEX_GROUPS || containsCompressedZeroes); + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/org/apache/commons/validator/routines/RegexValidator.java b/src/main/java/com/networknt/org/apache/commons/validator/routines/RegexValidator.java new file mode 100644 index 000000000..87d5a043e --- /dev/null +++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/RegexValidator.java @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.org.apache.commons.validator.routines; + +import java.io.Serializable; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Regular Expression validation (using the JRE's regular expression support). + *

+ * Construct the validator either for a single regular expression or a set (array) of + * regular expressions. By default validation is case sensitive but constructors + * are provided to allow case in-sensitive validation. For example to create + * a validator which does case in-sensitive validation for a set of regular + * expressions: + *

+ *
+ * 
+ * String[] regexs = new String[] {...};
+ * RegexValidator validator = new RegexValidator(regexs, false);
+ * 
+ * 
+ * + *
    + *
  • Validate {@code true} or {@code false}:
  • + *
  • + *
      + *
    • boolean valid = validator.isValid(value);
    • + *
    + *
  • + *
  • Validate returning an aggregated String of the matched groups:
  • + *
  • + *
      + *
    • String result = validator.validate(value);
    • + *
    + *
  • + *
  • Validate returning the matched groups:
  • + *
  • + *
      + *
    • String[] result = validator.match(value);
    • + *
    + *
  • + *
+ * + * Note that patterns are matched against the entire input. + * + *

+ * Cached instances pre-compile and re-use {@link Pattern}(s) - which according + * to the {@link Pattern} API are safe to use in a multi-threaded environment. + *

+ * + * @since 1.4 + */ +public class RegexValidator implements Serializable { + + private static final long serialVersionUID = -8832409930574867162L; + + private final Pattern[] patterns; + + /** + * Constructs a case sensitive validator that matches any one + * in the list of regular expressions. + * + * @param regexs The set of regular expressions this validator will + * validate against + */ + RegexValidator(final List regexs) { + this(regexs.toArray(new String[] {}), true); + } + + /** + * Constructs a case sensitive validator for a single + * regular expression. + * + * @param regex The regular expression this validator will + * validate against + */ + public RegexValidator(final String regex) { + this(regex, true); + } + + /** + * Constructs a validator for a single regular expression + * with the specified case sensitivity. + * + * @param regex The regular expression this validator will + * validate against + * @param caseSensitive when {@code true} matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(final String regex, final boolean caseSensitive) { + this(new String[] { regex }, caseSensitive); + } + + /** + * Constructs a case sensitive validator that matches any one + * in the array of regular expressions. + * + * @param regexs The set of regular expressions this validator will + * validate against + */ + public RegexValidator(final String... regexs) { + this(regexs, true); + } + + /** + * Constructs a validator that matches any one of the set of regular + * expressions with the specified case sensitivity. + * + * @param regexs The set of regular expressions this validator will + * validate against + * @param caseSensitive when {@code true} matching is case + * sensitive, otherwise matching is case in-sensitive + */ + public RegexValidator(final String[] regexs, final boolean caseSensitive) { + if (regexs == null || regexs.length == 0) { + throw new IllegalArgumentException("Regular expressions are missing"); + } + patterns = new Pattern[regexs.length]; + final int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); + for (int i = 0; i < regexs.length; i++) { + if (regexs[i] == null || regexs[i].isEmpty()) { + throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); + } + patterns[i] = Pattern.compile(regexs[i], flags); + } + } + + /** + * Gets a copy of the Patterns. + * + * @return a copy of the Patterns. + * @since 1.8 + */ + public Pattern[] getPatterns() { + return patterns.clone(); + } + + /** + * Validates a value against the set of regular expressions. + * + * @param value The value to validate. + * @return {@code true} if the value is valid + * otherwise {@code false}. + */ + public boolean isValid(final String value) { + if (value == null) { + return false; + } + for (final Pattern pattern : patterns) { + if (pattern.matcher(value).matches()) { + return true; + } + } + return false; + } + + /** + * Validates a value against the set of regular expressions + * returning the array of matched groups. + * + * @param value The value to validate. + * @return String array of the groups matched if + * valid or null if invalid + */ + public String[] match(final String value) { + if (value == null) { + return null; + } + for (final Pattern pattern : patterns) { + final Matcher matcher = pattern.matcher(value); + if (matcher.matches()) { + final int count = matcher.groupCount(); + final String[] groups = new String[count]; + for (int j = 0; j < count; j++) { + groups[j] = matcher.group(j + 1); + } + return groups; + } + } + return null; + } + + + /** + * Validates a value against the set of regular expressions + * returning a String value of the aggregated groups. + * + * @param value The value to validate. + * @return Aggregated String value comprised of the + * groups matched if valid or null if invalid + */ + public String validate(final String value) { + if (value == null) { + return null; + } + for (final Pattern pattern : patterns) { + final Matcher matcher = pattern.matcher(value); + if (matcher.matches()) { + final int count = matcher.groupCount(); + if (count == 1) { + return matcher.group(1); + } + final StringBuilder buffer = new StringBuilder(); + for (int j = 0; j < count; j++) { + final String component = matcher.group(j+1); + if (component != null) { + buffer.append(component); + } + } + return buffer.toString(); + } + } + return null; + } + + /** + * Provides a String representation of this validator. + * @return A String representation of this validator. + */ + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append("RegexValidator{"); + for (int i = 0; i < patterns.length; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(patterns[i].pattern()); + } + buffer.append("}"); + return buffer.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/AbsoluteIri.java b/src/main/java/com/networknt/schema/AbsoluteIri.java new file mode 100644 index 000000000..890f5e003 --- /dev/null +++ b/src/main/java/com/networknt/schema/AbsoluteIri.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.Objects; + +import com.networknt.schema.utils.Strings; + +/** + * The absolute IRI is an IRI without the fragment. + *

+ * absolute-IRI = scheme ":" ihier-part [ "?" iquery ] + *

+ * This does not attempt to validate whether the value really conforms to an + * absolute IRI format as in earlier drafts the IDs are not defined as such. + */ +public class AbsoluteIri { + private final String value; + + /** + * Constructs a new IRI given the value. + * + * @param value String + */ + public AbsoluteIri(String value) { + this.value = value; + } + + /** + * Constructs a new IRI given the value. + * + * @param iri the value + * @return the new absolute IRI + */ + public static AbsoluteIri of(String iri) { + return new AbsoluteIri(iri); + } + + /** + * Constructs a new IRI by parsing the given string and then resolving it + * against this IRI. + * + * @param iri to resolve + * @return the new absolute IRI + */ + public AbsoluteIri resolve(String iri) { + return new AbsoluteIri(resolve(this.value, iri)); + } + + /** + * Gets the scheme of the IRI. + * + * @return the scheme + */ + public String getScheme() { + return getScheme(this.value); + } + + /** + * Returns the scheme and authority components of the IRI. + * + * @return the scheme and authority components + */ + protected String getSchemeAuthority() { + return getSchemeAuthority(this.value); + } + + /** + * Determines if the iri is absolute or relative. + * + * @param iri to determine + * @return true if absolute + */ + private static boolean isAbsoluteIri(String iri) { + int length = iri.length(); + for (int x = 0; x < length; x++) { + char ch = iri.charAt(x); + if (ch == ':') { + // if the first segment is relative with a colon it must be a dot path segment + // ie. ./foo:bar + return true; + } else if (ch == '/' || ch == '?' || ch == '#') { + return false; + } + } + return false; + } + + /** + * Constructs a new IRI by parsing the given string and then resolving it + * against this IRI. + * + * @param parent the parent absolute IRI + * @param iri to resolve + * @return the new absolute IRI + */ + public static String resolve(String parent, String iri) { + if (isAbsoluteIri(iri)) { + // IRI is absolute + return iri; + } else { + // IRI is relative to this + if (parent == null) { + return iri; + } + if (iri.startsWith("/")) { + // IRI is relative to this root + return getSchemeAuthority(parent) + iri; + } else { + // IRI is relative to this path + String base = parent; + int scheme = parent.indexOf("://"); + if (scheme == -1) { + scheme = parent.indexOf(':'); + if (scheme == -1) { + scheme = 0; + } + } else { + scheme = scheme + 3; + } + base = parent(base, scheme); + + String[] iriParts = Strings.split(iri, '/'); + for (int x = 0; x < iriParts.length; x++) { + if ("..".equals(iriParts[x])) { + base = parent(base, scheme); + } else if (".".equals(iriParts[x])) { + // skip + } else { + if (base.endsWith(":")) { + if (parent.length() > base.length() && parent.charAt(base.length()) == '/') { + base = base + "/" + iriParts[x]; + } else { + base = base + iriParts[x]; + } + } else { + base = base + "/" + iriParts[x]; + } + } + } + return base; + } + } + } + + protected static String parent(String iri, int scheme) { + int slash = iri.lastIndexOf('/'); + if (slash == -1) { + slash = iri.lastIndexOf(':'); + if (slash != -1) { + slash = slash + 1; + } + } + if (slash != -1 && slash > scheme) { + return iri.substring(0, slash); + } + return iri; + } + + /** + * Returns the scheme and authority components of the IRI. + * + * @param iri to parse + * @return the scheme and authority components + */ + protected static String getSchemeAuthority(String iri) { + if (iri == null) { + return ""; + } + // iri refers to root + int start = iri.indexOf("://"); + if (start == -1) { + start = 0; + } else { + start = start + 3; + } + int end = iri.indexOf('/', start); + return end != -1 ? iri.substring(0, end) : iri; + } + + /** + * Returns the scheme of the IRI. + * + * @param iri to parse + * @return the scheme + */ + public static String getScheme(String iri) { + if (iri == null) { + return ""; + } + // iri refers to root + int start = iri.indexOf(':'); + if (start == -1) { + return ""; + } else { + return iri.substring(0, start); + } + } + + @Override + public String toString() { + return this.value; + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbsoluteIri other = (AbsoluteIri) obj; + return Objects.equals(this.value, other.value); + } + +} diff --git a/src/main/java/com/networknt/schema/AbstractCollector.java b/src/main/java/com/networknt/schema/AbstractCollector.java deleted file mode 100644 index cb0cf4bcc..000000000 --- a/src/main/java/com/networknt/schema/AbstractCollector.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -public abstract class AbstractCollector implements Collector { - - @Override - public void combine(Object object) { - // Do nothing. This is the default Implementation. - } -} diff --git a/src/main/java/com/networknt/schema/AbstractFormat.java b/src/main/java/com/networknt/schema/AbstractFormat.java deleted file mode 100644 index 20d7dac1c..000000000 --- a/src/main/java/com/networknt/schema/AbstractFormat.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -public abstract class AbstractFormat implements Format { - private final String name; - private final String errorMessageDescription; - - public AbstractFormat(String name) { - this(name, ""); - } - - public AbstractFormat(String name, String errorMessageDescription) { - this.name = name; - this.errorMessageDescription = errorMessageDescription; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getErrorMessageDescription() { - return errorMessageDescription; - } -} diff --git a/src/main/java/com/networknt/schema/AbstractJsonValidator.java b/src/main/java/com/networknt/schema/AbstractJsonValidator.java deleted file mode 100644 index c8196a87f..000000000 --- a/src/main/java/com/networknt/schema/AbstractJsonValidator.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -public abstract class AbstractJsonValidator implements JsonValidator { - private final String keyword; - - protected AbstractJsonValidator(String keyword) { - this.keyword = keyword; - } - - public Set validate(JsonNode node) { - return validate(node, node, AT_ROOT); - } - - protected ValidationMessage buildValidationMessage(ErrorMessageType errorMessageType, String at, String... arguments) { - return ValidationMessage.of(keyword, errorMessageType, at, arguments); - } - - protected ValidationMessage buildValidationMessage(ErrorMessageType errorMessageType, String at, Map details) { - return ValidationMessage.of(keyword, errorMessageType, at, details); - } - - protected Set pass() { - return Collections.emptySet(); - } - - protected Set fail(ErrorMessageType errorMessageType, String at, Map details) { - return Collections.singleton(buildValidationMessage(errorMessageType, at, details)); - } - - protected Set fail(ErrorMessageType errorMessageType, String at, String... arguments) { - return Collections.singleton(buildValidationMessage(errorMessageType, at, arguments)); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = Collections.emptySet(); - if (shouldValidateSchema) { - validationMessages = validate(node, rootNode, at); - } - return validationMessages; - } -} diff --git a/src/main/java/com/networknt/schema/AbstractKeyword.java b/src/main/java/com/networknt/schema/AbstractKeyword.java deleted file mode 100644 index 4f8feca0a..000000000 --- a/src/main/java/com/networknt/schema/AbstractKeyword.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - - -public abstract class AbstractKeyword implements Keyword { - private final String value; - - public AbstractKeyword(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - -} diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java deleted file mode 100644 index 5182bf5ed..000000000 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class AdditionalPropertiesValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(AdditionalPropertiesValidator.class); - - private final boolean allowAdditionalProperties; - private final JsonSchema additionalPropertiesSchema; - private final Set allowedProperties; - private final List patternProperties = new ArrayList(); - - public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ADDITIONAL_PROPERTIES, validationContext); - if (schemaNode.isBoolean()) { - allowAdditionalProperties = schemaNode.booleanValue(); - additionalPropertiesSchema = null; - } else if (schemaNode.isObject()) { - allowAdditionalProperties = true; - additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); - } else { - allowAdditionalProperties = false; - additionalPropertiesSchema = null; - } - - allowedProperties = new HashSet(); - JsonNode propertiesNode = parentSchema.getSchemaNode().get(PropertiesValidator.PROPERTY); - if (propertiesNode != null) { - for (Iterator it = propertiesNode.fieldNames(); it.hasNext(); ) { - allowedProperties.add(it.next()); - } - } - - JsonNode patternPropertiesNode = parentSchema.getSchemaNode().get(PatternPropertiesValidator.PROPERTY); - if (patternPropertiesNode != null) { - for (Iterator it = patternPropertiesNode.fieldNames(); it.hasNext(); ) { - patternProperties.add(Pattern.compile(it.next())); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - if (logger.isDebugEnabled()) debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - if (!node.isObject()) { - // ignore no object - return errors; - } - - for (Iterator it = node.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - // skip the context items - if (pname.startsWith("#")) { - continue; - } - boolean handledByPatternProperties = false; - for (Pattern pattern : patternProperties) { - Matcher m = pattern.matcher(pname); - if (m.find()) { - handledByPatternProperties = true; - break; - } - } - - if (!allowedProperties.contains(pname) && !handledByPatternProperties) { - if (!allowAdditionalProperties) { - errors.add(buildValidationMessage(at, pname)); - } else { - if (additionalPropertiesSchema != null) { - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - if (state != null && state.isWalkEnabled()) { - errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabled())); - } else { - errors.addAll(additionalPropertiesSchema.validate(node.get(pname), rootNode, at + "." + pname)); - } - } - } - } - } - return Collections.unmodifiableSet(errors); - } - - @Override - public void preloadJsonSchema() { - if(additionalPropertiesSchema!=null) { - additionalPropertiesSchema.initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java deleted file mode 100644 index 79ee22c2d..000000000 --- a/src/main/java/com/networknt/schema/AllOfValidator.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AllOfValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(AllOfValidator.class); - - private final ValidationContext validationContext; - private final List schemas = new ArrayList(); - - public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext); - this.validationContext = validationContext; - int size = schemaNode.size(); - for (int i = 0; i < size; i++) { - schemas.add(new JsonSchema(validationContext, - getValidatorType().getValue(), - parentSchema.getCurrentUri(), - schemaNode.get(i), - parentSchema)); - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (JsonSchema schema : schemas) { - errors.addAll(schema.validate(node, rootNode, at)); - - if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - final Iterator arrayElements = schemaNode.elements(); - while (arrayElements.hasNext()) { - final ObjectNode allOfEntry = (ObjectNode) arrayElements.next(); - final JsonNode $ref = allOfEntry.get("$ref"); - if (null != $ref) { - final ValidationContext.DiscriminatorContext currentDiscriminatorContext = validationContext - .getCurrentDiscriminatorContext(); - if (null != currentDiscriminatorContext) { - final ObjectNode discriminator = currentDiscriminatorContext - .getDiscriminatorForPath(allOfEntry.get("$ref").asText()); - if (null != discriminator) { - registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator, parentSchema, at); - // now we have to check whether we have hit the right target - final String discriminatorPropertyName = discriminator.get("propertyName").asText(); - final JsonNode discriminatorNode = node.get(discriminatorPropertyName); - final String discriminatorPropertyValue = discriminatorNode == null - ? null - : discriminatorNode.textValue(); - - final JsonSchema jsonSchema = parentSchema; - checkDiscriminatorMatch( - currentDiscriminatorContext, - discriminator, - discriminatorPropertyValue, - jsonSchema); - } - } - } - } - } - } - - return Collections.unmodifiableSet(errors); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - - for (JsonSchema schema : schemas) { - // Walk through the schema - validationMessages.addAll(schema.walk(node, rootNode, at, shouldValidateSchema)); - } - return Collections.unmodifiableSet(validationMessages); - } - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemas); - } -} diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java deleted file mode 100644 index 07cfb9111..000000000 --- a/src/main/java/com/networknt/schema/AnyOfValidator.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.stream.Collectors; - -public class AnyOfValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class); - private static final String REMARK = "Remaining validation messages report why candidate schemas didn't match"; - private static final String DISCRIMINATOR_REMARK = "and the discriminator-selected candidate schema didn't pass validation"; - - private final List schemas = new ArrayList(); - private final ValidationContext validationContext; - private final ValidationContext.DiscriminatorContext discriminatorContext; - - public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext); - this.validationContext = validationContext; - int size = schemaNode.size(); - for (int i = 0; i < size; i++) { - schemas.add(new JsonSchema(validationContext, - getValidatorType().getValue(), - parentSchema.getCurrentUri(), - schemaNode.get(i), - parentSchema)); - } - - if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - this.discriminatorContext = new ValidationContext.DiscriminatorContext(); - } else { - this.discriminatorContext = null; - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - validationContext.enterDiscriminatorContext(this.discriminatorContext, at); - } - - Set allErrors = new LinkedHashSet(); - String typeValidatorName = "anyOf/type"; - - try { - for (JsonSchema schema : schemas) { - if (schema.getValidators().containsKey(typeValidatorName)) { - TypeValidator typeValidator = ((TypeValidator) schema.getValidators().get(typeValidatorName)); - //If schema has type validator and node type doesn't match with schemaType then ignore it - //For union type, it is a must to call TypeValidator - if (typeValidator.getSchemaType() != JsonType.UNION && !typeValidator.equalsToSchemaType(node)) { - allErrors.add(buildValidationMessage(at, typeValidator.getSchemaType().toString())); - continue; - } - } - Set errors = schema.validate(node, rootNode, at); - if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())) { - return errors; - } else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - if (discriminatorContext.isDiscriminatorMatchFound()) { - if (!errors.isEmpty()) { - errors.add(buildValidationMessage(at, DISCRIMINATOR_REMARK)); - } - return errors; - } - } - allErrors.addAll(errors); - } - - if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && discriminatorContext.isActive()) { - final Set errors = new HashSet(); - errors.add(buildValidationMessage(at, "based on the provided discriminator. No alternative could be chosen based on the discriminator property")); - return Collections.unmodifiableSet(errors); - } - } finally { - if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - validationContext.leaveDiscriminatorContextImmediately(at); - } - } - return Collections.unmodifiableSet(allErrors); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - ArrayList> results = new ArrayList<>(schemas.size()); - for (JsonSchema schema : schemas) { - results.add(schema.walk(node, rootNode, at, shouldValidateSchema)); - } - if(! shouldValidateSchema) { - return new LinkedHashSet<>(); - } - boolean atLeastOneValid = results.stream() - .anyMatch(Set::isEmpty); - if(atLeastOneValid) { - return new LinkedHashSet<>(); - } - return results.stream() - .flatMap(Collection::stream) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemas); - } -} diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java deleted file mode 100644 index 89283e386..000000000 --- a/src/main/java/com/networknt/schema/BaseJsonValidator.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.net.URI; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.schema.ValidationContext.DiscriminatorContext; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; - -public abstract class BaseJsonValidator implements JsonValidator { - protected String schemaPath; - protected JsonNode schemaNode; - protected JsonSchema parentSchema; - private boolean suppressSubSchemaRetrieval; - private ValidatorTypeCode validatorType; - private ErrorMessageType errorMessageType; - - protected final boolean failFast; - protected final ApplyDefaultsStrategy applyDefaultsStrategy; - - public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidatorTypeCode validatorType, ValidationContext validationContext) { - this(schemaPath, schemaNode, parentSchema, validatorType, false, - validationContext.getConfig() != null && validationContext.getConfig().isFailFast(), - validationContext.getConfig() != null ? validationContext.getConfig().getApplyDefaultsStrategy() : null); - } - - // TODO: can this be made package private? - @Deprecated // use the BaseJsonValidator below - public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidatorTypeCode validatorType, boolean suppressSubSchemaRetrieval, boolean failFast) { - this(schemaPath, schemaNode, parentSchema, validatorType, false, failFast, null); - } - - public BaseJsonValidator(String schemaPath, - JsonNode schemaNode, - JsonSchema parentSchema, - ValidatorTypeCode validatorType, - boolean suppressSubSchemaRetrieval, - boolean failFast, - ApplyDefaultsStrategy applyDefaultsStrategy) { - this.errorMessageType = validatorType; - this.schemaPath = schemaPath; - this.schemaNode = schemaNode; - this.parentSchema = parentSchema; - this.validatorType = validatorType; - this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; - this.failFast = failFast; - this.applyDefaultsStrategy = applyDefaultsStrategy != null ? applyDefaultsStrategy : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY; - } - - public String getSchemaPath() { - return schemaPath; - } - - public JsonNode getSchemaNode() { - return schemaNode; - } - - public JsonSchema getParentSchema() { - return parentSchema; - } - - protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) { - return suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(schemaNode, validationContext); - } - - private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext) { - final JsonNode node = schemaNode.get("id"); - if (node == null) { - return null; - } - if (node.equals(schemaNode.get("$schema"))) { - return null; - } - - final String text = node.textValue(); - if (text == null) { - return null; - } else { - final URI uri; - try { - uri = validationContext.getURIFactory().create(node.textValue()); - } catch (IllegalArgumentException e) { - return null; - } - return validationContext.getJsonSchemaFactory().getSchema(uri, validationContext.getConfig()); - } - } - - public Set validate(JsonNode node) { - return validate(node, node, AT_ROOT); - } - - protected boolean equals(double n1, double n2) { - return Math.abs(n1 - n2) < 1e-12; - } - - protected boolean greaterThan(double n1, double n2) { - return n1 - n2 > 1e-12; - } - - protected boolean lessThan(double n1, double n2) { - return n1 - n2 < -1e-12; - } - - protected void parseErrorCode(String errorCodeKey) { - JsonNode errorCodeNode = getParentSchema().getSchemaNode().get(errorCodeKey); - if (errorCodeNode != null && errorCodeNode.isTextual()) { - String errorCodeText = errorCodeNode.asText(); - if (StringUtils.isNotBlank(errorCodeText)) { - errorMessageType = CustomErrorMessageType.of(errorCodeText); - } - } - } - - protected ValidationMessage buildValidationMessage(String at, String... arguments) { - final ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments); - if (failFast && !isPartOfOneOfMultipleType()) { - throw new JsonSchemaException(message); - } - return message; - } - - protected void debug(Logger logger, JsonNode node, JsonNode rootNode, String at) { - if (logger.isDebugEnabled()) { - logger.debug("validate( " + node + ", " + rootNode + ", " + at + ")"); - } - } - - protected ValidatorTypeCode getValidatorType() { - return validatorType; - } - - protected String getNodeFieldType() { - JsonNode typeField = this.getParentSchema().getSchemaNode().get("type"); - if (typeField != null) { - return typeField.asText(); - } - return null; - } - - /** - * This is default implementation of walk method. Its job is to call the - * validate method if shouldValidateSchema is enabled. - */ - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages = validate(node, rootNode, at); - } - return validationMessages; - } - - protected void preloadJsonSchemas(final Collection schemas) { - for (final JsonSchema schema: schemas) { - schema.initializeValidators(); - } - } - - protected boolean isPartOfOneOfMultipleType() { - return parentSchema.schemaPath.equals(ValidatorTypeCode.ONE_OF.getValue()); - } - - /* ********************** START OF OpenAPI 3.0.x DISCRIMINATOR METHODS ********************************* */ - - /** - * Checks based on the current {@link DiscriminatorContext} whether the provided {@link JsonSchema} a match against - * against the current discriminator. - * - * @param currentDiscriminatorContext the currently active {@link DiscriminatorContext} - * @param discriminator the discriminator to use for the check - * @param discriminatorPropertyValue the value of the discriminator/propertyName field - * @param jsonSchema the {@link JsonSchema} to check - */ - protected static void checkDiscriminatorMatch(final ValidationContext.DiscriminatorContext currentDiscriminatorContext, - final ObjectNode discriminator, - final String discriminatorPropertyValue, - final JsonSchema jsonSchema) { - if (discriminatorPropertyValue == null) { - currentDiscriminatorContext.markMatch(); - return; - } - - final JsonNode discriminatorMapping = discriminator.get("mapping"); - if (null == discriminatorMapping) { - checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext, - discriminatorPropertyValue, - jsonSchema); - } else { - checkForExplicitDiscriminatorMappingMatch(currentDiscriminatorContext, - discriminatorPropertyValue, - discriminatorMapping, - jsonSchema); - if (!currentDiscriminatorContext.isDiscriminatorMatchFound() - && noExplicitDiscriminatorKeyOverride(discriminatorMapping, jsonSchema)) { - checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext, - discriminatorPropertyValue, - jsonSchema); - } - } - } - - /** - * Rolls up all nested and compatible discriminators to the root discriminator of the type. Detects attempts to redefine - * the propertyName or mappings. - * - * @param currentDiscriminatorContext the currently active {@link DiscriminatorContext} - * @param discriminator the discriminator to use for the check - * @param schema the value of the discriminator/propertyName field - * @param at the logging prefix - */ - protected static void registerAndMergeDiscriminator(final DiscriminatorContext currentDiscriminatorContext, - final ObjectNode discriminator, - final JsonSchema schema, - final String at) { - final JsonNode discriminatorOnSchema = schema.schemaNode.get("discriminator"); - if (null != discriminatorOnSchema && null != currentDiscriminatorContext - .getDiscriminatorForPath(schema.schemaPath)) { - // this is where A -> B -> C inheritance exists, A has the root discriminator and B adds to the mapping - final JsonNode propertyName = discriminatorOnSchema.get("propertyName"); - if (null != propertyName) { - throw new JsonSchemaException(at + " schema " + schema + " attempts redefining the discriminator property"); - } - final ObjectNode mappingOnContextDiscriminator = (ObjectNode) discriminator.get("mapping"); - final ObjectNode mappingOnCurrentSchemaDiscriminator = (ObjectNode) discriminatorOnSchema.get("mapping"); - if (null == mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) { - // here we have a mapping on a nested discriminator and none on the root discriminator, so we can simply - // make it the root's - discriminator.set("mapping", discriminatorOnSchema); - } else if (null != mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) { - // here we have to merge. The spec doesn't specify anything on this, but here we don't accept redefinition of - // mappings that already exist - final Iterator> fieldsToAdd = mappingOnCurrentSchemaDiscriminator.fields(); - while (fieldsToAdd.hasNext()) { - final Map.Entry fieldToAdd = fieldsToAdd.next(); - final String mappingKeyToAdd = fieldToAdd.getKey(); - final JsonNode mappingValueToAdd = fieldToAdd.getValue(); - - final JsonNode currentMappingValue = mappingOnContextDiscriminator.get(mappingKeyToAdd); - if (null != currentMappingValue && currentMappingValue != mappingValueToAdd) { - throw new JsonSchemaException(at + "discriminator mapping redefinition from " + mappingKeyToAdd - + "/" + currentMappingValue + " to " + mappingValueToAdd); - } else if (null == currentMappingValue) { - mappingOnContextDiscriminator.set(mappingKeyToAdd, mappingValueToAdd); - } - } - } - } - currentDiscriminatorContext.registerDiscriminator(schema.schemaPath, discriminator); - } - - private static void checkForImplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext, - final String discriminatorPropertyValue, - final JsonSchema schema) { - if (schema.schemaPath.endsWith("/" + discriminatorPropertyValue)) { - currentDiscriminatorContext.markMatch(); - } - } - - private static void checkForExplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext, - final String discriminatorPropertyValue, - final JsonNode discriminatorMapping, - final JsonSchema schema) { - final Iterator> explicitMappings = discriminatorMapping.fields(); - while (explicitMappings.hasNext()) { - final Map.Entry candidateExplicitMapping = explicitMappings.next(); - if (candidateExplicitMapping.getKey().equals(discriminatorPropertyValue) - && schema.schemaPath.equals(candidateExplicitMapping.getValue().asText())) { - currentDiscriminatorContext.markMatch(); - break; - } - } - } - - private static boolean noExplicitDiscriminatorKeyOverride(final JsonNode discriminatorMapping, - final JsonSchema parentSchema) { - final Iterator> explicitMappings = discriminatorMapping.fields(); - while (explicitMappings.hasNext()) { - final Map.Entry candidateExplicitMapping = explicitMappings.next(); - if (candidateExplicitMapping.getValue().asText().equals(parentSchema.schemaPath)) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/com/networknt/schema/Collector.java b/src/main/java/com/networknt/schema/Collector.java deleted file mode 100644 index c8404e063..000000000 --- a/src/main/java/com/networknt/schema/Collector.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -/** - * Basic interface that allows the implementers to collect the information and - * return it. - * - * @param element - */ -public interface Collector { - - - /** - * This method should be called by the intermediate touch points that want to - * combine the data being collected by this collector. This is an optional - * method and could be used when the same collector is used for collecting data - * at multiple touch points or accumulating data at same touch point. - * @param object Object - */ - public void combine(Object object); - - /** - * Final method called by the framework that returns the actual collected data. - * If the collector is not accumulating data or being used to collect data at - * multiple touch points, only this method can be implemented. - * @return E element - */ - public E collect(); - - -} diff --git a/src/main/java/com/networknt/schema/CollectorContext.java b/src/main/java/com/networknt/schema/CollectorContext.java index b2b242f09..73bd1f33a 100644 --- a/src/main/java/com/networknt/schema/CollectorContext.java +++ b/src/main/java/com/networknt/schema/CollectorContext.java @@ -17,124 +17,86 @@ import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; +import java.util.function.Function; /** - * Context for holding the output returned by the {@link Collector} - * implementations. + * Context for holding data which can be set by custom walkers or validators and + * can be retrieved later from the execution context. */ public class CollectorContext { - - // Using a namespace string as key in ThreadLocal so that it is unique in - // ThreadLocal. - static final String COLLECTOR_CONTEXT_THREAD_LOCAL_KEY = "com.networknt.schema.CollectorKey"; - - // Get an instance from thread info (which uses ThreadLocal). - public static CollectorContext getInstance() { - return (CollectorContext) ThreadInfo.get(COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); - } - /** - * Map for holding the name and {@link Collector} or a simple Object. + * Map for the data. */ - private Map collectorMap = new HashMap(); + private final Map data; /** - * Map for holding the name and {@link Collector} class collect method output. + * Default constructor will use an unsynchronized HashMap to store data. This is + * suitable if the collector context is not shared with multiple threads. */ - private Map collectorLoadMap = new HashMap(); - - /** - * Adds a collector with give name. Preserving this method for backward - * compatibility. - * - * @param element - * @param name String - * @param collector Collector - */ - public void add(String name, Collector collector) { - collectorMap.put(name, collector); + public CollectorContext() { + this(new HashMap<>()); } - /** - * Adds a collector or a simple object with give name. - * - * @param element - * @param object Object - * @param name String - */ - public void add(String name, Object object) { - collectorMap.put(name, object); + /** + * Constructor that creates the context using the specified instances to store + * data. + *

+ * If for instance the collector context needs to be shared with multiple + * threads a ConcurrentHashMap can be used. + *

+ * It is however more likely that the data will only be used after the walk or + * validation is complete rather then during processing. + * + * @param data the data map + */ + public CollectorContext(Map data) { + this.data = data; } /** - * Gets the data associated with a given name. Please note if you are collecting - * {@link Collector} instances you should wait till the validation is complete - * to gather all data. - *

- * When {@link CollectorContext} is used to collect {@link Collector} instances - * for a particular key, this method will return the {@link Collector} instance - * as long as {@link #loadCollectors} method is not called. Once - * the {@link #loadCollectors} method is called this method will - * return the actual data collected by collector. + * Sets data associated with a given key. * - * @param name String - * @return Object + * @param the return type + * @param key the key + * @param value the value + * @return the previous value */ - public Object get(String name) { - Object object = collectorMap.get(name); - if (object instanceof Collector && (collectorLoadMap.get(name) != null)) { - return collectorLoadMap.get(name); - } - return collectorMap.get(name); + @SuppressWarnings("unchecked") + public T put(Object key, Object value) { + return (T) this.data.put(key, value); } /** - * Returns all the collected data. Please look into {@link #get(String)} method for more details. - * @return Map + * Gets the data associated with a given key. + * + * @param the return type + * @param key the key + * @return the value */ - public Map getAll() { - Map mergedMap = new HashMap(); - mergedMap.putAll(collectorMap); - mergedMap.putAll(collectorLoadMap); - return mergedMap; + @SuppressWarnings("unchecked") + public T get(Object key) { + return (T) this.data.get(key); } /** - * Combines data with Collector identified by the given name. + * Computes the value if absent. * - * @param name String - * @param data Object - */ - public void combineWithCollector(String name, Object data) { - Object object = collectorMap.get(name); - if (object instanceof Collector) { - Collector collector = (Collector) object; - collector.combine(data); - } - } - - /** - * Reset the context + * @param the return type + * @param key the key + * @param mappingFunction the mapping function + * @return the value */ - public void reset() { - this.collectorMap = new HashMap(); - this.collectorLoadMap = new HashMap(); + @SuppressWarnings("unchecked") + public T computeIfAbsent(Object key, Function mappingFunction) { + return (T) this.data.computeIfAbsent(key, mappingFunction); } /** - * Loads data from all collectors. + * Gets the data map. + * + * @return the data map */ - void loadCollectors() { - Set> entrySet = collectorMap.entrySet(); - for (Entry entry : entrySet) { - if (entry.getValue() instanceof Collector) { - Collector collector = (Collector) entry.getValue(); - collectorLoadMap.put(entry.getKey(), collector.collect()); - } - } - + public Map getData() { + return this.data; } - } diff --git a/src/main/java/com/networknt/schema/ConstValidator.java b/src/main/java/com/networknt/schema/ConstValidator.java deleted file mode 100644 index fd3f82552..000000000 --- a/src/main/java/com/networknt/schema/ConstValidator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -public class ConstValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class); - JsonNode schemaNode; - - public ConstValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext); - this.schemaNode = schemaNode; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - if (schemaNode.isNumber() && node.isNumber()) { - if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) { - errors.add(buildValidationMessage(at, schemaNode.asText())); - } - } else if (!schemaNode.equals(node)) { - errors.add(buildValidationMessage(at, schemaNode.asText())); - } - return Collections.unmodifiableSet(errors); - } -} diff --git a/src/main/java/com/networknt/schema/ContainsValidator.java b/src/main/java/com/networknt/schema/ContainsValidator.java deleted file mode 100644 index 97babf0e4..000000000 --- a/src/main/java/com/networknt/schema/ContainsValidator.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class ContainsValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(ContainsValidator.class); - - private final JsonSchema schema; - - public ContainsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.CONTAINS, validationContext); - if (schemaNode.isObject() || schemaNode.isBoolean()) { - schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); - } else { - schema = null; - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - - if (!node.isArray()) { - // ignores non-arrays - return Collections.emptySet(); - } - - // to support jackson < 2.10 - if (node.size() == 0) { - // Array was empty - return buildErrorMessageSet(at); - } else if (node.isArray()) { - int i = 0; - for (JsonNode n : node) { - if (schema.validate(n, rootNode, at + "[" + i + "]").isEmpty()) { - //Short circuit on first success - return Collections.emptySet(); - } - i++; - } - // None of the elements in the array satisfies the schema - return buildErrorMessageSet(at); - } - - return Collections.emptySet(); - } - - private Set buildErrorMessageSet(String at) { - return Collections.singleton(buildValidationMessage(at, schema.getSchemaNode().toString())); - } - - @Override - public void preloadJsonSchema() { - if (null != schema) { - schema.initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/CustomErrorMessageType.java b/src/main/java/com/networknt/schema/CustomErrorMessageType.java deleted file mode 100644 index 95e147c88..000000000 --- a/src/main/java/com/networknt/schema/CustomErrorMessageType.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.text.MessageFormat; - -public class CustomErrorMessageType implements ErrorMessageType { - private final String errorCode; - private final MessageFormat messageFormat; - - private CustomErrorMessageType(String errorCode, MessageFormat messageFormat) { - this.errorCode = errorCode; - this.messageFormat = messageFormat; - } - - public static ErrorMessageType of(String errorCode) { - return new CustomErrorMessageType(errorCode, null); - } - - public static ErrorMessageType of(String errorCode, MessageFormat messageFormat) { - return new CustomErrorMessageType(errorCode, messageFormat); - } - - @Override - public String getErrorCode() { - return errorCode; - } - - @Override - public MessageFormat getMessageFormat() { - return messageFormat; - } - -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/DateTimeValidator.java b/src/main/java/com/networknt/schema/DateTimeValidator.java deleted file mode 100644 index 69a082e3c..000000000 --- a/src/main/java/com/networknt/schema/DateTimeValidator.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.ethlo.time.ITU; -import com.ethlo.time.LeapSecondException; -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.LocalDate; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -public class DateTimeValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(DateTimeValidator.class); - - private final ValidationContext validationContext; - - private final String formatName; - private final String DATE = "date"; - private final String DATETIME = "date-time"; - - public DateTimeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DATETIME, validationContext); - this.formatName = formatName; - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return errors; - } - if (!isLegalDateTime(node.textValue())) { - errors.add(buildValidationMessage(at, node.textValue(), formatName)); - } - return Collections.unmodifiableSet(errors); - } - - private boolean isLegalDateTime(String string) { - if(formatName.equals(DATE)) { - return tryParse(() -> LocalDate.parse(string)); - } else if(formatName.equals(DATETIME)) { - return tryParse(() -> { - try { - ITU.parseDateTime(string); - } catch (LeapSecondException ex) { - if(!ex.isVerifiedValidLeapYearMonth()) { - throw ex; - } - } - }); - } else { - throw new IllegalStateException("Unknown format: " + formatName); - } - } - - private boolean tryParse(Runnable parser) { - try { - parser.run(); - return true; - } catch (Exception ex) { - logger.error("Invalid " + formatName + ": " + ex.getMessage()); - return false; - } - } -} diff --git a/src/main/java/com/networknt/schema/DependenciesValidator.java b/src/main/java/com/networknt/schema/DependenciesValidator.java deleted file mode 100644 index 4d1e0c852..000000000 --- a/src/main/java/com/networknt/schema/DependenciesValidator.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class DependenciesValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(DependenciesValidator.class); - private final Map> propertyDeps = new HashMap>(); - private final Map schemaDeps = new HashMap(); - - public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENCIES, validationContext); - - for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - JsonNode pvalue = schemaNode.get(pname); - if (pvalue.isArray()) { - List depsProps = propertyDeps.get(pname); - if (depsProps == null) { - depsProps = new ArrayList(); - propertyDeps.put(pname, depsProps); - } - for (int i = 0; i < pvalue.size(); i++) { - depsProps.add(pvalue.get(i).asText()); - } - } else if (pvalue.isObject() || pvalue.isBoolean()) { - schemaDeps.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema)); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (Iterator it = node.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - List deps = propertyDeps.get(pname); - if (deps != null && !deps.isEmpty()) { - for (String field : deps) { - if (node.get(field) == null) { - errors.add(buildValidationMessage(at, propertyDeps.toString())); - } - } - } - JsonSchema schema = schemaDeps.get(pname); - if (schema != null) { - errors.addAll(schema.validate(node, rootNode, at)); - } - } - - return Collections.unmodifiableSet(errors); - } - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemaDeps.values()); - } -} diff --git a/src/main/java/com/networknt/schema/DependentRequired.java b/src/main/java/com/networknt/schema/DependentRequired.java deleted file mode 100644 index d08870c88..000000000 --- a/src/main/java/com/networknt/schema/DependentRequired.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class DependentRequired extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(DependentRequired.class); - private final Map> propertyDependencies = new HashMap>(); - - public DependentRequired(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_REQUIRED, validationContext); - - for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - JsonNode pvalue = schemaNode.get(pname); - if (pvalue.isArray()) { - List dependencies = propertyDependencies.computeIfAbsent(pname, k -> new ArrayList<>()); - - for (int i = 0; i < pvalue.size(); i++) { - dependencies.add(pvalue.get(i).asText()); - } - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (Iterator it = node.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - List dependencies = propertyDependencies.get(pname); - if (dependencies != null && !dependencies.isEmpty()) { - for (String field : dependencies) { - if (node.get(field) == null) { - errors.add(buildValidationMessage(at, propertyDependencies.toString())); - } - } - } - } - - return Collections.unmodifiableSet(errors); - } - -} diff --git a/src/main/java/com/networknt/schema/DependentSchemas.java b/src/main/java/com/networknt/schema/DependentSchemas.java deleted file mode 100644 index accc2b161..000000000 --- a/src/main/java/com/networknt/schema/DependentSchemas.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class DependentSchemas extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(DependentSchemas.class); - private final Map schemaDependencies = new HashMap(); - - public DependentSchemas(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_SCHEMAS, validationContext); - - for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - JsonNode pvalue = schemaNode.get(pname); - if (pvalue.isObject() || pvalue.isBoolean()) { - schemaDependencies.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema)); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (Iterator it = node.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - JsonSchema schema = schemaDependencies.get(pname); - if (schema != null) { - errors.addAll(schema.validate(node, rootNode, at)); - } - } - - return Collections.unmodifiableSet(errors); - } - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemaDependencies.values()); - } -} diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java deleted file mode 100644 index dc24de97c..000000000 --- a/src/main/java/com/networknt/schema/EnumValidator.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.DecimalNode; -import com.fasterxml.jackson.databind.node.NullNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -public class EnumValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class); - - private final Set nodes; - private final String error; - private final ValidationContext validationContext; - - public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext); - this.validationContext = validationContext; - if (schemaNode != null && schemaNode.isArray()) { - nodes = new HashSet(); - StringBuilder sb = new StringBuilder(); - - sb.append('['); - String separator = ""; - - for (JsonNode n : schemaNode) { - if (n.isNumber()) { - // convert to DecimalNode for number comparison - nodes.add(DecimalNode.valueOf(n.decimalValue())); - } else { - nodes.add(n); - } - - sb.append(separator); - sb.append(n.asText()); - separator = ", "; - } - - // check if the parent schema declares the fields as nullable - if (validationContext.getConfig().isHandleNullableField()) { - JsonNode nullable = parentSchema.getSchemaNode().get("nullable"); - if (nullable != null && nullable.asBoolean()) { - nodes.add(NullNode.getInstance()); - separator = ", "; - sb.append(separator); - sb.append("null"); - } - } - // - sb.append(']'); - - error = sb.toString(); - } else { - nodes = Collections.emptySet(); - error = "[none]"; - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - if (node.isNumber()) node = DecimalNode.valueOf(node.decimalValue()); - if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) { - errors.add(buildValidationMessage(at, error)); - } - - return Collections.unmodifiableSet(errors); - } - - /** - * Check whether enum contains the value of the JsonNode if the typeLoose is enabled. - * - * @param node JsonNode to check - */ - private boolean isTypeLooseContainsInEnum(JsonNode node) { - if (TypeFactory.getValueNodeType(node, this.validationContext.getConfig()) == JsonType.STRING) { - String nodeText = node.textValue(); - for (JsonNode n : nodes) { - String value = n.asText(); - if (value != null && value.equals(nodeText)) { - return true; - } - } - } - return false; - } - -} diff --git a/src/main/java/com/networknt/schema/Error.java b/src/main/java/com/networknt/schema/Error.java new file mode 100644 index 000000000..6e117af76 --- /dev/null +++ b/src/main/java/com/networknt/schema/Error.java @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.networknt.schema.i18n.MessageFormatter; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.CachingSupplier; +import com.networknt.schema.utils.Strings; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Represents an error which could be when parsing a schema or when validating + * an instance. + * + * @see JSON + * Schema + */ +@JsonIgnoreProperties({ "messageSupplier", "schemaNode", "instanceNode", "valid" }) +@JsonPropertyOrder({ "keyword", "instanceLocation", "message", "evaluationPath", "schemaLocation", + "messageKey", "arguments", "details" }) +@JsonInclude(Include.NON_NULL) +public class Error { + private final String keyword; + @JsonSerialize(using = ToStringSerializer.class) + private final NodePath evaluationPath; + @JsonSerialize(using = ToStringSerializer.class) + private final SchemaLocation schemaLocation; + @JsonSerialize(using = ToStringSerializer.class) + private final NodePath instanceLocation; + private final Object[] arguments; + private final String messageKey; + private final Supplier messageSupplier; + private final Map details; + private final JsonNode instanceNode; + private final JsonNode schemaNode; + + Error(String keyword, NodePath evaluationPath, SchemaLocation schemaLocation, + NodePath instanceLocation, Object[] arguments, Map details, + String messageKey, Supplier messageSupplier, JsonNode instanceNode, JsonNode schemaNode) { + super(); + this.keyword = keyword; + this.instanceLocation = instanceLocation; + this.schemaLocation = schemaLocation; + this.evaluationPath = evaluationPath; + this.arguments = arguments; + this.details = details; + this.messageKey = messageKey; + this.messageSupplier = messageSupplier; + this.instanceNode = instanceNode; + this.schemaNode = schemaNode; + } + + /** + * The instance location is the location of the JSON value within the root + * instance being validated. + * + * @return The path to the input json + */ + public NodePath getInstanceLocation() { + return instanceLocation; + } + + /** + * The evaluation path is the set of keys, starting from the schema root, + * through which evaluation passes to reach the schema object that produced a + * specific result. + * + * @return the evaluation path + */ + public NodePath getEvaluationPath() { + return evaluationPath; + } + + /** + * The schema location is the canonical IRI of the schema object plus a JSON + * Pointer fragment indicating the subschema that produced a result. In contrast + * with the evaluation path, the schema location MUST NOT include by-reference + * applicators such as $ref or $dynamicRef. + * + * @return the schema location + */ + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + /** + * Returns the instance node which was evaluated. + *

+ * This corresponds with the instance location. + * + * @return the instance node + */ + public JsonNode getInstanceNode() { + return instanceNode; + } + + /** + * Returns the schema node which was evaluated. + *

+ * This corresponds with the schema location. + * + * @return the schema node + */ + public JsonNode getSchemaNode() { + return schemaNode; + } + + /** + * Returns the property with the error. + *

+ * For instance, for the required validator the instance location does not + * contain the missing property name as the instance must refer to the input + * data. + * + * @return the property name + */ + public String getProperty() { + if (details == null) { + return null; + } + return (String) getDetails().get("property"); + } + + public Integer getIndex() { + if (details == null) { + return null; + } + return (Integer) getDetails().get("index"); + } + + public Object[] getArguments() { + return arguments; + } + + public Map getDetails() { + return details; + } + + /** + * Gets the formatted error message. + * + * @return the error message + */ + public String getMessage() { + return messageSupplier.get(); + } + + public String getMessageKey() { + return messageKey; + } + + public boolean isValid() { + return messageSupplier != null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (instanceLocation != null) { + // Validation Error + builder.append(instanceLocation.toString()); + } else if (schemaLocation != null) { + // Parse Error + builder.append(schemaLocation.toString()); + } + builder.append(": "); + builder.append(messageSupplier.get()); + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Error that = (Error) o; + + if (keyword != null ? !keyword.equals(that.keyword) : that.keyword != null) return false; + if (instanceLocation != null ? !instanceLocation.equals(that.instanceLocation) : that.instanceLocation != null) return false; + if (evaluationPath != null ? !evaluationPath.equals(that.evaluationPath) : that.evaluationPath != null) return false; + if (details != null ? !details.equals(that.details) : that.details != null) return false; + if (messageKey != null ? !messageKey.equals(that.messageKey) : that.messageKey != null) return false; + return Arrays.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + int result = keyword != null ? keyword.hashCode() : 0; + result = 31 * result + (instanceLocation != null ? instanceLocation.hashCode() : 0); + result = 31 * result + (evaluationPath != null ? evaluationPath.hashCode() : 0); + result = 31 * result + (details != null ? details.hashCode() : 0); + result = 31 * result + (arguments != null ? Arrays.hashCode(arguments) : 0); + result = 31 * result + (messageKey != null ? messageKey.hashCode() : 0); + return result; + } + + public String getKeyword() { + return keyword; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends BuilderSupport { + @Override + public Builder self() { + return this; + } + } + + public static abstract class BuilderSupport { + public abstract S self(); + + protected String keyword; + protected NodePath evaluationPath; + protected SchemaLocation schemaLocation; + protected NodePath instanceLocation; + protected Object[] arguments; + protected Map details; + protected MessageFormat format; + protected String message; + protected Supplier messageSupplier; + protected MessageFormatter messageFormatter; + protected String messageKey; + protected JsonNode instanceNode; + protected JsonNode schemaNode; + + public S keyword(String keyword) { + this.keyword = keyword; + return self(); + } + + public S property(String properties) { + if (this.details == null) { + this.details = new HashMap<>(); + } + this.details.put("property", properties); + return self(); + } + + public S index(Integer index) { + if (this.details == null) { + this.details = new HashMap<>(); + } + this.details.put("index", index); + return self(); + } + + + /** + * The instance location is the location of the JSON value within the root + * instance being validated. + * + * @param instanceLocation the instance location + * @return the builder + */ + public S instanceLocation(NodePath instanceLocation) { + this.instanceLocation = instanceLocation; + return self(); + } + + /** + * The schema location is the canonical URI of the schema object plus a JSON + * Pointer fragment indicating the subschema that produced a result. In contrast + * with the evaluation path, the schema location MUST NOT include by-reference + * applicators such as $ref or $dynamicRef. + * + * @param schemaLocation the schema location + * @return the builder + */ + public S schemaLocation(SchemaLocation schemaLocation) { + this.schemaLocation = schemaLocation; + return self(); + } + + /** + * The evaluation path is the set of keys, starting from the schema root, + * through which evaluation passes to reach the schema object that produced a + * specific result. + * + * @param evaluationPath the evaluation path + * @return the builder + */ + public S evaluationPath(NodePath evaluationPath) { + this.evaluationPath = evaluationPath; + return self(); + } + + public S arguments(Object... arguments) { + this.arguments = arguments; + return self(); + } + + public S details(Map details) { + this.details = details; + return self(); + } + + public S format(MessageFormat format) { + this.format = format; + return self(); + } + + /** + * Explicitly sets the message pattern to be used. + *

+ * If set the message supplier and message formatter will be ignored. + * + * @param message the message pattern + * @return the builder + */ + public S message(String message) { + this.message = message; + return self(); + } + + public S messageSupplier(Supplier messageSupplier) { + this.messageSupplier = messageSupplier; + return self(); + } + + public S messageFormatter(MessageFormatter messageFormatter) { + this.messageFormatter = messageFormatter; + return self(); + } + + public S messageKey(String messageKey) { + this.messageKey = messageKey; + return self(); + } + + public S instanceNode(JsonNode instanceNode) { + this.instanceNode = instanceNode; + return self(); + } + + public S schemaNode(JsonNode schemaNode) { + this.schemaNode = schemaNode; + return self(); + } + + public Error build() { + Supplier messageSupplier = this.messageSupplier; + String messageKey = this.messageKey; + + if (!Strings.isBlank(this.message)) { + messageKey = this.message; + if (this.message.contains("{")) { + messageSupplier = new CachingSupplier<>(() -> { + MessageFormat format = new MessageFormat(this.message); + return format.format(getMessageArguments()); + }); + } else { + messageSupplier = message::toString; + } + } else if (messageSupplier == null) { + messageSupplier = new CachingSupplier<>(() -> { + MessageFormatter formatter = this.messageFormatter != null ? this.messageFormatter : format::format; + return formatter.format(getMessageArguments()); + }); + } + return new Error(keyword, evaluationPath, schemaLocation, instanceLocation, + arguments, details, messageKey, messageSupplier, this.instanceNode, this.schemaNode); + } + + protected Object[] getMessageArguments() { + return arguments; + } + + protected String getKeyword() { + return keyword; + } + + protected NodePath getEvaluationPath() { + return evaluationPath; + } + + protected SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + protected NodePath getInstanceLocation() { + return instanceLocation; + } + + protected Object[] getArguments() { + return arguments; + } + + protected Map getDetails() { + return details; + } + + protected MessageFormat getFormat() { + return format; + } + + protected String getMessage() { + return message; + } + + protected Supplier getMessageSupplier() { + return messageSupplier; + } + + protected MessageFormatter getMessageFormatter() { + return messageFormatter; + } + + protected String getMessageKey() { + return messageKey; + } + } +} diff --git a/src/main/java/com/networknt/schema/ErrorMessageType.java b/src/main/java/com/networknt/schema/ErrorMessageType.java deleted file mode 100644 index 9d3b9b201..000000000 --- a/src/main/java/com/networknt/schema/ErrorMessageType.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.text.MessageFormat; - -public interface ErrorMessageType { - /** - * Your error code. Please ensure global uniqueness. Builtin error codes are sequential numbers. - *

- * Customer error codes could have a prefix to denote the namespace of your custom keywords and errors. - * - * @return error code - */ - String getErrorCode(); - - /** - * optional message format - * - * @return the message format or null if no message text shall be rendered. - */ - MessageFormat getMessageFormat(); - - default String getCustomMessage() { - return null; - } - -} diff --git a/src/main/java/com/networknt/schema/ErrorMessages.java b/src/main/java/com/networknt/schema/ErrorMessages.java new file mode 100644 index 000000000..aaff9bf2a --- /dev/null +++ b/src/main/java/com/networknt/schema/ErrorMessages.java @@ -0,0 +1,58 @@ +package com.networknt.schema; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * ErrorMessages. + */ +public class ErrorMessages { + /** + * Gets the custom error message to use. + * + * @param parentSchema the parent schema + * @param errorMessageKeyword the error message keyword + * @param keyword the keyword + * @return the custom error message + */ + public static Map getErrorMessage(Schema parentSchema, String errorMessageKeyword, + String keyword) { + final JsonNode message = getMessageNode(errorMessageKeyword, parentSchema.schemaNode, parentSchema, keyword); + if (message != null) { + JsonNode messageNode = message.get(keyword); + if (messageNode != null) { + if (messageNode.isTextual()) { + return Collections.singletonMap("", messageNode.asText()); + } else if (messageNode.isObject()) { + Map result = new LinkedHashMap<>(); + messageNode.fields() + .forEachRemaining(entry -> result.put(entry.getKey(), entry.getValue().textValue())); + if (!result.isEmpty()) { + return result; + } + } + } + } + return Collections.emptyMap(); + } + + protected static JsonNode getMessageNode(String errorMessageKeyword, JsonNode schemaNode, Schema parentSchema, + String pname) { + if (schemaNode.get(errorMessageKeyword) != null && schemaNode.get(errorMessageKeyword).get(pname) != null) { + return schemaNode.get(errorMessageKeyword); + } + JsonNode messageNode; + messageNode = schemaNode.get(errorMessageKeyword); + if (messageNode == null && parentSchema != null) { + messageNode = parentSchema.schemaNode.get(errorMessageKeyword); + if (messageNode == null) { + return getMessageNode(errorMessageKeyword, parentSchema.schemaNode, parentSchema.getParentSchema(), + pname); + } + } + return messageNode; + } +} diff --git a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java deleted file mode 100644 index b9f7d4491..000000000 --- a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collections; -import java.util.Set; - -public class ExclusiveMaximumValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(ExclusiveMaximumValidator.class); - - private final ThresholdMixin typedMaximum; - - private final ValidationContext validationContext; - - public ExclusiveMaximumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MAXIMUM, validationContext); - this.validationContext = validationContext; - if (!schemaNode.isNumber()) { - throw new JsonSchemaException("exclusiveMaximum value is not a number"); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - - final String maximumText = schemaNode.asText(); - if ((schemaNode.isLong() || schemaNode.isInt()) && (JsonType.INTEGER.toString().equals(getNodeFieldType()))) { - // "integer", and within long range - final long lm = schemaNode.asLong(); - typedMaximum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (node.isBigInteger()) { - //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. - int compare = node.bigIntegerValue().compareTo(new BigInteger(schemaNode.asText())); - return compare > 0 || compare == 0; - - } else if (node.isTextual()) { - BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || compare == 0; - } - long val = node.asLong(); - return lm < val || lm == val; - } - - @Override - public String thresholdValue() { - return String.valueOf(lm); - } - }; - } else { - typedMaximum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - final BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || compare == 0; - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (!TypeValidator.isNumber(node, validationContext.getConfig())) { - // maximum only applies to numbers - return Collections.emptySet(); - } - - if (typedMaximum.crossesThreshold(node)) { - return Collections.singleton(buildValidationMessage(at, typedMaximum.thresholdValue())); - } - return Collections.emptySet(); - } -} diff --git a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java deleted file mode 100644 index 1444a2d4c..000000000 --- a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collections; -import java.util.Set; - -public class ExclusiveMinimumValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MinimumValidator.class); - - /** - * In order to limit number of `if` statements in `validate` method, all the - * logic of picking the right comparison is abstracted into a mixin. - */ - private final ThresholdMixin typedMinimum; - - private final ValidationContext validationContext; - - public ExclusiveMinimumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MINIMUM, validationContext); - this.validationContext = validationContext; - if (!schemaNode.isNumber()) { - throw new JsonSchemaException("exclusiveMinimum value is not a number"); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - - final String minimumText = schemaNode.asText(); - if ((schemaNode.isLong() || schemaNode.isInt()) && JsonType.INTEGER.toString().equals(getNodeFieldType())) { - // "integer", and within long range - final long lmin = schemaNode.asLong(); - typedMinimum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (node.isBigInteger()) { - //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. - int compare = node.bigIntegerValue().compareTo(new BigInteger(minimumText)); - return compare < 0 || compare == 0; - - } else if (node.isTextual()) { - BigDecimal min = new BigDecimal(minimumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(min); - return compare < 0 || compare == 0; - - } - long val = node.asLong(); - return lmin > val || lmin == val; - } - - @Override - public String thresholdValue() { - return String.valueOf(lmin); - } - }; - - } else { - typedMinimum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - // jackson's BIG_DECIMAL parsing is limited. see https://github.com/FasterXML/jackson-databind/issues/1770 - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - final BigDecimal min = new BigDecimal(minimumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(min); - return compare < 0 || compare == 0; - } - - @Override - public String thresholdValue() { - return minimumText; - } - }; - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { - // minimum only applies to numbers - return Collections.emptySet(); - } - - if (typedMinimum.crossesThreshold(node)) { - return Collections.singleton(buildValidationMessage(at, typedMinimum.thresholdValue())); - } - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/ExecutionConfig.java b/src/main/java/com/networknt/schema/ExecutionConfig.java new file mode 100644 index 000000000..0c84b1826 --- /dev/null +++ b/src/main/java/com/networknt/schema/ExecutionConfig.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.Locale; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Configuration per execution. + */ +public class ExecutionConfig { + private static class Holder { + private static final ExecutionConfig INSTANCE = ExecutionConfig.builder().build(); + } + + public static ExecutionConfig getInstance() { + return Holder.INSTANCE; + } + + /** + * The locale to use for formatting messages. + */ + private final Locale locale; + + /** + * Determines if annotation collection is enabled. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + */ + private final boolean annotationCollectionEnabled; + + /** + * If annotation collection is enabled, determine which annotations to collect. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + */ + private final Predicate annotationCollectionFilter; + + /** + * Since Draft 2019-09 format assertions are not enabled by default. + */ + private final Boolean formatAssertionsEnabled; + + /** + * Determine if the validation execution can fail fast. + */ + private final boolean failFast; + + /** + * When set to true assumes that schema is used to validate incoming data from + * an API. + */ + private final Boolean readOnly; + + /** + * When set to true assumes that schema is used to validate outgoing data from + * an API. + */ + private final Boolean writeOnly; + + protected ExecutionConfig(Locale locale, boolean annotationCollectionEnabled, + Predicate annotationCollectionFilter, Boolean formatAssertionsEnabled, boolean failFast, + Boolean readOnly, Boolean writeOnly) { + super(); + this.locale = locale; + this.annotationCollectionEnabled = annotationCollectionEnabled; + this.annotationCollectionFilter = annotationCollectionFilter; + this.formatAssertionsEnabled = formatAssertionsEnabled; + this.failFast = failFast; + this.readOnly = readOnly; + this.writeOnly = writeOnly; + } + + /** + * Gets the locale to use for formatting messages. + * + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * Gets the format assertion enabled flag. + *

+ * This defaults to null meaning that it will follow the defaults of the + * specification. + *

+ * Since draft 2019-09 this will default to false unless enabled by using the + * $vocabulary keyword. + * + * @return the format assertions enabled flag + */ + public Boolean getFormatAssertionsEnabled() { + return formatAssertionsEnabled; + } + + /** + * Return if fast fail is enabled. + * + * @return if fast fail is enabled + */ + public boolean isFailFast() { + return failFast; + } + + /** + * Return if annotation collection is enabled. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + *

+ * The annotations to collect can be customized using the annotation collection + * predicate. + * + * @return if annotation collection is enabled + */ + public boolean isAnnotationCollectionEnabled() { + return annotationCollectionEnabled; + } + + /** + * Gets the predicate to determine if annotation collection is allowed for a + * particular keyword. This only has an effect if annotation collection is + * enabled. + *

+ * The default value is to not collect any annotation keywords if annotation + * collection is enabled. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + * + * @return the predicate to determine if annotation collection is allowed for + * the keyword + */ + public Predicate getAnnotationCollectionFilter() { + return annotationCollectionFilter; + } + + /** + * Returns the value of the read only flag. + * + * @return the value of read only flag or null if not set + */ + public Boolean getReadOnly() { + return this.readOnly; + } + + /** + * Returns the value of the write only flag. + * + * @return the value of the write only flag or null if not set + */ + public Boolean getWriteOnly() { + return this.writeOnly; + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(ExecutionConfig config) { + Builder copy = new Builder(); + copy.locale = config.locale; + copy.annotationCollectionEnabled = config.annotationCollectionEnabled; + copy.annotationCollectionFilter = config.annotationCollectionFilter; + copy.formatAssertionsEnabled = config.formatAssertionsEnabled; + copy.failFast = config.failFast; + copy.readOnly = config.readOnly; + copy.writeOnly = config.writeOnly; + return copy; + } + + /** + * Builder for {@link ExecutionConfig}. + */ + public static class Builder extends BuilderSupport { + + @Override + protected Builder self() { + return this; + } + } + + /** + * Builder for {@link ExecutionConfig}. + */ + public static abstract class BuilderSupport { + protected Locale locale = Locale.ROOT; + protected boolean annotationCollectionEnabled = false; + protected Predicate annotationCollectionFilter = keyword -> false; + protected Boolean formatAssertionsEnabled = null; + protected boolean failFast = false; + protected Boolean readOnly = null; + protected Boolean writeOnly = null; + + protected abstract T self(); + + /** + * Sets the locale to use for formatting messages. + * + * @param locale the locale + * @return the builder + */ + public T locale(Locale locale) { + this.locale = locale; + return self(); + } + + /** + * Sets whether the annotation collection is enabled. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + *

+ * The annotations to collect can be customized using the annotation collection + * predicate. + * + * @param annotationCollectionEnabled true to enable annotation collection + * @return the builder + */ + public T annotationCollectionEnabled(boolean annotationCollectionEnabled) { + this.annotationCollectionEnabled = annotationCollectionEnabled; + return self(); + } + + /** + * Predicate to determine if annotation collection is allowed for a particular + * keyword. This only has an effect if annotation collection is enabled. + *

+ * The default value is to not collect any annotation keywords if annotation + * collection is enabled. + *

+ * This does not affect annotation collection required for evaluating keywords + * such as unevaluatedItems or unevaluatedProperties and only affects reporting. + * + * @param annotationCollectionFilter the predicate accepting the keyword + * @return the builder + */ + public T annotationCollectionFilter(Predicate annotationCollectionFilter) { + this.annotationCollectionFilter = annotationCollectionFilter; + return self(); + } + + /** + * Sets the format assertion enabled flag. + * + * @param formatAssertionsEnabled the format assertions enabled flag + * @return the builder + */ + public T formatAssertionsEnabled(Boolean formatAssertionsEnabled) { + this.formatAssertionsEnabled = formatAssertionsEnabled; + return self(); + } + + /** + * Sets whether fast fail is enabled. + * + * @param failFast true to fast fail + * @return the builder + */ + public T failFast(boolean failFast) { + this.failFast = failFast; + return self(); + } + + public T readOnly(Boolean readOnly) { + this.readOnly = readOnly; + return self(); + } + + public T writeOnly(Boolean writeOnly) { + this.writeOnly = writeOnly; + return self(); + } + + /** + * Builds the {@link ExecutionConfig}. + * + * @return the execution configuration + */ + public ExecutionConfig build() { + Locale locale = this.locale; + if (locale == null) { + locale = Locale.getDefault(); + } + Objects.requireNonNull(annotationCollectionFilter, "annotationCollectionFilter must not be null"); + return new ExecutionConfig(locale, annotationCollectionEnabled, annotationCollectionFilter, + formatAssertionsEnabled, failFast, readOnly, writeOnly); + } + } +} diff --git a/src/main/java/com/networknt/schema/ExecutionContext.java b/src/main/java/com/networknt/schema/ExecutionContext.java new file mode 100644 index 000000000..51037797f --- /dev/null +++ b/src/main/java/com/networknt/schema/ExecutionContext.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import com.networknt.schema.annotation.Annotations; +import com.networknt.schema.keyword.DiscriminatorState; +import com.networknt.schema.path.NodePath; +//import com.networknt.schema.result.InstanceResults; +import com.networknt.schema.walk.WalkConfig; + +/** + * Stores the execution context for the validation run. + */ +public class ExecutionContext { + private ExecutionConfig executionConfig; + private WalkConfig walkConfig = null; + private CollectorContext collectorContext = null; + + private Annotations annotations = null; +// private InstanceResults instanceResults = null; + private List errors = new ArrayList<>(); + + private final Map discriminatorMapping = new HashMap<>(); + + NodePath evaluationPath; + final ArrayDeque evaluationSchema = new ArrayDeque<>(64); + final ArrayDeque evaluationSchemaPath = new ArrayDeque<>(64); + + public NodePath getEvaluationPath() { + return evaluationPath; + } + + public void evaluationPathAddLast(String token) { + this.evaluationPath = evaluationPath.append(token); + } + + public void evaluationPathAddLast(int token) { + this.evaluationPath = evaluationPath.append(token); + } + + public void evaluationPathRemoveLast() { + this.evaluationPath = evaluationPath.getParent(); + } + + + public ArrayDeque getEvaluationSchema() { + return evaluationSchema; + } + + public ArrayDeque getEvaluationSchemaPath() { + return evaluationSchemaPath; + } + + public Map getDiscriminatorMapping() { + return discriminatorMapping; + } + + /** + * This is used during the execution to determine if the validator should fail fast. + *

+ * This valid is determined by the previous validator. + */ + private Boolean failFast = null; + + /** + * Creates an execution context. + */ + public ExecutionContext() { + this(ExecutionConfig.getInstance(), null); + } + + /** + * Creates an execution context. + * + * @param collectorContext the collector context + */ + public ExecutionContext(CollectorContext collectorContext) { + this(ExecutionConfig.getInstance(), collectorContext); + } + + /** + * Creates an execution context. + * + * @param executionConfig the execution configuration + */ + public ExecutionContext(ExecutionConfig executionConfig) { + this(executionConfig, null); + } + + /** + * Creates an execution context. + * + * @param executionConfig the execution configuration + * @param collectorContext the collector context + */ + public ExecutionContext(ExecutionConfig executionConfig, CollectorContext collectorContext) { + this.collectorContext = collectorContext; + this.executionConfig = executionConfig; + } + + /** + * Sets the walk configuration. + * + * @param walkConfig the walk configuration + */ + public void setWalkConfig(WalkConfig walkConfig) { + this.walkConfig = walkConfig; + } + + /** + * Gets the walk configuration. + * + * @return the walk configuration + */ + public WalkConfig getWalkConfig() { + if (this.walkConfig == null) { + this.walkConfig = WalkConfig.getInstance(); + } + return this.walkConfig; + } + + /** + * Gets the collector context. + * + * @return the collector context + */ + public CollectorContext getCollectorContext() { + if (this.collectorContext == null) { + this.collectorContext = new CollectorContext(); + } + return this.collectorContext; + } + + /** + * Sets the collector context. + * + * @param collectorContext the collector context + */ + public void setCollectorContext(CollectorContext collectorContext) { + this.collectorContext = collectorContext; + } + + /** + * Gets the execution configuration. + * + * @return the execution configuration + */ + public ExecutionConfig getExecutionConfig() { + return executionConfig; + } + + /** + * Sets the execution configuration. + * + * @param executionConfig the execution configuration + */ + public void setExecutionConfig(ExecutionConfig executionConfig) { + this.executionConfig = executionConfig; + } + + public Annotations getAnnotations() { + if (this.annotations == null) { + this.annotations = new Annotations(); + } + return annotations; + } + +// public InstanceResults getInstanceResults() { +// if (this.instanceResults == null) { +// this.instanceResults = new InstanceResults(); +// } +// return instanceResults; +// } + + /** + * Determines if the validator should immediately throw a fail fast exception if + * an error has occurred. + *

+ * This defaults to the execution config fail fast at the start of the execution. + * + * @return true if fail fast + */ + public boolean isFailFast() { + if (this.failFast == null) { + this.failFast = getExecutionConfig().isFailFast(); + } + return failFast; + } + + /** + * Sets if the validator should immediately throw a fail fast exception if an + * error has occurred. + * + * @param failFast true to fail fast + */ + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } + + public List getErrors() { + return this.errors; + } + + public void addError(Error error) { + this.errors.add(error); + if (this.isFailFast()) { + throw new FailFastAssertionException(error); + } + } + + public void setErrors(List errors) { + this.errors = errors; + } + + /** + * Customize the execution configuration. + * + * @param customizer the customizer + */ + public void executionConfig(Consumer customizer) { + ExecutionConfig.Builder builder = ExecutionConfig.builder(this.getExecutionConfig()); + customizer.accept(builder); + this.executionConfig = builder.build(); + } + + /** + * Customize the walk configuration. + * + * @param customizer the customizer + */ + public void walkConfig(Consumer customizer) { + WalkConfig.Builder builder = WalkConfig.builder(this.getWalkConfig()); + customizer.accept(builder); + this.walkConfig = builder.build(); + } + + boolean unevaluatedPropertiesPresent = false; + + boolean unevaluatedItemsPresent = false; + + public boolean isUnevaluatedPropertiesPresent() { + return this.unevaluatedPropertiesPresent; + } + + public boolean isUnevaluatedItemsPresent() { + return this.unevaluatedItemsPresent; + } + + public void setUnevaluatedPropertiesPresent(boolean set) { + this.unevaluatedPropertiesPresent = set; + } + + public void setUnevaluatedItemsPresent(boolean set) { + this.unevaluatedItemsPresent = set; + } +} diff --git a/src/main/java/com/networknt/schema/ExecutionContextCustomizer.java b/src/main/java/com/networknt/schema/ExecutionContextCustomizer.java new file mode 100644 index 000000000..e79e64e79 --- /dev/null +++ b/src/main/java/com/networknt/schema/ExecutionContextCustomizer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +/** + * Customize the execution context before validation. + */ +@FunctionalInterface +public interface ExecutionContextCustomizer { + /** + * Customize the execution context before validation. + *

+ * The schema context should only be used for reference as it is shared. + * + * @param executionContext the execution context + * @param schemaContext the schema context for reference + */ + void customize(ExecutionContext executionContext, SchemaContext schemaContext); +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/FailFastAssertionException.java b/src/main/java/com/networknt/schema/FailFastAssertionException.java new file mode 100644 index 000000000..0e033d28e --- /dev/null +++ b/src/main/java/com/networknt/schema/FailFastAssertionException.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Thrown when an assertion happens and the evaluation can fail fast. + *

+ * This doesn't extend off SchemaException as it is used for flow control + * and is intended to be caught in a specific place. + *

+ * This will be caught in the Schema validate method to be passed to the + * output formatter. + */ +public class FailFastAssertionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private final Error error; + + /** + * Constructor. + * + * @param error the validation message + */ + public FailFastAssertionException(Error error) { + this.error = Objects.requireNonNull(error); + } + + /** + * Gets the validation message. + * + * @return the validation message + */ + public Error getError() { + return this.error; + } + + /** + * Gets the validation message. + * + * @return the validation message + */ + public List getErrors() { + return Collections.singletonList(this.error); + } + + @Override + public String getMessage() { + return this.error != null ? this.error.getMessage() : super.getMessage(); + } + + @Override + public Throwable fillInStackTrace() { + /* + * This is overridden for performance as filling in the stack trace is expensive + * and this is used for flow control. + */ + return this; + } +} diff --git a/src/main/java/com/networknt/schema/FalseValidator.java b/src/main/java/com/networknt/schema/FalseValidator.java deleted file mode 100644 index a1bca6eb2..000000000 --- a/src/main/java/com/networknt/schema/FalseValidator.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -public class FalseValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(FalseValidator.class); - - public FalseValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FALSE, validationContext); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - // For the false validator, it is always not valid - Set errors = new LinkedHashSet(); - errors.add(buildValidationMessage(at)); - return Collections.unmodifiableSet(errors); - } -} diff --git a/src/main/java/com/networknt/schema/Format.java b/src/main/java/com/networknt/schema/Format.java deleted file mode 100644 index 43eb20d3f..000000000 --- a/src/main/java/com/networknt/schema/Format.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -public interface Format { - /** - * @return the format name as referred to in a json schema format node. - */ - String getName(); - - boolean matches(String value); - - String getErrorMessageDescription(); -} diff --git a/src/main/java/com/networknt/schema/FormatKeyword.java b/src/main/java/com/networknt/schema/FormatKeyword.java deleted file mode 100644 index 5c7793533..000000000 --- a/src/main/java/com/networknt/schema/FormatKeyword.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.format.EmailValidator; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -public class FormatKeyword implements Keyword { - private final ValidatorTypeCode type; - private final Map formats; - - private final String DATE = "date"; - private final String DATE_TIME = "date-time"; - private final String UUID = "uuid"; - private final String EMAIL = "email"; - - public FormatKeyword(ValidatorTypeCode type, Map formats) { - this.type = type; - this.formats = formats; - } - - Collection getFormats() { - return Collections.unmodifiableCollection(formats.values()); - } - - @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) - throws Exception { - Format format = null; - if (schemaNode != null && schemaNode.isTextual()) { - String formatName = schemaNode.textValue(); - format = formats.get(formatName); - // if you set custom format, override default Email/DateTime/UUID Validator - if (format != null) { - return new FormatValidator(schemaPath, schemaNode, parentSchema, validationContext, format); - } - // Validate date and time separately - if (formatName.equals(DATE) || formatName.equals(DATE_TIME)) { - return new DateTimeValidator(schemaPath, schemaNode, parentSchema, validationContext, formatName); - } else if (formatName.equals(UUID)) { - return new UUIDValidator(schemaPath, schemaNode, parentSchema, validationContext, formatName); - } else if (formatName.equals(EMAIL)) { - return new EmailValidator(schemaPath, schemaNode, parentSchema, validationContext, formatName); - } - } - return new FormatValidator(schemaPath, schemaNode, parentSchema, validationContext, format); - } - - @Override - public String getValue() { - return type.getValue(); - } - - @Override - public void setCustomMessage(String message) { - type.setCustomMessage(message); - } - - -} diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java deleted file mode 100644 index 8fec8e4c6..000000000 --- a/src/main/java/com/networknt/schema/FormatValidator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.regex.PatternSyntaxException; - -public class FormatValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(FormatValidator.class); - - private final Format format; - private final ValidationContext validationContext; - - public FormatValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, Format format) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT, validationContext); - this.format = format; - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return errors; - } - - if (format != null) { - if(format.getName().equals("ipv6")) { - if(!node.textValue().trim().equals(node.textValue())) { - // leading and trailing spaces - errors.add(buildValidationMessage(at, format.getName(), format.getErrorMessageDescription())); - } else if(node.textValue().contains("%")) { - // zone id is not part of the ipv6 - errors.add(buildValidationMessage(at, format.getName(), format.getErrorMessageDescription())); - } - } - try { - if (!format.matches(node.textValue())) { - errors.add(buildValidationMessage(at, format.getName(), format.getErrorMessageDescription())); - } - } catch (PatternSyntaxException pse) { - // String is considered valid if pattern is invalid - logger.error("Failed to apply pattern on " + at + ": Invalid RE syntax [" + format.getName() + "]", pse); - } - } - - return Collections.unmodifiableSet(errors); - } - -} diff --git a/src/main/java/com/networknt/schema/I18nSupport.java b/src/main/java/com/networknt/schema/I18nSupport.java deleted file mode 100644 index a6cb16217..000000000 --- a/src/main/java/com/networknt/schema/I18nSupport.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.networknt.schema; - -import java.util.ResourceBundle; - -/** - * Created by leaves chen leaves615@gmail.com on 2021/8/23. - * - * @author leaves chen leaves615@gmail.com - */ -public class I18nSupport { - private static final String BASE_NAME = "jsv-messages"; - private static ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME); - - public static String getString(String key) { - return bundle.getString(key); - } -} diff --git a/src/main/java/com/networknt/schema/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java deleted file mode 100644 index 908df067b..000000000 --- a/src/main/java/com/networknt/schema/IfValidator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class IfValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(IfValidator.class); - - private static final ArrayList KEYWORDS = new ArrayList(Arrays.asList("if", "then", "else")); - - private final JsonSchema ifSchema; - private final JsonSchema thenSchema; - private final JsonSchema elseSchema; - - public IfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.IF_THEN_ELSE, validationContext); - - JsonSchema foundIfSchema = null; - JsonSchema foundThenSchema = null; - JsonSchema foundElseSchema = null; - - for (final String keyword : KEYWORDS) { - final JsonNode node = schemaNode.get(keyword); - if (keyword.equals("if")) { - foundIfSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); - } else if (keyword.equals("then") && node != null) { - foundThenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); - } else if (keyword.equals("else") && node != null) { - foundElseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); - } - } - - ifSchema = foundIfSchema; - thenSchema = foundThenSchema; - elseSchema = foundElseSchema; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - boolean ifConditionPassed; - try { - ifConditionPassed = ifSchema.validate(node, rootNode, at).isEmpty(); - } catch (JsonSchemaException ex) { - // When failFast is enabled, validations are thrown as exceptions. - // An exception means the condition failed - ifConditionPassed = false; - } - - if (ifConditionPassed && thenSchema != null) { - errors.addAll(thenSchema.validate(node, rootNode, at)); - } else if (!ifConditionPassed && elseSchema != null) { - errors.addAll(elseSchema.validate(node, rootNode, at)); - } - - return Collections.unmodifiableSet(errors); - } - - @Override - public void preloadJsonSchema() { - if(null != ifSchema) { - ifSchema.initializeValidators(); - } - if(null != thenSchema) { - thenSchema.initializeValidators(); - } - if(null != elseSchema) { - elseSchema.initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/InputFormat.java b/src/main/java/com/networknt/schema/InputFormat.java new file mode 100644 index 000000000..f375dce49 --- /dev/null +++ b/src/main/java/com/networknt/schema/InputFormat.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +/** + * The input data format. + */ +public enum InputFormat { + /** + * JSON. + */ + JSON, + + /** + * YAML. + */ + YAML +} diff --git a/src/main/java/com/networknt/schema/InvalidSchemaException.java b/src/main/java/com/networknt/schema/InvalidSchemaException.java new file mode 100644 index 000000000..d575429e5 --- /dev/null +++ b/src/main/java/com/networknt/schema/InvalidSchemaException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.Objects; + +/** + * Thrown when an invalid schema is used. + */ +public class InvalidSchemaException extends SchemaException { + private static final long serialVersionUID = 1L; + + public InvalidSchemaException(Error message, Exception cause) { + super(Objects.requireNonNull(message)); + this.initCause(cause); + } + + public InvalidSchemaException(Error message) { + super(Objects.requireNonNull(message)); + } +} diff --git a/src/main/java/com/networknt/schema/InvalidSchemaRefException.java b/src/main/java/com/networknt/schema/InvalidSchemaRefException.java new file mode 100644 index 000000000..adb655718 --- /dev/null +++ b/src/main/java/com/networknt/schema/InvalidSchemaRefException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +/** + * Thrown when an invalid schema ref is used. + */ +public class InvalidSchemaRefException extends InvalidSchemaException { + private static final long serialVersionUID = 1L; + + public InvalidSchemaRefException(Error message, Exception cause) { + super(message, cause); + } + + public InvalidSchemaRefException(Error message) { + super(message); + } +} diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java deleted file mode 100644 index 0785efe18..000000000 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.networknt.schema.walk.DefaultItemWalkListenerRunner; -import com.networknt.schema.walk.WalkListenerRunner; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class ItemsValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(ItemsValidator.class); - private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems"; - - private final JsonSchema schema; - private final List tupleSchema; - private boolean additionalItems = true; - private final JsonSchema additionalSchema; - private WalkListenerRunner arrayItemWalkListenerRunner; - private final ValidationContext validationContext; - - public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext); - tupleSchema = new ArrayList(); - JsonSchema foundSchema = null; - JsonSchema foundAdditionalSchema = null; - - if (schemaNode.isObject() || schemaNode.isBoolean()) { - foundSchema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, - parentSchema); - } else { - for (JsonNode s : schemaNode) { - tupleSchema.add( - new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema)); - } - - JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS); - if (addItemNode != null) { - if (addItemNode.isBoolean()) { - additionalItems = addItemNode.asBoolean(); - } else if (addItemNode.isObject()) { - foundAdditionalSchema = new JsonSchema(validationContext, "#", parentSchema.getCurrentUri(), addItemNode, parentSchema); - } - } - } - arrayItemWalkListenerRunner = new DefaultItemWalkListenerRunner(validationContext.getConfig().getArrayItemWalkListeners()); - - this.validationContext = validationContext; - - parseErrorCode(getValidatorType().getErrorCodeKey()); - - this.schema = foundSchema; - this.additionalSchema = foundAdditionalSchema; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - if (!node.isArray() && !this.validationContext.getConfig().isTypeLoose()) { - // ignores non-arrays - return errors; - } - if (node.isArray()) { - int i = 0; - for (JsonNode n : node) { - doValidate(errors, i, n, rootNode, at); - i++; - } - } else { - doValidate(errors, 0, node, rootNode, at); - } - return Collections.unmodifiableSet(errors); - } - - private void doValidate(Set errors, int i, JsonNode node, JsonNode rootNode, String at) { - if (schema != null) { - // validate with item schema (the whole array has the same item - // schema) - errors.addAll(schema.validate(node, rootNode, at + "[" + i + "]")); - } - - if (tupleSchema != null) { - if (i < tupleSchema.size()) { - // validate against tuple schema - errors.addAll(tupleSchema.get(i).validate(node, rootNode, at + "[" + i + "]")); - } else { - if (additionalSchema != null) { - // validate against additional item schema - errors.addAll(additionalSchema.validate(node, rootNode, at + "[" + i + "]")); - } else if (!additionalItems) { - // no additional item allowed, return error - errors.add(buildValidationMessage(at, "" + i)); - } - } - } - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - if (node instanceof ArrayNode) { - ArrayNode arrayNode = (ArrayNode) node; - JsonNode defaultNode = null; - if (applyDefaultsStrategy.shouldApplyArrayDefaults() && schema != null) { - defaultNode = schema.getSchemaNode().get("default"); - if (defaultNode != null && defaultNode.isNull()) { - defaultNode = null; - } - } - int i = 0; - for (JsonNode n : arrayNode) { - if (n.isNull() && defaultNode != null) { - arrayNode.set(i, defaultNode); - n = defaultNode; - } - doWalk(validationMessages, i, n, rootNode, at, shouldValidateSchema); - i++; - } - } else { - doWalk(validationMessages, 0, node, rootNode, at, shouldValidateSchema); - } - return validationMessages; - } - - private void doWalk(HashSet validationMessages, int i, JsonNode node, JsonNode rootNode, - String at, boolean shouldValidateSchema) { - if (schema != null) { - // Walk the schema. - walkSchema(schema, node, rootNode, at + "[" + i + "]", shouldValidateSchema, validationMessages); - } - - if (tupleSchema != null) { - if (i < tupleSchema.size()) { - // walk tuple schema - walkSchema(tupleSchema.get(i), node, rootNode, at + "[" + i + "]", shouldValidateSchema, - validationMessages); - } else { - if (additionalSchema != null) { - // walk additional item schema - walkSchema(additionalSchema, node, rootNode, at + "[" + i + "]", shouldValidateSchema, - validationMessages); - } - } - } - } - - private void walkSchema(JsonSchema walkSchema, JsonNode node, JsonNode rootNode, String at, - boolean shouldValidateSchema, Set validationMessages) { - boolean executeWalk = arrayItemWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.ITEMS.getValue(), node, - rootNode, at, walkSchema.getSchemaPath(), walkSchema.getSchemaNode(), walkSchema.getParentSchema(), - validationContext, validationContext.getJsonSchemaFactory()); - if (executeWalk) { - validationMessages.addAll(walkSchema.walk(node, rootNode, at, shouldValidateSchema)); - } - arrayItemWalkListenerRunner.runPostWalkListeners(ValidatorTypeCode.ITEMS.getValue(), node, rootNode, at, - walkSchema.getSchemaPath(), walkSchema.getSchemaNode(), walkSchema.getParentSchema(), - validationContext, validationContext.getJsonSchemaFactory(), validationMessages); - - } - - public List getTupleSchema() { - return this.tupleSchema; - } - - public JsonSchema getSchema() { - return schema; - } - - @Override - public void preloadJsonSchema() { - if (null != schema) { - schema.initializeValidators(); - } - preloadJsonSchemas(tupleSchema); - if (null != additionalSchema) { - additionalSchema.initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java deleted file mode 100644 index c4903885d..000000000 --- a/src/main/java/com/networknt/schema/JsonMetaSchema.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class JsonMetaSchema { - - - private static final Logger logger = LoggerFactory - .getLogger(JsonMetaSchema.class); - private static Map UNKNOWN_KEYWORDS = new ConcurrentHashMap(); - - static PatternFormat pattern(String name, String regex) { - return new PatternFormat(name, regex); - } - - public static final List COMMON_BUILTIN_FORMATS = new ArrayList(); - - // this section contains formats that is common for all specification versions. - static { - COMMON_BUILTIN_FORMATS.add(pattern("time", "^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?$")); - COMMON_BUILTIN_FORMATS.add(pattern("ip-address", - "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")); - COMMON_BUILTIN_FORMATS.add(pattern("ipv4", "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$")); - COMMON_BUILTIN_FORMATS.add(pattern("ipv6", - "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$")); - - // From RFC 3986 - // ALPHA [A-Za-z] - // DIGIT [0-9] - // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - // => [A-Za-z][A-Za-z0-9+.-]* - // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - // => [A-Za-z0-9._~\-] - // gen-delims [:/?#\[\]@] - // sub-delims [!$&'()*+,;=] - // reserved = = gen-delims / sub-delims - // => [:/?#\[\]@!$&'()*+,;=] - // pct-encoded = "%" HEXDIG HEXDIG - // => [A-Za-z0-9%] (approximation) - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - // => [A-Za-z0-9._~\-%!$&'()*+,;=:@] - // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) - // => [A-Za-z0-9._~\-%!$&'()*+,;=:]* - // host = IP-literal / IPv4address / reg-name - // => [A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]* (approximation) - // port = *DIGiT - // => [0-9]* - // authority = [ userinfo "@" ] host [ ":" port ] - // => ([A-Za-z0-9._~\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]*(:[0-9]*)? - // hier-part = "//" authority path-abempty - // / path-absolute - // / path-rootless - // / path-empty - // => (\/\/([A-Za-z0-9._~\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\-!$&'()*+,;=%:\[\]]*(:[0-9]*)?)?[A-Za-z0-9._~\-%!$&'()*+,;=:@\/]* (approximation) - // query = *( pchar / "/" / "?" ) - // => [A-Za-z0-9._~\-%!$&'()*+,;=:@\/?]* - // fragment = *( pchar / "/" / "?" ) - // => [A-Za-z0-9._~\-%!$&'()*+,;=:@\/?]* - // uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - COMMON_BUILTIN_FORMATS.add(pattern("uri", - "^[A-Za-z][A-Za-z0-9+.-]*:(\\/\\/([A-Za-z0-9._~\\-%!$&'()*+,;=:]*@)?[A-Za-z0-9._~\\-!$&'()*+,;=%:\\[\\]]*(:[0-9]*)?)?[A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/]*([?][A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/?]*)?([#][A-Za-z0-9._~\\-%!$&'()*+,;=:@\\/?]*)?")); - COMMON_BUILTIN_FORMATS.add(pattern("color", - "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))")); - COMMON_BUILTIN_FORMATS.add(pattern("hostname", - "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$")); - COMMON_BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$")); - COMMON_BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$")); - COMMON_BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); - COMMON_BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$")); - COMMON_BUILTIN_FORMATS.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?")); - } - - private static class V4 { - private static String URI = "https://json-schema.org/draft-04/schema"; - private static final String ID = "id"; - - public static final List BUILTIN_FORMATS = new ArrayList(JsonMetaSchema.COMMON_BUILTIN_FORMATS); - - static { - // add version specific formats here. - //BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); - } - - public static JsonMetaSchema getInstance() { - return new Builder(URI) - .idKeyword(ID) - .addFormats(BUILTIN_FORMATS) - .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V4)) - // keywords that may validly exist, but have no validation aspect to them - .addKeywords(Arrays.asList( - new NonValidationKeyword("$schema"), - new NonValidationKeyword("id"), - new NonValidationKeyword("title"), - new NonValidationKeyword("description"), - new NonValidationKeyword("default"), - new NonValidationKeyword("definitions"), - new NonValidationKeyword("exampleSetFlag") - )) - .build(); - } - } - - private static class V6 { - private static String URI = "https://json-schema.org/draft-06/schema"; - // Draft 6 uses "$id" - private static final String ID = "$id"; - - public static final List BUILTIN_FORMATS = new ArrayList(JsonMetaSchema.COMMON_BUILTIN_FORMATS); - - static { - // add version specific formats here. - //BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); - } - - public static JsonMetaSchema getInstance() { - return new Builder(URI) - .idKeyword(ID) - .addFormats(BUILTIN_FORMATS) - .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V6)) - // keywords that may validly exist, but have no validation aspect to them - .addKeywords(Arrays.asList( - new NonValidationKeyword("$schema"), - new NonValidationKeyword("$id"), - new NonValidationKeyword("title"), - new NonValidationKeyword("description"), - new NonValidationKeyword("default"), - new NonValidationKeyword("definitions") - )) - .build(); - } - } - - private static class V7 { - private static String URI = "https://json-schema.org/draft-07/schema"; - private static final String ID = "$id"; - - public static final List BUILTIN_FORMATS = new ArrayList(JsonMetaSchema.COMMON_BUILTIN_FORMATS); - - static { - // add version specific formats here. - //BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); - } - - public static JsonMetaSchema getInstance() { - return new Builder(URI) - .idKeyword(ID) - .addFormats(BUILTIN_FORMATS) - .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V7)) - // keywords that may validly exist, but have no validation aspect to them - .addKeywords(Arrays.asList( - new NonValidationKeyword("$schema"), - new NonValidationKeyword("$id"), - new NonValidationKeyword("title"), - new NonValidationKeyword("description"), - new NonValidationKeyword("default"), - new NonValidationKeyword("definitions"), - new NonValidationKeyword("$comment"), - new NonValidationKeyword("contentMediaType"), - new NonValidationKeyword("contentEncoding"), - new NonValidationKeyword("examples"), - new NonValidationKeyword("message") - )) - .build(); - } - } - - private static class V201909 { - private static String URI = "https://json-schema.org/draft/2019-09/schema"; - private static final String ID = "$id"; - - public static final List BUILTIN_FORMATS = new ArrayList(JsonMetaSchema.COMMON_BUILTIN_FORMATS); - - static { - // add version specific formats here. - //BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); - } - - public static JsonMetaSchema getInstance() { - return new Builder(URI) - .idKeyword(ID) - .addFormats(BUILTIN_FORMATS) - .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V201909)) - // keywords that may validly exist, but have no validation aspect to them - .addKeywords(Arrays.asList( - new NonValidationKeyword("$schema"), - new NonValidationKeyword("$id"), - new NonValidationKeyword("title"), - new NonValidationKeyword("description"), - new NonValidationKeyword("default"), - new NonValidationKeyword("definitions"), - new NonValidationKeyword("$comment"), - new NonValidationKeyword("$defs"), // newly added in 2019-09 release. - new NonValidationKeyword("$anchor"), - new NonValidationKeyword("additionalItems"), - new NonValidationKeyword("deprecated"), - new NonValidationKeyword("contentMediaType"), - new NonValidationKeyword("contentEncoding"), - new NonValidationKeyword("examples"), - new NonValidationKeyword("then") - )) - .build(); - } - } - - public static class Builder { - private Map keywords = new HashMap(); - private Map formats = new HashMap(); - private String uri; - private String idKeyword = "id"; - - public Builder(String uri) { - this.uri = uri; - } - - private static Map createKeywordsMap(Map kwords, Map formats) { - final Map map = new HashMap(); - for (Map.Entry type : kwords.entrySet()) { - String keywordName = type.getKey(); - Keyword keyword = type.getValue(); - if (ValidatorTypeCode.FORMAT.getValue().equals(keywordName)) { - if (!(keyword instanceof FormatKeyword)) { - throw new IllegalArgumentException("Overriding the keyword 'format' is not supported"); - } - // ignore - format keyword will be created again below. - } else { - map.put(keyword.getValue(), keyword); - } - } - final FormatKeyword formatKeyword = new FormatKeyword(ValidatorTypeCode.FORMAT, formats); - map.put(formatKeyword.getValue(), formatKeyword); - return Collections.unmodifiableMap(map); - } - - public Builder addKeyword(Keyword keyword) { - this.keywords.put(keyword.getValue(), keyword); - return this; - } - - public Builder addKeywords(Collection keywords) { - for (Keyword keyword : keywords) { - this.keywords.put(keyword.getValue(), keyword); - } - return this; - } - - public Builder addFormat(Format format) { - this.formats.put(format.getName(), format); - return this; - } - - public Builder addFormats(Collection formats) { - for (Format format : formats) { - addFormat(format); - } - return this; - } - - - public Builder idKeyword(String idKeyword) { - this.idKeyword = idKeyword; - return this; - } - - public JsonMetaSchema build() { - // create builtin keywords with (custom) formats. - final Map kwords = createKeywordsMap(keywords, formats); - return new JsonMetaSchema(uri, idKeyword, kwords); - } - } - - private final String uri; - private final String idKeyword; - private final Map keywords; - - private JsonMetaSchema(String uri, String idKeyword, Map keywords) { - if (StringUtils.isBlank(uri)) { - throw new IllegalArgumentException("uri must not be null or blank"); - } - if (StringUtils.isBlank(idKeyword)) { - throw new IllegalArgumentException("idKeyword must not be null or blank"); - } - if (keywords == null) { - throw new IllegalArgumentException("keywords must not be null "); - } - - this.uri = uri; - this.idKeyword = idKeyword; - this.keywords = keywords; - } - - public static JsonMetaSchema getV4() { - return V4.getInstance(); - } - - public static JsonMetaSchema getV6() { - return V6.getInstance(); - } - - public static JsonMetaSchema getV7() { - return V7.getInstance(); - } - - public static JsonMetaSchema getV201909() { - return V201909.getInstance(); - } - - /** - * Builder without keywords or formats. - *

- * Use {@link #getV4()} for the Draft 4 Metaschema, or if you need a builder based on Draft4, use - * - * - * JsonMetaSchema.builder("http://your-metaschema-uri", JsonSchemaFactory.getDraftV4()).build(); - * - * - * @param uri the URI of the metaschema that will be defined via this builder. - * @return a builder instance without any keywords or formats - usually not what one needs. - */ - public static Builder builder(String uri) { - return new Builder(uri); - } - - /** - * @param uri the URI of your new JsonMetaSchema that will be defined via this builder. - * @param blueprint the JsonMetaSchema to base your custom JsonMetaSchema on. - * @return a builder instance preconfigured to be the same as blueprint, but with a different uri. - */ - public static Builder builder(String uri, JsonMetaSchema blueprint) { - FormatKeyword formatKeyword = (FormatKeyword) blueprint.keywords.get(ValidatorTypeCode.FORMAT.getValue()); - if (formatKeyword == null) { - throw new IllegalArgumentException("The formatKeyword did not exist - blueprint is invalid."); - } - return builder(uri) - .idKeyword(blueprint.idKeyword) - .addKeywords(blueprint.keywords.values()) - .addFormats(formatKeyword.getFormats()); - } - - public String readId(JsonNode schemaNode) { - return readText(schemaNode, idKeyword); - } - - public JsonNode getNodeByFragmentRef(String ref, JsonNode node) { - boolean supportsAnchor = keywords.containsKey("$anchor"); - String refName = supportsAnchor ? ref.substring(1) : ref; - String fieldToRead = supportsAnchor ? "$anchor" : idKeyword; - - boolean nodeContainsRef = refName.equals(readText(node, fieldToRead)); - if (nodeContainsRef) { - return node; - } else { - Iterator children = node.elements(); - while (children.hasNext()) { - JsonNode refNode = getNodeByFragmentRef(ref, children.next()); - if (refNode != null) { - return refNode; - } - } - } - return null; - } - - private String readText(JsonNode node, String field) { - JsonNode idNode = node.get(field); - if (idNode == null || !idNode.isTextual()) { - return null; - } - return idNode.textValue(); - } - - public String getUri() { - return uri; - } - - - public JsonValidator newValidator(ValidationContext validationContext, String schemaPath, String keyword /* keyword */, JsonNode schemaNode, - JsonSchema parentSchema, String customMessage) { - - try { - Keyword kw = keywords.get(keyword); - if (kw == null) { - if (UNKNOWN_KEYWORDS.put(keyword, keyword) == null) { - logger.warn("Unknown keyword " + keyword + " - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword"); - } - return null; - } - kw.setCustomMessage(customMessage); - return kw.newValidator(schemaPath, schemaNode, parentSchema, validationContext); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof JsonSchemaException) { - logger.error("Error:", e); - throw (JsonSchemaException) e.getTargetException(); - } else { - logger.warn("Could not load validator " + keyword); - throw new JsonSchemaException(e.getTargetException()); - } - } catch (JsonSchemaException e) { - throw e; - } catch (Exception e) { - logger.warn("Could not load validator " + keyword); - throw new JsonSchemaException(e); - } - } - - -} diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java deleted file mode 100644 index c45e6286c..000000000 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.schema.ValidationContext.DiscriminatorContext; -import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner; -import com.networknt.schema.walk.JsonSchemaWalker; -import com.networknt.schema.walk.WalkListenerRunner; - -/** - * This is the core of json constraint implementation. It parses json constraint - * file and generates JsonValidators. The class is thread safe, once it is - * constructed, it can be used to validate multiple json data concurrently. - */ -public class JsonSchema extends BaseJsonValidator { - private static final Pattern intPattern = Pattern.compile("^[0-9]+$"); - private Map validators; - private final ValidationContext validationContext; - private final JsonMetaSchema metaSchema; - private boolean validatorsLoaded = false; - - /** - * This is the current uri of this schema. This uri could refer to the uri of this schema's file - * or it could potentially be a uri that has been altered by an id. An 'id' is able to completely overwrite - * the current uri or add onto it. This is necessary so that '$ref's are able to be relative to a - * combination of the current schema file's uri and 'id' uris visible to this schema. - *

- * This can be null. If it is null, then the creation of relative uris will fail. However, an absolute - * 'id' would still be able to specify an absolute uri. - */ - private final URI currentUri; - - private JsonValidator requiredValidator = null; - - public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode) { - this(validationContext, "#", baseUri, schemaNode, null); - } - - public JsonSchema(ValidationContext validationContext, String schemaPath, URI currentUri, JsonNode schemaNode, - JsonSchema parent) { - this(validationContext, schemaPath, currentUri, schemaNode, parent, false); - } - - public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) { - this(validationContext, "#", baseUri, schemaNode, null, suppressSubSchemaRetrieval); - } - - private JsonSchema(ValidationContext validationContext, String schemaPath, URI currentUri, JsonNode schemaNode, - JsonSchema parent, boolean suppressSubSchemaRetrieval) { - super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval, - validationContext.getConfig() != null && validationContext.getConfig().isFailFast(), - validationContext.getConfig() != null ? validationContext.getConfig().getApplyDefaultsStrategy() : null); - this.validationContext = validationContext; - this.metaSchema = validationContext.getMetaSchema(); - this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode); - if (validationContext.getConfig() != null) { - if (validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { - ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator"); - if (null != discriminator && null != validationContext.getCurrentDiscriminatorContext()) { - validationContext.getCurrentDiscriminatorContext().registerDiscriminator(schemaPath, discriminator); - } - } - } - } - - ValidationContext getValidationContext() { - return this.validationContext; - } - - private URI combineCurrentUriWithIds(URI currentUri, JsonNode schemaNode) { - final String id = validationContext.resolveSchemaId(schemaNode); - if (id == null) { - return currentUri; - } else if (isUriFragmentWithNoContext(currentUri, id)) { - return null; - } else { - try { - return this.validationContext.getURIFactory().create(currentUri, id); - } catch (IllegalArgumentException e) { - throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.ID.getValue(), - ValidatorTypeCode.ID, - id, - currentUri == null ? "null" : currentUri.toString())); - } - } - } - - private boolean isUriFragmentWithNoContext(URI currentUri, String id) { - return id.startsWith("#") && currentUri == null; - } - - public URI getCurrentUri() { - return this.currentUri; - } - - /** - * Find the schema node for $ref attribute. - * - * @param ref String - * @return JsonNode - */ - public JsonNode getRefSchemaNode(String ref) { - JsonSchema schema = findAncestor(); - JsonNode node = schema.getSchemaNode(); - - if (ref.startsWith("#/")) { - // handle local ref - String[] keys = ref.substring(2).split("/"); - for (String key : keys) { - try { - key = URLDecoder.decode(key, "utf-8"); - } catch (UnsupportedEncodingException e) { - } - Matcher matcher = intPattern.matcher(key); - if (matcher.matches()) { - node = node.get(Integer.parseInt(key)); - } else { - node = node.get(key); - } - if (node == null) { - node = handleNullNode(ref, schema); - } - if (node == null) { - break; - } - } - } else if (ref.startsWith("#") && ref.length() > 1) { - node = metaSchema.getNodeByFragmentRef(ref, node); - if (node == null) { - node = handleNullNode(ref, schema); - } - } - return node; - } - - public JsonSchema findAncestor() { - JsonSchema ancestor = this; - if (this.getParentSchema() != null) { - ancestor = this.getParentSchema().findAncestor(); - } - return ancestor; - } - - private JsonNode handleNullNode(String ref, JsonSchema schema) { - JsonSchema subSchema = schema.fetchSubSchemaNode(validationContext); - if (subSchema != null) { - return subSchema.getRefSchemaNode(ref); - } - return null; - } - - /** - * Please note that the key in {@link #validators} map is a schema path. It is - * used in {@link com.networknt.schema.walk.DefaultKeywordWalkListenerRunner} to derive the keyword. - */ - private Map read(JsonNode schemaNode) { - Map validators = new TreeMap<>(VALIDATOR_SORT); - if (schemaNode.isBoolean()) { - if (schemaNode.booleanValue()) { - final String customMessage = getCustomMessage(schemaNode, "true"); - JsonValidator validator = validationContext.newValidator(getSchemaPath(), "true", schemaNode, this, customMessage); - validators.put(getSchemaPath() + "/true", validator); - } else { - final String customMessage = getCustomMessage(schemaNode, "false"); - JsonValidator validator = validationContext.newValidator(getSchemaPath(), "false", schemaNode, this, customMessage); - validators.put(getSchemaPath() + "/false", validator); - } - } else { - Iterator pnames = schemaNode.fieldNames(); - while (pnames.hasNext()) { - String pname = pnames.next(); - JsonNode nodeToUse = pname.equals("if") ? schemaNode : schemaNode.get(pname); - String customMessage = getCustomMessage(schemaNode, pname); - JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, nodeToUse, this, customMessage); - if (validator != null) { - validators.put(getSchemaPath() + "/" + pname, validator); - - if (pname.equals("required")) { - requiredValidator = validator; - } - } - - } - } - return validators; - } - - /** - * A comparator that sorts validators, such such that 'properties' comes before 'required', - * so that we can apply default values before validating required. - */ - private static Comparator VALIDATOR_SORT = (lhs, rhs) -> { - if (lhs.endsWith("/properties")) { - return -1; - } - if (rhs.endsWith("/properties")) { - return 1; - } - return lhs.compareTo(rhs); - }; - - private String getCustomMessage(JsonNode schemaNode, String pname) { - final JsonSchema parentSchema = getParentSchema(); - final JsonNode message = getMessageNode(schemaNode, parentSchema); - if (message != null && message.get(pname) != null) { - return message.get(pname).asText(); - } - return null; - } - - private JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema) { - JsonNode nodeContainingMessage; - if (parentSchema == null) - nodeContainingMessage = schemaNode; - else - nodeContainingMessage = parentSchema.schemaNode; - return nodeContainingMessage.get("message"); - } - - /************************ START OF VALIDATE METHODS **********************************/ - - public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { - SchemaValidatorsConfig config = validationContext.getConfig(); - Set errors = new LinkedHashSet(); - // Get the collector context. - getCollectorContext(); - // Set the walkEnabled and isValidationEnabled flag in internal validator state. - setValidatorState(false, true); - for (JsonValidator v : getValidators().values()) { - // Validate. - errors.addAll(v.validate(jsonNode, rootNode, at)); - } - if (null != config && config.isOpenAPI3StyleDiscriminators()) { - ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator"); - if (null != discriminator) { - final DiscriminatorContext discriminatorContext = validationContext.getCurrentDiscriminatorContext(); - if (null != discriminatorContext) { - final ObjectNode discriminatorToUse; - final ObjectNode discriminatorFromContext = discriminatorContext.getDiscriminatorForPath(schemaPath); - if (null == discriminatorFromContext) { - // register the current discriminator. This can only happen when the current context discriminator - // was not registered via allOf. In that case we have a $ref to the schema with discriminator that gets - // used for validation before allOf validation has kicked in - discriminatorContext.registerDiscriminator(schemaPath, discriminator); - discriminatorToUse = discriminator; - } else { - discriminatorToUse = discriminatorFromContext; - } - - final String discriminatorPropertyName = discriminatorToUse.get("propertyName").asText(); - final JsonNode discriminatorNode = jsonNode.get(discriminatorPropertyName); - final String discriminatorPropertyValue = discriminatorNode == null ? null : discriminatorNode.asText(); - checkDiscriminatorMatch(discriminatorContext, - discriminatorToUse, - discriminatorPropertyValue, - this); - } - } - } - return errors; - } - - public ValidationResult validateAndCollect(JsonNode node) { - return validateAndCollect(node, node, AT_ROOT); - } - - /** - * This method both validates and collects the data in a CollectorContext. - * Unlike others this methods cleans and removes everything from collector - * context before returning. - * - * @param jsonNode JsonNode - * @param rootNode JsonNode - * @param at String path - * @return ValidationResult - */ - protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) { - try { - // Get the config. - SchemaValidatorsConfig config = validationContext.getConfig(); - // Get the collector context from the thread local. - CollectorContext collectorContext = getCollectorContext(); - // Validate. - Set errors = validate(jsonNode, rootNode, at); - // When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors. - if (config.doLoadCollectors()) { - // Load all the data from collectors into the context. - collectorContext.loadCollectors(); - } - // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, collectorContext); - return validationResult; - } finally { - ThreadInfo.remove(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); - } - } - - /************************ END OF VALIDATE METHODS **********************************/ - - /************************ START OF WALK METHODS **********************************/ - - /** - * Walk the JSON node - * - * @param node JsonNode - * @param shouldValidateSchema indicator on validation - * @return result of ValidationResult - */ - public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { - // Get the config. - SchemaValidatorsConfig config = validationContext.getConfig(); - // Get the collector context from the thread local. - CollectorContext collectorContext = getCollectorContext(); - // Set the walkEnabled flag in internal validator state. - setValidatorState(true, shouldValidateSchema); - // Walk through the schema. - Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); - // When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors. - if (config.doLoadCollectors()) { - // Load all the data from collectors into the context. - collectorContext.loadCollectors(); - } - // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, collectorContext); - return validationResult; - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(this.validationContext.getConfig().getKeywordWalkListenersMap()); - // Walk through all the JSONWalker's. - for (Entry entry : getValidators().entrySet()) { - JsonSchemaWalker jsonWalker = entry.getValue(); - String schemaPathWithKeyword = entry.getKey(); - try { - // Call all the pre-walk listeners. If at least one of the pre walk listeners - // returns SKIP, then skip the walk. - if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, - node, - rootNode, - at, - schemaPath, - schemaNode, - parentSchema, - validationContext, - validationContext.getJsonSchemaFactory())) { - validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); - } - } finally { - // Call all the post-walk listeners. - keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, - node, - rootNode, - at, - schemaPath, - schemaNode, - parentSchema, - validationContext, - validationContext.getJsonSchemaFactory(), - validationMessages); - } - } - return validationMessages; - } - - /************************ END OF WALK METHODS **********************************/ - - private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSchema) { - // Get the Validator state object storing validation data - Object stateObj = CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - // if one has not been created, instantiate one - if (stateObj == null) { - ValidatorState state = new ValidatorState(); - state.setWalkEnabled(isWalkEnabled); - state.setValidationEnabled(shouldValidateSchema); - CollectorContext.getInstance().add(ValidatorState.VALIDATOR_STATE_KEY, state); - } - } - - public CollectorContext getCollectorContext() { - SchemaValidatorsConfig config = validationContext.getConfig(); - CollectorContext collectorContext = (CollectorContext) ThreadInfo - .get(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); - if (collectorContext == null) { - if (config != null && config.getCollectorContext() != null) { - collectorContext = config.getCollectorContext(); - } else { - collectorContext = new CollectorContext(); - } - // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); - } - return collectorContext; - } - - @Override - public String toString() { - return "\"" + getSchemaPath() + "\" : " + getSchemaNode().toString(); - } - - public boolean hasRequiredValidator() { - return requiredValidator != null; - } - - public JsonValidator getRequiredValidator() { - return requiredValidator; - } - - public Map getValidators() { - if (validators == null) { - validators = Collections.unmodifiableMap(this.read(getSchemaNode())); - } - return validators; - } - - /** - * Initializes the validators' {@link com.networknt.schema.JsonSchema} instances. - * For avoiding issues with concurrency, in 1.0.49 the {@link com.networknt.schema.JsonSchema} instances affiliated with - * validators were modified to no more preload the schema and lazy loading is used instead. - *

This comes with the issue that this way you cannot rely on validating important schema features, in particular - * $ref resolution at instantiation from {@link com.networknt.schema.JsonSchemaFactory}.

- *

By calling initializeValidators you can enforce preloading of the {@link com.networknt.schema.JsonSchema} - * instances of the validators.

- */ - public void initializeValidators() { - if (!validatorsLoaded) { - validatorsLoaded = true; - for (final JsonValidator validator : getValidators().values()) { - validator.preloadJsonSchema(); - } - } - } -} diff --git a/src/main/java/com/networknt/schema/JsonSchemaException.java b/src/main/java/com/networknt/schema/JsonSchemaException.java deleted file mode 100644 index a95d1463e..000000000 --- a/src/main/java/com/networknt/schema/JsonSchemaException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.Collections; -import java.util.Set; - -public class JsonSchemaException extends RuntimeException { - private static final long serialVersionUID = -7805792737596582110L; - private ValidationMessage validationMessage; - - public JsonSchemaException(ValidationMessage validationMessage) { - super(validationMessage.getMessage()); - this.validationMessage = validationMessage; - } - - public JsonSchemaException(String message) { - super(message); - } - - public JsonSchemaException(Throwable throwable) { - super(throwable); - } - - public Set getValidationMessages() { - if (validationMessage == null) { - return Collections.emptySet(); - } - return Collections.singleton(validationMessage); - } -} diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java deleted file mode 100644 index aa278f152..000000000 --- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.uri.*; -import com.networknt.schema.urn.URNFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -public class JsonSchemaFactory { - private static final Logger logger = LoggerFactory - .getLogger(JsonSchemaFactory.class); - - - public static class Builder { - private ObjectMapper objectMapper = new ObjectMapper(); - private String defaultMetaSchemaURI; - private final Map uriFactoryMap = new HashMap(); - private final Map uriFetcherMap = new HashMap(); - private URNFactory urnFactory; - private final Map jsonMetaSchemas = new HashMap(); - private final Map uriMap = new HashMap(); - private boolean forceHttps = true; - private boolean removeEmptyFragmentSuffix = true; - - public Builder() { - // Adds support for creating {@link URL}s. - final URIFactory urlFactory = new URLFactory(); - for (final String scheme : URLFactory.SUPPORTED_SCHEMES) { - this.uriFactoryMap.put(scheme, urlFactory); - } - - // Adds support for fetching with {@link URL}s. - final URIFetcher urlFetcher = new URLFetcher(); - for (final String scheme : URLFetcher.SUPPORTED_SCHEMES) { - this.uriFetcherMap.put(scheme, urlFetcher); - } - - // Adds support for creating and fetching with classpath {@link URL}s. - final URIFactory classpathURLFactory = new ClasspathURLFactory(); - final URIFetcher classpathURLFetcher = new ClasspathURLFetcher(); - for (final String scheme : ClasspathURLFactory.SUPPORTED_SCHEMES) { - this.uriFactoryMap.put(scheme, classpathURLFactory); - this.uriFetcherMap.put(scheme, classpathURLFetcher); - } - } - - public Builder objectMapper(final ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - return this; - } - - public Builder defaultMetaSchemaURI(final String defaultMetaSchemaURI) { - this.defaultMetaSchemaURI = defaultMetaSchemaURI; - return this; - } - - /** - * Maps a number of schemes to a {@link URIFactory}. - * - * @param uriFactory the uri factory that will be used for the given schemes. - * @param schemes the scheme that the uri factory will be assocaited with. - * @return this builder. - */ - public Builder uriFactory(final URIFactory uriFactory, final String... schemes) { - return uriFactory(uriFactory, Arrays.asList(schemes)); - } - - public Builder uriFactory(final URIFactory uriFactory, final Iterable schemes) { - for (final String scheme : schemes) { - this.uriFactoryMap.put(scheme, uriFactory); - } - return this; - } - - /** - * Maps a number of schemes to a {@link URIFetcher}. - * - * @param uriFetcher the uri fetcher that will be used for the given schemes. - * @param schemes the scheme that the uri fetcher will be assocaited with. - * @return this builder. - */ - public Builder uriFetcher(final URIFetcher uriFetcher, final String... schemes) { - return uriFetcher(uriFetcher, Arrays.asList(schemes)); - } - - public Builder uriFetcher(final URIFetcher uriFetcher, final Iterable schemes) { - for (final String scheme : schemes) { - this.uriFetcherMap.put(scheme, uriFetcher); - } - return this; - } - - public Builder addMetaSchema(final JsonMetaSchema jsonMetaSchema) { - this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema); - return this; - } - - public Builder addMetaSchemas(final Collection jsonMetaSchemas) { - for (JsonMetaSchema jsonMetaSchema : jsonMetaSchemas) { - this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema); - } - return this; - } - - public Builder addUriMappings(final Map map) { - this.uriMap.putAll(map); - return this; - } - - public Builder addUrnFactory(URNFactory urnFactory) { - this.urnFactory = urnFactory; - return this; - } - - public Builder forceHttps(boolean forceHttps) { - this.forceHttps = forceHttps; - return this; - } - - public Builder removeEmptyFragmentSuffix(boolean removeEmptyFragmentSuffix) { - this.removeEmptyFragmentSuffix = removeEmptyFragmentSuffix; - return this; - } - - public JsonSchemaFactory build() { - // create builtin keywords with (custom) formats. - return new JsonSchemaFactory( - objectMapper == null ? new ObjectMapper() : objectMapper, - defaultMetaSchemaURI, - new URISchemeFactory(uriFactoryMap), - new URISchemeFetcher(uriFetcherMap), - urnFactory, - jsonMetaSchemas, - uriMap, - forceHttps, - removeEmptyFragmentSuffix - ); - } - } - - private final ObjectMapper mapper; - private final String defaultMetaSchemaURI; - private final URISchemeFactory uriFactory; - private final URISchemeFetcher uriFetcher; - private final URNFactory urnFactory; - private final Map jsonMetaSchemas; - private final Map uriMap; - private final ConcurrentMap uriSchemaCache = new ConcurrentHashMap(); - private final boolean forceHttps; - private final boolean removeEmptyFragmentSuffix; - - - private JsonSchemaFactory( - final ObjectMapper mapper, - final String defaultMetaSchemaURI, - final URISchemeFactory uriFactory, - final URISchemeFetcher uriFetcher, - final URNFactory urnFactory, - final Map jsonMetaSchemas, - final Map uriMap, - final boolean forceHttps, - final boolean removeEmptyFragmentSuffix) { - if (mapper == null) { - throw new IllegalArgumentException("ObjectMapper must not be null"); - } else if (defaultMetaSchemaURI == null || defaultMetaSchemaURI.trim().isEmpty()) { - throw new IllegalArgumentException("defaultMetaSchemaURI must not be null or empty"); - } else if (uriFactory == null) { - throw new IllegalArgumentException("URIFactory must not be null"); - } else if (uriFetcher == null) { - throw new IllegalArgumentException("URIFetcher must not be null"); - } else if (jsonMetaSchemas == null || jsonMetaSchemas.isEmpty()) { - throw new IllegalArgumentException("Json Meta Schemas must not be null or empty"); - } else if (jsonMetaSchemas.get(defaultMetaSchemaURI) == null) { - throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided"); - } else if (uriMap == null) { - throw new IllegalArgumentException("URL Mappings must not be null"); - } - this.mapper = mapper; - this.defaultMetaSchemaURI = defaultMetaSchemaURI; - this.uriFactory = uriFactory; - this.uriFetcher = uriFetcher; - this.urnFactory = urnFactory; - this.jsonMetaSchemas = jsonMetaSchemas; - this.uriMap = uriMap; - this.forceHttps = forceHttps; - this.removeEmptyFragmentSuffix = removeEmptyFragmentSuffix; - } - - /** - * Builder without keywords or formats. - * - * - * JsonSchemaFactory.builder(JsonSchemaFactory.getDraftV4()).build(); - * - * - * @return a builder instance without any keywords or formats - usually not what one needs. - */ - static Builder builder() { - return new Builder(); - } - - /** - * @deprecated - * This is a method that is kept to ensure backward compatible. You shouldn't use it anymore. - * Please specify the draft version when get an instance. - * - * @return JsonSchemaFactory - */ - @Deprecated - public static JsonSchemaFactory getInstance() { - return getInstance(SpecVersion.VersionFlag.V4); - } - - public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag) { - JsonMetaSchema metaSchema = null; - switch (versionFlag) { - case V201909: - metaSchema = JsonMetaSchema.getV201909(); - break; - case V7: - metaSchema = JsonMetaSchema.getV7(); - break; - case V6: - metaSchema = JsonMetaSchema.getV6(); - break; - case V4: - metaSchema = JsonMetaSchema.getV4(); - break; - } - return builder() - .defaultMetaSchemaURI(metaSchema.getUri()) - .addMetaSchema(metaSchema) - .build(); - } - - public static Builder builder(final JsonSchemaFactory blueprint) { - Builder builder = builder() - .addMetaSchemas(blueprint.jsonMetaSchemas.values()) - .defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI) - .objectMapper(blueprint.mapper) - .addUriMappings(blueprint.uriMap); - - for (Map.Entry entry : blueprint.uriFactory.getURIFactories().entrySet()) { - builder = builder.uriFactory(entry.getValue(), entry.getKey()); - } - for (Map.Entry entry : blueprint.uriFetcher.getURIFetchers().entrySet()) { - builder = builder.uriFetcher(entry.getValue(), entry.getKey()); - } - return builder; - } - - protected JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) { - final ValidationContext validationContext = createValidationContext(schemaNode); - validationContext.setConfig(config); - return new JsonSchema(validationContext, schemaUri, schemaNode); - } - - protected ValidationContext createValidationContext(final JsonNode schemaNode) { - final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode); - return new ValidationContext(this.uriFactory, this.urnFactory, jsonMetaSchema, this, null); - } - - private JsonMetaSchema findMetaSchemaForSchema(final JsonNode schemaNode) { - final JsonNode uriNode = schemaNode.get("$schema"); - if (uriNode != null && !uriNode.isNull() && !uriNode.isTextual()) { - throw new JsonSchemaException("Unknown MetaSchema: " + uriNode.toString()); - } - final String uri = uriNode == null || uriNode.isNull() ? defaultMetaSchemaURI : normalizeMetaSchemaUri(uriNode.textValue(), forceHttps, removeEmptyFragmentSuffix); - final JsonMetaSchema jsonMetaSchema = jsonMetaSchemas.get(uri); - if (jsonMetaSchema == null) { - throw new JsonSchemaException("Unknown MetaSchema: " + uri); - } - return jsonMetaSchema; - } - - /** - * @return A shared {@link URI} factory that is used for creating the URI references in schemas. - */ - public URIFactory getUriFactory() { - return this.uriFactory; - } - - public JsonSchema getSchema(final String schema, final SchemaValidatorsConfig config) { - try { - final JsonNode schemaNode = mapper.readTree(schema); - return newJsonSchema(null, schemaNode, config); - } catch (IOException ioe) { - logger.error("Failed to load json schema!", ioe); - throw new JsonSchemaException(ioe); - } - } - - public JsonSchema getSchema(final String schema) { - return getSchema(schema, null); - } - - public JsonSchema getSchema(final InputStream schemaStream, final SchemaValidatorsConfig config) { - try { - final JsonNode schemaNode = mapper.readTree(schemaStream); - return newJsonSchema(null, schemaNode, config); - } catch (IOException ioe) { - logger.error("Failed to load json schema!", ioe); - throw new JsonSchemaException(ioe); - } - } - - public JsonSchema getSchema(final InputStream schemaStream) { - return getSchema(schemaStream, null); - } - - public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig config) { - try { - InputStream inputStream = null; - final Map map = (config != null) ? config.getUriMappings() : new HashMap(); - map.putAll(uriMap); - - final URI mappedUri; - try { - mappedUri = this.uriFactory.create(map.get(schemaUri.toString()) != null ? map.get(schemaUri.toString()) : schemaUri.toString()); - } catch (IllegalArgumentException e) { - logger.error("Failed to create URI.", e); - throw new JsonSchemaException(e); - } - - if (uriSchemaCache.containsKey(mappedUri)) { - JsonSchema cachedUriSchema = uriSchemaCache.get(mappedUri); - // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, - // these schemas will be cached along with config. We have to replace the config for cached $ref references - // with the latest config. - cachedUriSchema.getValidationContext().setConfig(config); - return cachedUriSchema; - } - - try { - inputStream = this.uriFetcher.fetch(mappedUri); - final JsonNode schemaNode = mapper.readTree(inputStream); - final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode); - - JsonSchema jsonSchema; - if (idMatchesSourceUri(jsonMetaSchema, schemaNode, schemaUri)) { - jsonSchema = new JsonSchema( - new ValidationContext(this.uriFactory, this.urnFactory, jsonMetaSchema, this, config), - mappedUri, schemaNode, true /* retrieved via id, resolving will not change anything */); - } else { - final ValidationContext validationContext = createValidationContext(schemaNode); - validationContext.setConfig(config); - jsonSchema = new JsonSchema(validationContext, mappedUri, schemaNode); - } - uriSchemaCache.put(mappedUri, jsonSchema); - - return jsonSchema; - } finally { - if (inputStream != null) { - inputStream.close(); - } - } - } catch (IOException ioe) { - logger.error("Failed to load json schema!", ioe); - throw new JsonSchemaException(ioe); - } - } - - public JsonSchema getSchema(final URI schemaUri) { - return getSchema(schemaUri, new SchemaValidatorsConfig()); - } - - public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode, final SchemaValidatorsConfig config) { - return newJsonSchema(schemaUri, jsonNode, config); - } - - - public JsonSchema getSchema(final JsonNode jsonNode, final SchemaValidatorsConfig config) { - return newJsonSchema(null, jsonNode, config); - } - - public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode) { - return newJsonSchema(schemaUri, jsonNode, null); - } - - public JsonSchema getSchema(final JsonNode jsonNode) { - return newJsonSchema(null, jsonNode, null); - } - - private boolean idMatchesSourceUri(final JsonMetaSchema metaSchema, final JsonNode schema, final URI schemaUri) { - String id = metaSchema.readId(schema); - if (id == null || id.isEmpty()) { - return false; - } - boolean result = id.equals(schemaUri.toString()); - if (logger.isDebugEnabled()) { - logger.debug("Matching " + id + " to " + schemaUri.toString() + ": " + result); - } - return result; - } - - static protected String normalizeMetaSchemaUri(String u, boolean forceHttps, boolean removeEmptyFragmentSuffix) { - try { - URI uri = new URI(u); - String scheme = forceHttps ? "https" : uri.getScheme(); - URI newUri = new URI(scheme, uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); - - if (!removeEmptyFragmentSuffix && u.endsWith("#")) { - return newUri + "#"; - } else { - return newUri.toString(); - } - } catch (URISyntaxException e) { - throw new JsonSchemaException("Wrong MetaSchema URI: " + u); - } - } -} diff --git a/src/main/java/com/networknt/schema/JsonSchemaRef.java b/src/main/java/com/networknt/schema/JsonSchemaRef.java deleted file mode 100644 index 8acedf432..000000000 --- a/src/main/java/com/networknt/schema/JsonSchemaRef.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Set; - -/** - * Use this object instead a JsonSchema for references. - *

- * This reference may be empty (if the reference is being parsed) or with data (after the reference has been parsed), - * helping to prevent recursive reference to cause an infinite loop. - */ - -public class JsonSchemaRef { - - private final JsonSchema schema; - - public JsonSchemaRef(JsonSchema schema) { - this.schema = schema; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - return schema.validate(node, rootNode, at); - } - - public JsonSchema getSchema() { - return schema; - } - - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - return schema.walk(node, rootNode, at, shouldValidateSchema); - } -} diff --git a/src/main/java/com/networknt/schema/JsonType.java b/src/main/java/com/networknt/schema/JsonType.java deleted file mode 100644 index 4e7a0ecd7..000000000 --- a/src/main/java/com/networknt/schema/JsonType.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -public enum JsonType { - OBJECT("object"), - ARRAY("array"), - STRING("string"), - NUMBER("number"), - INTEGER("integer"), - BOOLEAN("boolean"), - NULL("null"), - ANY("any"), - - UNKNOWN("unknown"), - UNION("union"); - - private String type; - - private JsonType(String typeStr) { - type = typeStr; - } - - public String toString() { - return type; - } - -} diff --git a/src/main/java/com/networknt/schema/JsonValidator.java b/src/main/java/com/networknt/schema/JsonValidator.java deleted file mode 100644 index dafc27926..000000000 --- a/src/main/java/com/networknt/schema/JsonValidator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.Set; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.walk.JsonSchemaWalker; - -/** - * Standard json validator interface, implemented by all validators and JsonSchema. - */ -public interface JsonValidator extends JsonSchemaWalker { - String AT_ROOT = "$"; - - /** - * Validate the given root JsonNode, starting at the root of the data path. - * - * @param rootNode JsonNode - * @return A list of ValidationMessage if there is any validation error, or an empty - * list if there is no error. - */ - Set validate(JsonNode rootNode); - - /** - * Validate the given JsonNode, the given node is the child node of the root node at given - * data path. - * - * @param node JsonNode - * @param rootNode JsonNode - * @param at String - * @return A list of ValidationMessage if there is any validation error, or an empty - * list if there is no error. - */ - Set validate(JsonNode node, JsonNode rootNode, String at); - - /** - * In case the {@link com.networknt.schema.JsonValidator} has a related {@link com.networknt.schema.JsonSchema} or several - * ones, calling preloadJsonSchema will actually load the schema document(s) eagerly. - * - * @throws JsonSchemaException (a {@link java.lang.RuntimeException}) in case the {@link com.networknt.schema.JsonSchema} or nested schemas - * are invalid (like $ref not resolving) - * @since 1.0.54 - */ - default void preloadJsonSchema() throws JsonSchemaException { - // do nothing by default - to be overridden in subclasses - } -} diff --git a/src/main/java/com/networknt/schema/Keyword.java b/src/main/java/com/networknt/schema/Keyword.java deleted file mode 100644 index 0f9283b19..000000000 --- a/src/main/java/com/networknt/schema/Keyword.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - - -import com.fasterxml.jackson.databind.JsonNode; - -public interface Keyword { - String getValue(); - - default void setCustomMessage(String message) { - //setCustom message - } - - JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception; -} diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java deleted file mode 100644 index eef6f24f2..000000000 --- a/src/main/java/com/networknt/schema/MaxItemsValidator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator { - - private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class); - - private final ValidationContext validationContext; - - private int max = 0; - - public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext); - if (schemaNode.canConvertToExactIntegral()) { - max = schemaNode.intValue(); - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (node.isArray()) { - if (node.size() > max) { - return Collections.singleton(buildValidationMessage(at, "" + max)); - } - } else if (this.validationContext.getConfig().isTypeLoose()) { - if (1 > max) { - return Collections.singleton(buildValidationMessage(at, "" + max)); - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MaxLengthValidator.java b/src/main/java/com/networknt/schema/MaxLengthValidator.java deleted file mode 100644 index f0f0ead7f..000000000 --- a/src/main/java/com/networknt/schema/MaxLengthValidator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MaxLengthValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MaxLengthValidator.class); - - private final ValidationContext validationContext; - - private int maxLength; - - public MaxLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_LENGTH, validationContext); - maxLength = Integer.MAX_VALUE; - if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { - maxLength = schemaNode.intValue(); - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - // ignore no-string typs - return Collections.emptySet(); - } - if (node.textValue().codePointCount(0, node.textValue().length()) > maxLength) { - return Collections.singleton(buildValidationMessage(at, "" + maxLength)); - } - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java deleted file mode 100644 index b1363a19e..000000000 --- a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MaxPropertiesValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MaxPropertiesValidator.class); - - private int max; - - public MaxPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES, validationContext); - if (schemaNode.canConvertToExactIntegral()) { - max = schemaNode.intValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (node.isObject()) { - if (node.size() > max) { - return Collections.singleton(buildValidationMessage(at, "" + max)); - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java deleted file mode 100644 index 9bd3a3438..000000000 --- a/src/main/java/com/networknt/schema/MaximumValidator.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collections; -import java.util.Set; - -public class MaximumValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MaximumValidator.class); - private static final String PROPERTY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum"; - - private boolean excludeEqual = false; - - private final ThresholdMixin typedMaximum; - private final ValidationContext validationContext; - - - public MaximumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext); - this.validationContext = validationContext; - if (!schemaNode.isNumber()) { - throw new JsonSchemaException("maximum value is not a number"); - } - - JsonNode exclusiveMaximumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MAXIMUM); - if (exclusiveMaximumNode != null && exclusiveMaximumNode.isBoolean()) { - excludeEqual = exclusiveMaximumNode.booleanValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - - final String maximumText = schemaNode.asText(); - if ((schemaNode.isLong() || schemaNode.isInt()) && (JsonType.INTEGER.toString().equals(getNodeFieldType()))) { - // "integer", and within long range - final long lm = schemaNode.asLong(); - typedMaximum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (node.isBigInteger()) { - //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. - int compare = node.bigIntegerValue().compareTo(new BigInteger(schemaNode.asText())); - return compare > 0 || (excludeEqual && compare == 0); - - } else if (node.isTextual()) { - BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - long val = node.asLong(); - return lm < val || (excludeEqual && lm == val); - } - - @Override - public String thresholdValue() { - return String.valueOf(lm); - } - }; - } else { - typedMaximum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - final BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { - // maximum only applies to numbers - return Collections.emptySet(); - } - - if (typedMaximum.crossesThreshold(node)) { - return Collections.singleton(buildValidationMessage(at, typedMaximum.thresholdValue())); - } - return Collections.emptySet(); - } -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/MessageSourceError.java b/src/main/java/com/networknt/schema/MessageSourceError.java new file mode 100644 index 000000000..eb8ca652a --- /dev/null +++ b/src/main/java/com/networknt/schema/MessageSourceError.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.Locale; +import java.util.Map; + +import com.networknt.schema.i18n.MessageSource; + +/** + * MessageSourceError. + */ +public class MessageSourceError { + + public static Builder builder(MessageSource messageSource, Map errorMessage) { + return new Builder(messageSource, errorMessage); + } + + public static class Builder extends BuilderSupport { + public Builder(MessageSource messageSource, Map errorMessage) { + super(messageSource, errorMessage); + } + + @Override + public Builder self() { + return this; + } + } + + public abstract static class BuilderSupport extends Error.BuilderSupport { + private final MessageSource messageSource; + private final Map errorMessage; + private Locale locale; + + public BuilderSupport(MessageSource messageSource, Map errorMessage) { + this.messageSource = messageSource; + this.errorMessage = errorMessage; + } + + @Override + public Error build() { + // Use custom error message if present + String messagePattern = null; + if (this.errorMessage != null) { + messagePattern = this.errorMessage.get(""); + if (this.details != null && this.details.get("property") != null) { + String specificMessagePattern = this.errorMessage.get(this.details.get("property")); + if (specificMessagePattern != null) { + messagePattern = specificMessagePattern; + } + } + if (messagePattern != null && !"".equals(messagePattern)) { + this.message = messagePattern; + } + } + + // Default to message source formatter + if (this.message == null && this.messageSupplier == null && this.messageFormatter == null) { + this.messageFormatter = args -> this.messageSource.getMessage(this.messageKey, + this.locale == null ? Locale.ROOT : this.locale, args); + } + return super.build(); + } + + public S locale(Locale locale) { + this.locale = locale; + return self(); + } + } +} diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java deleted file mode 100644 index f0486b52c..000000000 --- a/src/main/java/com/networknt/schema/MinItemsValidator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MinItemsValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MinItemsValidator.class); - - private final ValidationContext validationContext; - - private int min = 0; - - public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS, validationContext); - if (schemaNode.canConvertToExactIntegral()) { - min = schemaNode.intValue(); - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (node.isArray()) { - if (node.size() < min) { - return Collections.singleton(buildValidationMessage(at, "" + min)); - } - } else if (this.validationContext.getConfig().isTypeLoose()) { - if (1 < min) { - return Collections.singleton(buildValidationMessage(at, "" + min)); - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MinLengthValidator.java b/src/main/java/com/networknt/schema/MinLengthValidator.java deleted file mode 100644 index 53720ee8b..000000000 --- a/src/main/java/com/networknt/schema/MinLengthValidator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MinLengthValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MinLengthValidator.class); - - private final ValidationContext validationContext; - - private int minLength; - - public MinLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_LENGTH, validationContext); - minLength = Integer.MIN_VALUE; - if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { - minLength = schemaNode.intValue(); - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - // ignore non-string types - return Collections.emptySet(); - } - - if (node.textValue().codePointCount(0, node.textValue().length()) < minLength) { - return Collections.singleton(buildValidationMessage(at, "" + minLength)); - } - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java deleted file mode 100644 index d57ab31c6..000000000 --- a/src/main/java/com/networknt/schema/MinPropertiesValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class MinPropertiesValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MinPropertiesValidator.class); - - protected int min; - - public MinPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES, validationContext); - if (schemaNode.canConvertToExactIntegral()) { - min = schemaNode.intValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (node.isObject()) { - if (node.size() < min) { - return Collections.singleton(buildValidationMessage(at, "" + min)); - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java deleted file mode 100644 index 2208120ea..000000000 --- a/src/main/java/com/networknt/schema/MinimumValidator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collections; -import java.util.Set; - -public class MinimumValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MinimumValidator.class); - private static final String PROPERTY_EXCLUSIVE_MINIMUM = "exclusiveMinimum"; - - private boolean excludeEqual = false; - - /** - * In order to limit number of `if` statements in `validate` method, all the - * logic of picking the right comparison is abstracted into a mixin. - */ - private final ThresholdMixin typedMinimum; - - private final ValidationContext validationContext; - - public MinimumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext); - - if (!schemaNode.isNumber()) { - throw new JsonSchemaException("minimum value is not a number"); - } - - JsonNode exclusiveMinimumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MINIMUM); - if (exclusiveMinimumNode != null && exclusiveMinimumNode.isBoolean()) { - excludeEqual = exclusiveMinimumNode.booleanValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - - final String minimumText = schemaNode.asText(); - if ((schemaNode.isLong() || schemaNode.isInt()) && JsonType.INTEGER.toString().equals(getNodeFieldType())) { - // "integer", and within long range - final long lmin = schemaNode.asLong(); - typedMinimum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (node.isBigInteger()) { - //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. - int compare = node.bigIntegerValue().compareTo(new BigInteger(minimumText)); - return compare < 0 || (excludeEqual && compare == 0); - - } else if (node.isTextual()) { - BigDecimal min = new BigDecimal(minimumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(min); - return compare < 0 || (excludeEqual && compare == 0); - - } - long val = node.asLong(); - return lmin > val || (excludeEqual && lmin == val); - } - - @Override - public String thresholdValue() { - return String.valueOf(lmin); - } - }; - - } else { - typedMinimum = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - // jackson's BIG_DECIMAL parsing is limited. see https://github.com/FasterXML/jackson-databind/issues/1770 - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - final BigDecimal min = new BigDecimal(minimumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(min); - return compare < 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return minimumText; - } - }; - } - this.validationContext = validationContext; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { - // minimum only applies to numbers - return Collections.emptySet(); - } - - if (typedMinimum.crossesThreshold(node)) { - return Collections.singleton(buildValidationMessage(at, typedMinimum.thresholdValue())); - } - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/MultipleOfValidator.java b/src/main/java/com/networknt/schema/MultipleOfValidator.java deleted file mode 100644 index 4b7933b61..000000000 --- a/src/main/java/com/networknt/schema/MultipleOfValidator.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.Set; - -public class MultipleOfValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(MultipleOfValidator.class); - - private double divisor = 0; - - public MultipleOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF, validationContext); - if (schemaNode.isNumber()) { - divisor = schemaNode.doubleValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (node.isNumber()) { - double nodeValue = node.doubleValue(); - if (divisor != 0) { - // convert to BigDecimal since double type is not accurate enough to do the division and multiple - BigDecimal accurateDividend = node.isBigDecimal() ? node.decimalValue() : new BigDecimal(String.valueOf(nodeValue)); - BigDecimal accurateDivisor = new BigDecimal(String.valueOf(divisor)); - if (accurateDividend.divideAndRemainder(accurateDivisor)[1].abs().compareTo(BigDecimal.ZERO) > 0) { - return Collections.singleton(buildValidationMessage(at, "" + divisor)); - } - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/NonValidationKeyword.java b/src/main/java/com/networknt/schema/NonValidationKeyword.java deleted file mode 100644 index 80271ac1e..000000000 --- a/src/main/java/com/networknt/schema/NonValidationKeyword.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Collections; -import java.util.Set; - -/** - * Used for Keywords that have no validation aspect, but are part of the metaschema. - */ -public class NonValidationKeyword extends AbstractKeyword { - - private static final class Validator extends AbstractJsonValidator { - private Validator(String keyword) { - super(keyword); - } - - @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { - return Collections.emptySet(); - } - } - - public NonValidationKeyword(String keyword) { - super(keyword); - } - - @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { - return new Validator(getValue()); - } -} diff --git a/src/main/java/com/networknt/schema/NotAllowedValidator.java b/src/main/java/com/networknt/schema/NotAllowedValidator.java deleted file mode 100644 index 1bf141f11..000000000 --- a/src/main/java/com/networknt/schema/NotAllowedValidator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class NotAllowedValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(NotAllowedValidator.class); - - private List fieldNames = new ArrayList(); - - public NotAllowedValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT_ALLOWED, validationContext); - if (schemaNode.isArray()) { - int size = schemaNode.size(); - for (int i = 0; i < size; i++) { - fieldNames.add(schemaNode.get(i).asText()); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (String fieldName : fieldNames) { - JsonNode propertyNode = node.get(fieldName); - - if (propertyNode != null) { - errors.add(buildValidationMessage(at, fieldName)); - } - } - - return Collections.unmodifiableSet(errors); - } - -} diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java deleted file mode 100644 index 9de8ed3c8..000000000 --- a/src/main/java/com/networknt/schema/NotValidator.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -public class NotValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class); - - private final JsonSchema schema; - - public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext); - schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = schema.validate(node, rootNode, at); - if (errors.isEmpty()) { - return Collections.singleton(buildValidationMessage(at, schema.toString())); - } - return Collections.emptySet(); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - return schema.walk(node, rootNode, at, shouldValidateSchema); - } - - @Override - public void preloadJsonSchema() { - if (null != schema) { - schema.initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java deleted file mode 100644 index cc974592e..000000000 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.stream.Collectors; - -public class OneOfValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(OneOfValidator.class); - - private final List schemas = new ArrayList(); - - private static class ShortcutValidator { - private final JsonSchema schema; - private final Map constants; - - ShortcutValidator(JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext, JsonSchema schema) { - JsonNode refNode = schemaNode.get(ValidatorTypeCode.REF.getValue()); - JsonSchema resolvedRefSchema = refNode != null && refNode.isTextual() ? RefValidator.getRefSchema(parentSchema, validationContext, refNode.textValue()).getSchema() : null; - this.constants = extractConstants(schemaNode, resolvedRefSchema); - this.schema = schema; - } - - private Map extractConstants(JsonNode schemaNode, JsonSchema resolvedRefSchema) { - Map refMap = resolvedRefSchema != null ? extractConstants(resolvedRefSchema.getSchemaNode()) : Collections.emptyMap(); - Map schemaMap = extractConstants(schemaNode); - if (refMap.isEmpty()) { - return schemaMap; - } - if (schemaMap.isEmpty()) { - return refMap; - } - Map joined = new HashMap(); - joined.putAll(schemaMap); - joined.putAll(refMap); - return joined; - } - - private Map extractConstants(JsonNode schemaNode) { - Map result = new HashMap(); - if (!schemaNode.isObject()) { - return result; - } - - JsonNode propertiesNode = schemaNode.get("properties"); - if (propertiesNode == null || !propertiesNode.isObject()) { - return result; - } - Iterator fit = propertiesNode.fieldNames(); - while (fit.hasNext()) { - String fieldName = fit.next(); - JsonNode jsonNode = propertiesNode.get(fieldName); - String constantFieldValue = getConstantFieldValue(jsonNode); - if (constantFieldValue != null && !constantFieldValue.isEmpty()) { - result.put(fieldName, constantFieldValue); - } - } - return result; - } - - private String getConstantFieldValue(JsonNode jsonNode) { - if (jsonNode == null || !jsonNode.isObject() || !jsonNode.has("enum")) { - return null; - } - JsonNode enumNode = jsonNode.get("enum"); - if (enumNode == null || !enumNode.isArray()) { - return null; - } - if (enumNode.size() != 1) { - return null; - } - JsonNode valueNode = enumNode.get(0); - if (valueNode == null || !valueNode.isTextual()) { - return null; - } - return valueNode.textValue(); - } - - public boolean allConstantsMatch(JsonNode node) { - for (Map.Entry e : constants.entrySet()) { - JsonNode valueNode = node.get(e.getKey()); - if (valueNode != null && valueNode.isTextual()) { - boolean match = e.getValue().equals(valueNode.textValue()); - if (!match) { - return false; - } - } - } - return true; - } - - private JsonSchema getSchema() { - return schema; - } - - } - - public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext); - int size = schemaNode.size(); - for (int i = 0; i < size; i++) { - JsonNode childNode = schemaNode.get(i); - JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), childNode, parentSchema); - schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema)); - } - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - // this is a complex validator, we set the flag to true - state.setComplexValidator(true); - - int numberOfValidSchema = 0; - Set errors = new LinkedHashSet(); - Set childErrors = new LinkedHashSet(); - // validate that only a single element has been received in the oneOf node - // validation should not continue, as it contradicts the oneOf requirement of only one -// if(node.isObject() && node.size()>1) { -// errors = Collections.singleton(buildValidationMessage(at, "")); -// return Collections.unmodifiableSet(errors); -// } - - for (ShortcutValidator validator : schemas) { - Set schemaErrors = null; - // Reset state in case the previous validator did not match - state.setMatchedNode(true); - - //This prevents from collecting all the error messages in proper format. - /* if (!validator.allConstantsMatch(node)) { - // take a shortcut: if there is any constant that does not match, - // we can bail out of the validation - continue; - }*/ - - // get the current validator - JsonSchema schema = validator.schema; - if (!state.isWalkEnabled()) { - schemaErrors = schema.validate(node, rootNode, at); - } else { - schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabled()); - } - - // check if any validation errors have occurred - if (schemaErrors.isEmpty()) { - // check whether there are no errors HOWEVER we have validated the exact validator - if (!state.hasMatchedNode()) - continue; - - numberOfValidSchema++; - } - childErrors.addAll(schemaErrors); - } - - - // ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1. - if(numberOfValidSchema > 1){ - final ValidationMessage message = getMultiSchemasValidErrorMsg(at); - if( failFast ) { - throw new JsonSchemaException(message); - } - errors.add(message); - } - // ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1. - else if (numberOfValidSchema < 1) { - if (!childErrors.isEmpty()) { - if (childErrors.size() > 1) { - Set notAdditionalPropertiesOnly = new LinkedHashSet<>(childErrors.stream() - .filter((ValidationMessage validationMessage) -> !ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(validationMessage.getType())) - .sorted((vm1, vm2) -> compareValidationMessages(vm1, vm2)) - .collect(Collectors.toList())); - if (notAdditionalPropertiesOnly.size() > 0) { - childErrors = notAdditionalPropertiesOnly; - } - } - errors.addAll(childErrors); - } - if( failFast ){ - throw new JsonSchemaException(errors.toString()); - } - } - - // Make sure to signal parent handlers we matched - if (errors.isEmpty()) - state.setMatchedNode(true); - - // reset the ValidatorState object in the ThreadLocal - resetValidatorState(); - - return Collections.unmodifiableSet(errors); - } - - /** - * Sort ValidationMessage by its type - * @return - */ - private static int compareValidationMessages(ValidationMessage vm1, ValidationMessage vm2) { - // ValidationMessage's type has smaller index in the list below has high priority - final List typeCodes = Arrays.asList( - ValidatorTypeCode.TYPE.getValue(), - ValidatorTypeCode.DATETIME.getValue(), - ValidatorTypeCode.UUID.getValue(), - ValidatorTypeCode.ID.getValue(), - ValidatorTypeCode.EXCLUSIVE_MAXIMUM.getValue(), - ValidatorTypeCode.EXCLUSIVE_MINIMUM.getValue(), - ValidatorTypeCode.TRUE.getValue(), - ValidatorTypeCode.FALSE.getValue(), - ValidatorTypeCode.CONST.getValue(), - ValidatorTypeCode.CONTAINS.getValue(), - ValidatorTypeCode.PROPERTYNAMES.getValue() - ); - - final int index1 = typeCodes.indexOf(vm1.getType()); - final int index2 = typeCodes.indexOf(vm2.getType()); - - if (index1 >= 0) { - if (index2 >= 0) { - return Integer.compare(index1, index2); - } else { - return -1; - } - } else { - if (index2 >= 0) { - return 1; - } else { - return vm1.getCode().compareTo(vm2.getCode()); - } - } - } - - private void resetValidatorState() { - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - state.setComplexValidator(false); - state.setMatchedNode(true); - } - - public List getChildSchemas() { - List childJsonSchemas = new ArrayList(); - for (ShortcutValidator shortcutValidator: schemas ) { - childJsonSchemas.add(shortcutValidator.getSchema()); - } - return childJsonSchemas; - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } else { - for (ShortcutValidator validator : schemas) { - validator.schema.walk(node, rootNode, at , shouldValidateSchema); - } - } - return validationMessages; - } - - private ValidationMessage getMultiSchemasValidErrorMsg(String at){ - String msg=""; - for(ShortcutValidator schema: schemas){ - String schemaValue = schema.getSchema().getSchemaNode().toString(); - msg = msg.concat(schemaValue); - } - - return ValidationMessage.of(getValidatorType().getValue(), ValidatorTypeCode.ONE_OF , - at, String.format("but more than one schemas {%s} are valid ",msg)); - } - - @Override - public void preloadJsonSchema() { - for (final ShortcutValidator scValidator: schemas) { - scValidator.getSchema().initializeValidators(); - } - } -} diff --git a/src/main/java/com/networknt/schema/OutputFormat.java b/src/main/java/com/networknt/schema/OutputFormat.java new file mode 100644 index 000000000..9a64c71c2 --- /dev/null +++ b/src/main/java/com/networknt/schema/OutputFormat.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.function.Function; + +import com.networknt.schema.output.HierarchicalOutputUnitFormatter; +import com.networknt.schema.output.ListOutputUnitFormatter; +import com.networknt.schema.output.OutputFlag; +import com.networknt.schema.output.OutputUnit; +import com.networknt.schema.output.OutputUnitData; + +/** + * Formats the validation results. + * + * @param the result type + */ +public interface OutputFormat { + /** + * Customize the execution context before validation. + *

+ * The schema context should only be used for reference as it is shared. + * + * @param executionContext the execution context + * @param schemaContext the schema context for reference + */ + default void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + } + + /** + * Formats the validation results. + * + * @param jsonSchema the schema + * @param executionContext the execution context + * @param schemaContext the schema context + * + * @return the result + */ + T format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext); + + /** + * The Default output format. + */ + Default DEFAULT = new Default(); + + /** + * The Boolean output format. + */ + Boolean BOOLEAN = new Boolean(); + + /** + * The Flag output format. + */ + Flag FLAG = new Flag(); + + /** + * The List output format. + */ + List LIST = new List(); + + + /** + * The Hierarchical output format. + */ + Hierarchical HIERARCHICAL = new Hierarchical(); + + /** + * The Result output format. + *

+ * This is currently not exposed to consumers. + */ + Result RESULT = new Result(); + + /** + * The Default output format. + */ + class Default implements OutputFormat> { + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + executionContext.executionConfig(executionConfig -> executionConfig.annotationCollectionEnabled(false)); + } + + @Override + public java.util.List format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return executionContext.getErrors(); + } + } + + /** + * The Flag output format. + */ + class Flag implements OutputFormat { + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + executionContext.executionConfig( + executionConfig -> executionConfig.annotationCollectionEnabled(false).failFast(true)); + } + + @Override + public OutputFlag format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return new OutputFlag(executionContext.getErrors().isEmpty()); + } + } + + /** + * The Boolean output format. + */ + class Boolean implements OutputFormat { + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + executionContext.executionConfig( + executionConfig -> executionConfig.annotationCollectionEnabled(false).failFast(true)); + } + + @Override + public java.lang.Boolean format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return executionContext.getErrors().isEmpty(); + } + } + + /** + * The List output format. + */ + class List implements OutputFormat { + private final Function errorMapper; + + public List() { + this(OutputUnitData::formatError); + } + + /** + * Constructor. + * + * @param errorMapper to map the error + */ + public List(Function errorMapper) { + this.errorMapper = errorMapper; + } + + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + } + + @Override + public OutputUnit format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return ListOutputUnitFormatter.format(executionContext.getErrors(), executionContext, schemaContext, + this.errorMapper); + } + } + + /** + * The Hierarchical output format. + */ + class Hierarchical implements OutputFormat { + private final Function errorMapper; + + public Hierarchical() { + this(OutputUnitData::formatError); + } + + /** + * Constructor. + * + * @param errorMapper to map the error + */ + public Hierarchical(Function errorMapper) { + this.errorMapper = errorMapper; + } + + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + } + + @Override + public OutputUnit format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return HierarchicalOutputUnitFormatter.format(jsonSchema, executionContext.getErrors(), executionContext, + schemaContext, this.errorMapper); + } + } + + /** + * The Result output format. + *

+ * This is currently not exposed to consumers. + */ + class Result implements OutputFormat { + @Override + public void customize(ExecutionContext executionContext, SchemaContext schemaContext) { + } + + @Override + public com.networknt.schema.Result format(Schema jsonSchema, ExecutionContext executionContext, + SchemaContext schemaContext) { + return new com.networknt.schema.Result(executionContext); + } + } +} diff --git a/src/main/java/com/networknt/schema/PatternFormat.java b/src/main/java/com/networknt/schema/PatternFormat.java deleted file mode 100644 index 5be97e513..000000000 --- a/src/main/java/com/networknt/schema/PatternFormat.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.regex.Pattern; - -public class PatternFormat implements Format { - private final String name; - private final Pattern pattern; - - public PatternFormat(String name, String regex) { - this.name = name; - this.pattern = Pattern.compile(regex); - } - - @Override - public boolean matches(String value) { - return pattern.matcher(value).matches(); - } - - @Override - public String getName() { - return name; - } - - @Override - public String getErrorMessageDescription() { - return pattern.pattern(); - } -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java deleted file mode 100644 index 3d1f67a9b..000000000 --- a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class PatternPropertiesValidator extends BaseJsonValidator implements JsonValidator { - public static final String PROPERTY = "patternProperties"; - private static final Logger logger = LoggerFactory.getLogger(PatternPropertiesValidator.class); - private final Map schemas = new IdentityHashMap(); - - public PatternPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN_PROPERTIES, validationContext); - if (!schemaNode.isObject()) { - throw new JsonSchemaException("patternProperties must be an object node"); - } - Iterator names = schemaNode.fieldNames(); - while (names.hasNext()) { - String name = names.next(); - schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, parentSchema.getCurrentUri(), schemaNode.get(name), parentSchema)); - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - if (!node.isObject()) { - return errors; - } - - Iterator names = node.fieldNames(); - while (names.hasNext()) { - String name = names.next(); - JsonNode n = node.get(name); - for (Map.Entry entry : schemas.entrySet()) { - Matcher m = entry.getKey().matcher(name); - if (m.find()) { - errors.addAll(entry.getValue().validate(n, rootNode, at + "." + name)); - } - } - } - return Collections.unmodifiableSet(errors); - } - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemas.values()); - } -} diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java deleted file mode 100644 index df6d13a4d..000000000 --- a/src/main/java/com/networknt/schema/PatternValidator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.jcodings.specific.UTF8Encoding; -import org.joni.Option; -import org.joni.Regex; -import org.joni.Syntax; -import org.joni.exception.SyntaxException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -public class PatternValidator implements JsonValidator { - - private final JsonValidator delegate; - - private final ValidationContext validationContext; - - public PatternValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - this.validationContext = validationContext; - if (validationContext.getConfig() != null && validationContext.getConfig().isEcma262Validator()) { - delegate = new PatternValidatorEcma262(schemaPath, schemaNode, parentSchema, validationContext); - } else { - delegate = new PatternValidatorJava(schemaPath, schemaNode, parentSchema, validationContext); - } - } - - @Override - public Set validate(JsonNode rootNode) { - return delegate.validate(rootNode); - } - - @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { - return delegate.validate(node, rootNode, at); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - return delegate.walk(node, rootNode, at, shouldValidateSchema); - } - - private static class PatternValidatorJava extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class); - private final ValidationContext validationContext; - private String pattern; - private Pattern compiledPattern; - - public PatternValidatorJava(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN, validationContext); - pattern = ""; - if (schemaNode != null && schemaNode.isTextual()) { - pattern = schemaNode.textValue(); - try { - compiledPattern = Pattern.compile(pattern); - } catch (PatternSyntaxException pse) { - logger.error("Failed to compile pattern : Invalid syntax [" + pattern + "]", pse); - throw pse; - } - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - private boolean matches(String value) { - return compiledPattern == null || compiledPattern.matcher(value).find(); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return Collections.emptySet(); - } - - try { - if (!matches(node.asText())) { - return Collections.singleton(buildValidationMessage(at, pattern)); - } - } catch (PatternSyntaxException pse) { - logger.error("Failed to apply pattern on " + at + ": Invalid syntax [" + pattern + "]", pse); - } - - return Collections.emptySet(); - } - } - - private static class PatternValidatorEcma262 extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class); - private final ValidationContext validationContext; - private String pattern; - private Regex compiledRegex; - - public PatternValidatorEcma262(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN, validationContext); - pattern = ""; - if (schemaNode != null && schemaNode.isTextual()) { - pattern = schemaNode.textValue(); - try { - compileRegexPattern(pattern, validationContext.getConfig() != null && validationContext.getConfig().isEcma262Validator()); - } catch (SyntaxException se) { - logger.error("Failed to compile pattern : Invalid syntax [" + pattern + "]", se); - throw se; - } - } - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - private void compileRegexPattern(String regex, boolean useEcma262Validator) { - byte[] regexBytes = regex.getBytes(); - this.compiledRegex = new Regex(regexBytes, 0, regexBytes.length, Option.NONE, UTF8Encoding.INSTANCE, Syntax.ECMAScript); - } - - private boolean matches(String value) { - if (compiledRegex == null) { - return true; - } - - byte[] bytes = value.getBytes(); - return compiledRegex.matcher(bytes).search(0, bytes.length, Option.NONE) >= 0; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return Collections.emptySet(); - } - - try { - if (!matches(node.asText())) { - return Collections.singleton(buildValidationMessage(at, pattern)); - } - } catch (PatternSyntaxException pse) { - logger.error("Failed to apply pattern on " + at + ": Invalid syntax [" + pattern + "]", pse); - } - - return Collections.emptySet(); - } - } -} diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java deleted file mode 100644 index 69138a719..000000000 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.schema.walk.DefaultPropertyWalkListenerRunner; -import com.networknt.schema.walk.WalkListenerRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class PropertiesValidator extends BaseJsonValidator implements JsonValidator { - public static final String PROPERTY = "properties"; - private static final Logger logger = LoggerFactory.getLogger(PropertiesValidator.class); - private final Map schemas = new HashMap(); - private final ValidationContext validationContext; - - public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTIES, validationContext); - this.validationContext = validationContext; - for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { - String pname = it.next(); - schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema)); - } - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(this.validationContext.getConfig().getPropertyWalkListeners()); - - Set errors = new LinkedHashSet(); - - // get the Validator state object storing validation data - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - - for (Map.Entry entry : schemas.entrySet()) { - JsonSchema propertySchema = entry.getValue(); - JsonNode propertyNode = node.get(entry.getKey()); - if (propertyNode != null) { - // check whether this is a complex validator. save the state - boolean isComplex = state.isComplexValidator(); - // if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated - if (state.isComplexValidator()) { - state.setMatchedNode(true); - } - // reset the complex validator for child element validation, and reset it after the return from the recursive call - state.setComplexValidator(false); - - if (!state.isWalkEnabled()) { - //validate the child element(s) - errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); - } else { - // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. - walkSchema(entry, node, rootNode, at, state.isValidationEnabled(), errors, propertyWalkListenerRunner); - } - - // reset the complex flag to the original value before the recursive call - state.setComplexValidator(isComplex); - // if this was a complex validator, the node has matched and has been validated - if (state.isComplexValidator()) { - state.setMatchedNode(true); - } - } else { - // check whether the node which has not matched was mandatory or not - if (getParentSchema().hasRequiredValidator()) { - Set requiredErrors = getParentSchema().getRequiredValidator().validate(node, rootNode, at); - - if (!requiredErrors.isEmpty()) { - // the node was mandatory, decide which behavior to employ when validator has not matched - if (state.isComplexValidator()) { - // this was a complex validator (ex oneOf) and the node has not been matched - state.setMatchedNode(false); - return Collections.unmodifiableSet(new LinkedHashSet()); - } else { - errors.addAll(requiredErrors); - } - } - } - } - } - - return Collections.unmodifiableSet(errors); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - if (applyDefaultsStrategy.shouldApplyPropertyDefaults()) { - applyPropertyDefaults((ObjectNode) node); - } - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } else { - WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(this.validationContext.getConfig().getPropertyWalkListeners()); - for (Map.Entry entry : schemas.entrySet()) { - walkSchema(entry, node, rootNode, at, shouldValidateSchema, validationMessages, propertyWalkListenerRunner); - } - } - return validationMessages; - } - - private void applyPropertyDefaults(ObjectNode node) { - for (Map.Entry entry : schemas.entrySet()) { - JsonNode propertyNode = node.get(entry.getKey()); - - if (propertyNode == null || (applyDefaultsStrategy.shouldApplyPropertyDefaultsIfNull() && propertyNode.isNull())) { - JsonSchema propertySchema = entry.getValue(); - JsonNode defaultNode = propertySchema.getSchemaNode().get("default"); - if (defaultNode != null && !defaultNode.isNull()) { - // mutate the input json - node.set(entry.getKey(), defaultNode); - } - } - } - } - - private void walkSchema(Map.Entry entry, JsonNode node, JsonNode rootNode, String at, - boolean shouldValidateSchema, Set validationMessages, WalkListenerRunner propertyWalkListenerRunner) { - JsonSchema propertySchema = entry.getValue(); - JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); - boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), - propertyNode, rootNode, at + "." + entry.getKey(), propertySchema.getSchemaPath(), - propertySchema.getSchemaNode(), propertySchema.getParentSchema(), validationContext, - validationContext.getJsonSchemaFactory()); - if (executeWalk) { - validationMessages.addAll( - propertySchema.walk(propertyNode, rootNode, at + "." + entry.getKey(), shouldValidateSchema)); - } - propertyWalkListenerRunner.runPostWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, rootNode, - at + "." + entry.getKey(), propertySchema.getSchemaPath(), propertySchema.getSchemaNode(), - propertySchema.getParentSchema(), validationContext, validationContext.getJsonSchemaFactory(), validationMessages); - - } - - public Map getSchemas() { - return schemas; - } - - - @Override - public void preloadJsonSchema() { - preloadJsonSchemas(schemas.values()); - } -} diff --git a/src/main/java/com/networknt/schema/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/PropertyNamesValidator.java deleted file mode 100644 index 7a6bf8482..000000000 --- a/src/main/java/com/networknt/schema/PropertyNamesValidator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.TextNode; - -public class PropertyNamesValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(PropertyNamesValidator.class); - private final JsonSchema innerSchema; - public PropertyNamesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTYNAMES, validationContext); - innerSchema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, parentSchema); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - for (Iterator it = node.fieldNames(); it.hasNext(); ) { - final String pname = it.next(); - final TextNode pnameText = TextNode.valueOf(pname); - final Set schemaErrors = innerSchema.validate(pnameText, node, at + "." + pname); - for (final ValidationMessage schemaError : schemaErrors) { - final String path = schemaError.getPath(); - String msg = schemaError.getMessage(); - if (msg.startsWith(path)) - msg = msg.substring(path.length()).replaceFirst("^:\\s*", ""); - - errors.add(buildValidationMessage(schemaError.getPath(), msg)); - } - } - return Collections.unmodifiableSet(errors); - } - - - @Override - public void preloadJsonSchema() { - innerSchema.initializeValidators(); - } -} diff --git a/src/main/java/com/networknt/schema/ReadOnlyValidator.java b/src/main/java/com/networknt/schema/ReadOnlyValidator.java deleted file mode 100644 index 8718d6cb0..000000000 --- a/src/main/java/com/networknt/schema/ReadOnlyValidator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class ReadOnlyValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class); - - private List fieldNames = new ArrayList(); - - public ReadOnlyValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.READ_ONLY, validationContext); - if (schemaNode.isArray()) { - int size = schemaNode.size(); - for (int i = 0; i < size; i++) { - fieldNames.add(schemaNode.get(i).asText()); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - for (String fieldName : fieldNames) { - JsonNode propertyNode = node.get(fieldName); - String datapath = ""; - if (at.equals("$")) { - datapath = datapath + "#original." + fieldName; - } else { - datapath = datapath + "#original." + at.substring(2) + "." + fieldName; - } - JsonNode originalNode = getNode(datapath, rootNode); - - boolean theSame = propertyNode != null && originalNode != null && propertyNode.equals(originalNode); - if (!theSame) { - errors.add(buildValidationMessage(at)); - } - } - - return Collections.unmodifiableSet(errors); - } - - private JsonNode getNode(String datapath, JsonNode data) { - String path = datapath; - if (path.startsWith("$.")) { - path = path.substring(2); - } - - String[] parts = path.split("\\."); - JsonNode result = null; - for (int i = 0; i < parts.length; i++) { - if (parts[i].contains("[")) { - int idx1 = parts[i].indexOf("["); - int idx2 = parts[i].indexOf("]"); - String key = parts[i].substring(0, idx1).trim(); - int idx = Integer.parseInt(parts[i].substring(idx1 + 1, idx2).trim()); - result = data.get(key).get(idx); - } else { - result = data.get(parts[i]); - } - if (result == null) { - break; - } - data = result; - } - return result; - } - -} diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java deleted file mode 100644 index 74d2f73fa..000000000 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.uri.URIFactory; -import com.networknt.schema.urn.URNFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -public class RefValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(RefValidator.class); - - protected JsonSchemaRef schema; - - private JsonSchema parentSchema; - - private static final String REF_CURRENT = "#"; - - public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext); - String refValue = schemaNode.asText(); - this.parentSchema = parentSchema; - schema = getRefSchema(parentSchema, validationContext, refValue); - if (schema == null) { - throw new JsonSchemaException( - ValidationMessage.of(ValidatorTypeCode.REF.getValue(), - CustomErrorMessageType.of("internal.unresolvedRef", new MessageFormat("{0}: Reference {1} cannot be resolved")), schemaPath, refValue)); - } - } - - static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue) { - final String refValueOriginal = refValue; - - if (!refValue.startsWith(REF_CURRENT)) { - // This will be the uri extracted from the refValue (this may be a relative or absolute uri). - final String refUri; - final int index = refValue.indexOf(REF_CURRENT); - if (index > 0) { - refUri = refValue.substring(0, index); - } else { - refUri = refValue; - } - - // This will determine the correct absolute uri for the refUri. This decision will take into - // account the current uri of the parent schema. - URI schemaUri = determineSchemaUri(validationContext.getURIFactory(), parentSchema, refUri); - if (schemaUri == null) { - // the URNFactory is optional - if (validationContext.getURNFactory() == null) { - return null; - } - // If the uri dose't determinate try to determinate with urn factory - schemaUri = determineSchemaUrn(validationContext.getURNFactory(), refUri); - if (schemaUri == null) { - return null; - } - } - - // This should retrieve schemas regardless of the protocol that is in the uri. - parentSchema = validationContext.getJsonSchemaFactory().getSchema(schemaUri, validationContext.getConfig()); - - if (index < 0) { - return new JsonSchemaRef(parentSchema.findAncestor()); - } else { - refValue = refValue.substring(index); - } - } - if (refValue.equals(REF_CURRENT)) { - return new JsonSchemaRef(parentSchema.findAncestor()); - } else { - JsonNode node = parentSchema.getRefSchemaNode(refValue); - if (node != null) { - JsonSchemaRef ref = validationContext.getReferenceParsingInProgress(refValueOriginal); - if (ref == null) { - final JsonSchema schema = new JsonSchema(validationContext, refValue, parentSchema.getCurrentUri(), node, parentSchema); - ref = new JsonSchemaRef(schema); - validationContext.setReferenceParsingInProgress(refValueOriginal, ref); - } - return ref; - } - } - return null; - } - - private static URI determineSchemaUri(final URIFactory uriFactory, final JsonSchema parentSchema, final String refUri) { - URI schemaUri; - final URI currentUri = parentSchema.getCurrentUri(); - try { - if (currentUri == null) { - schemaUri = uriFactory.create(refUri); - } else { - schemaUri = uriFactory.create(currentUri, refUri); - } - } catch (IllegalArgumentException e) { - schemaUri = null; - } - return schemaUri; - } - - private static URI determineSchemaUrn(final URNFactory urnFactory, final String refUri) { - URI schemaUrn; - try { - schemaUrn = urnFactory.create(refUri); - } catch (IllegalArgumentException e) { - schemaUrn = null; - } - return schemaUrn; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, - // these schemas will be cached along with config. We have to replace the config for cached $ref references - // with the latest config. Reset the config. - schema.getSchema().getValidationContext().setConfig(parentSchema.getValidationContext().getConfig()); - if (schema != null) { - return schema.validate(node, rootNode, at); - } else { - return Collections.emptySet(); - } - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, - // these schemas will be cached along with config. We have to replace the config for cached $ref references - // with the latest config. Reset the config. - schema.getSchema().getValidationContext().setConfig(parentSchema.getValidationContext().getConfig()); - if (schema != null) { - return schema.walk(node, rootNode, at, shouldValidateSchema); - } - return Collections.emptySet(); - } - - public JsonSchemaRef getSchemaRef() { - return schema; - } - - - @Override - public void preloadJsonSchema() { - schema.getSchema().initializeValidators(); - } -} diff --git a/src/main/java/com/networknt/schema/RequiredValidator.java b/src/main/java/com/networknt/schema/RequiredValidator.java deleted file mode 100644 index 5d605f084..000000000 --- a/src/main/java/com/networknt/schema/RequiredValidator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class RequiredValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class); - - private List fieldNames = new ArrayList(); - - public RequiredValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REQUIRED, validationContext); - if (schemaNode.isArray()) { - for (JsonNode fieldNme : schemaNode) { - fieldNames.add(fieldNme.asText()); - } - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (!node.isObject()) { - return Collections.emptySet(); - } - - Set errors = new LinkedHashSet(); - - for (String fieldName : fieldNames) { - JsonNode propertyNode = node.get(fieldName); - - if (propertyNode == null) { - errors.add(buildValidationMessage(at, fieldName)); - } - } - - return Collections.unmodifiableSet(errors); - } - -} diff --git a/src/main/java/com/networknt/schema/Result.java b/src/main/java/com/networknt/schema/Result.java new file mode 100644 index 000000000..028d6bd2b --- /dev/null +++ b/src/main/java/com/networknt/schema/Result.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.List; + +/** + * Represents a validation result. + */ +public class Result { + private final ExecutionContext executionContext; + + public Result(ExecutionContext executionContext) { + super(); + this.executionContext = executionContext; + } + + public List getErrors() { + return getExecutionContext().getErrors(); + } + + public ExecutionContext getExecutionContext() { + return executionContext; + } + + public CollectorContext getCollectorContext() { + return getExecutionContext().getCollectorContext(); + } +} diff --git a/src/main/java/com/networknt/schema/Schema.java b/src/main/java/com/networknt/schema/Schema.java new file mode 100644 index 000000000..bfd264019 --- /dev/null +++ b/src/main/java/com/networknt/schema/Schema.java @@ -0,0 +1,1625 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Consumer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.keyword.TypeValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.path.PathType; +import com.networknt.schema.resource.ClasspathResourceLoader; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.ResourceLoader; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.utils.JsonNodes; + +/** + * Used for creating a schema with validators for validating inputs. This is + * created using SchemaRegistry#withDefaultDialect(Version, Consumer) and should + * be cached for performance. + *

+ * This is the core of json constraint implementation. It parses json constraint + * file and generates KeywordValidators. The class is thread safe, once it is + * constructed, it can be used to validate multiple json data concurrently. + *

+ * Schema instances are thread-safe provided its configuration is not + * modified. + */ +public class Schema implements Validator { + private static final long DRAFT_2019_09_VALUE = SpecificationVersion.DRAFT_2019_09.getOrder(); + private final String id; + + /** + * The validators sorted and indexed by evaluation path. + */ + private List validators = null; + private boolean unevaluatedPropertiesPresent = false; + private boolean unevaluatedItemsPresent = false; + + private boolean validatorsLoaded = false; + private boolean recursiveAnchor = false; + + protected final JsonNode schemaNode; + protected final Schema parentSchema; + protected final SchemaLocation schemaLocation; + protected final SchemaContext schemaContext; + protected final boolean suppressSubSchemaRetrieval; + + public JsonNode getSchemaNode() { + return this.schemaNode; + } + + public SchemaLocation getSchemaLocation() { + return this.schemaLocation; + } + + public Schema getParentSchema() { + return parentSchema; + } + + public boolean isSuppressSubSchemaRetrieval() { + return suppressSubSchemaRetrieval; + } + + protected Schema fetchSubSchemaNode(SchemaContext schemaContext) { + return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, schemaContext); + } + + private static Schema obtainSubSchemaNode(final JsonNode schemaNode, final SchemaContext schemaContext) { + final JsonNode node = schemaNode.get("id"); + + if (node == null) { + return null; + } + + if (node.equals(schemaNode.get("$schema"))) { + return null; + } + + final String text = node.textValue(); + if (text == null) { + return null; + } + final SchemaLocation schemaLocation = SchemaLocation.of(node.textValue()); + return schemaContext.getSchemaRegistry().getSchema(schemaLocation); + } + public static class JsonNodePathLegacy { + private static final NodePath INSTANCE = new NodePath(PathType.LEGACY); + public static NodePath getInstance() { + return INSTANCE; + } + } + + public static class JsonNodePathJsonPointer { + private static final NodePath INSTANCE = new NodePath(PathType.JSON_POINTER); + public static NodePath getInstance() { + return INSTANCE; + } + } + + public static class JsonNodePathJsonPath { + private static final NodePath INSTANCE = new NodePath(PathType.JSON_PATH); + public static NodePath getInstance() { + return INSTANCE; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node) { + /* Previously the evaluation path started with the fragment of the schema due to the way it was implemented + * as part of the schema's state + * int count = this.schemaLocation.getFragment().getNameCount(); + * for (int x = 0; x < count; x++) { + * executionContext.evaluationPath.addLast(this.schemaLocation.getFragment().getElement(x)); + * } + */ + executionContext.evaluationPath = atRoot(); + validate(executionContext, node, node, atRoot()); + } + + /** + * Get the root path. + * + * @return The path. + */ + protected NodePath atRoot() { + if (this.schemaContext.getSchemaRegistryConfig().getPathType().equals(PathType.JSON_POINTER)) { + return JsonNodePathJsonPointer.getInstance(); + } else if (this.schemaContext.getSchemaRegistryConfig().getPathType().equals(PathType.LEGACY)) { + return JsonNodePathLegacy.getInstance(); + } else if (this.schemaContext.getSchemaRegistryConfig().getPathType().equals(PathType.JSON_PATH)) { + return JsonNodePathJsonPath.getInstance(); + } + return new NodePath(this.schemaContext.getSchemaRegistryConfig().getPathType()); + } + + static Schema from(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parent, boolean suppressSubSchemaRetrieval) { + return new Schema(schemaContext, schemaLocation, schemaNode, parent, suppressSubSchemaRetrieval); + } + + private boolean hasNoFragment(SchemaLocation schemaLocation) { + NodePath fragment = this.schemaLocation.getFragment(); + return fragment == null || (fragment.getParent() == null && fragment.getNameCount() == 0); + } + + private static SchemaLocation resolve(SchemaLocation schemaLocation, JsonNode schemaNode, boolean rootSchema, + SchemaContext schemaContext) { + String id = schemaContext.resolveSchemaId(schemaNode); + if (id != null) { + String resolve = id; + int fragment = id.indexOf('#'); + // Check if there is a non-empty fragment + if (fragment != -1 && !(fragment + 1 >= id.length())) { + // strip the fragment when resolving + resolve = id.substring(0, fragment); + } + SchemaLocation result = !"".equals(resolve) ? schemaLocation.resolve(resolve) : schemaLocation; + SchemaIdValidator validator = schemaContext.getSchemaRegistryConfig().getSchemaIdValidator(); + if (validator != null) { + if (!validator.validate(id, rootSchema, schemaLocation, result, schemaContext)) { + SchemaLocation idSchemaLocation = schemaLocation.append(schemaContext.getDialect().getIdKeyword()); + Error error = Error.builder() + .messageKey(KeywordType.ID.getValue()).keyword(KeywordType.ID.getValue()) + .instanceLocation(idSchemaLocation.getFragment()) + .arguments(id, schemaContext.getDialect().getIdKeyword(), idSchemaLocation) + .schemaLocation(idSchemaLocation) + .schemaNode(schemaNode) + .messageFormatter(args -> schemaContext.getSchemaRegistryConfig().getMessageSource().getMessage( + KeywordType.ID.getValue(), schemaContext.getSchemaRegistryConfig().getLocale(), args)) + .build(); + throw new InvalidSchemaException(error); + } + } + return result; + } else { + return schemaLocation; + } + } + + private Schema(SchemaContext schemaContext, SchemaLocation schemaLocation, + JsonNode schemaNode, Schema parent, boolean suppressSubSchemaRetrieval) { + this.schemaContext = schemaContext; + this.schemaLocation = resolve(schemaLocation, schemaNode, parent == null, schemaContext); + this.schemaNode = schemaNode; + this.parentSchema = parent; + this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; + + String id = this.schemaContext.resolveSchemaId(this.schemaNode); + if (id != null) { + // In earlier drafts $id may contain an anchor fragment see draft4/idRef.json + // Note that json pointer fragments in $id are not allowed + SchemaLocation result = id.indexOf('#') != -1 ? schemaLocation.resolve(id) : this.schemaLocation; + if (hasNoFragment(result)) { + this.id = id; + } else { + // This is an anchor fragment and is not a document + // This will be added to schema resources later + this.id = null; + } + this.schemaContext.getSchemaResources().putIfAbsent(result != null ? result.toString() : id, this); + } else { + if (hasNoFragment(schemaLocation)) { + // No $id but there is no fragment and is thus a schema resource + this.id = schemaLocation.getAbsoluteIri() != null ? schemaLocation.getAbsoluteIri().toString() : ""; + this.schemaContext.getSchemaResources() + .putIfAbsent(schemaLocation != null ? schemaLocation.toString() : this.id, this); + } else { + this.id = null; + } + } + String anchor = this.schemaContext.getDialect().readAnchor(this.schemaNode); + if (anchor != null) { + String absoluteIri = this.schemaLocation.getAbsoluteIri() != null + ? this.schemaLocation.getAbsoluteIri().toString() + : ""; + this.schemaContext.getSchemaResources() + .putIfAbsent(absoluteIri + "#" + anchor, this); + } + String dynamicAnchor = this.schemaContext.getDialect().readDynamicAnchor(schemaNode); + if (dynamicAnchor != null) { + String absoluteIri = this.schemaLocation.getAbsoluteIri() != null + ? this.schemaLocation.getAbsoluteIri().toString() + : ""; + this.schemaContext.getDynamicAnchors() + .putIfAbsent(absoluteIri + "#" + dynamicAnchor, this); + } + getValidators(); + } + + /** + * Constructor to create a copy using fields. + * + * @param validators the validators + * @param validatorsLoaded whether the validators are preloaded + * @param recursiveAnchor whether this is has a recursive anchor + * @param id the id + * @param suppressSubSchemaRetrieval to suppress sub schema retrieval + * @param schemaNode the schema node + * @param schemaContext the schema context + * @param parentSchema the parent schema + * @param schemaLocation the schema location + * @param errorMessage the error message + */ + protected Schema( + List validators, + boolean validatorsLoaded, + boolean recursiveAnchor, + TypeValidator typeValidator, + String id, + boolean suppressSubSchemaRetrieval, + JsonNode schemaNode, + SchemaContext schemaContext, + Schema parentSchema, + SchemaLocation schemaLocation, + Map errorMessage) { + this.validators = validators; + this.validatorsLoaded = validatorsLoaded; + this.recursiveAnchor = recursiveAnchor; + this.id = id; + + this.schemaContext = schemaContext; + this.schemaLocation = schemaLocation; + this.schemaNode = schemaNode; + this.parentSchema = parentSchema; + this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; + } + + public SchemaContext getSchemaContext() { + return this.schemaContext; + } + + public boolean hasKeyword(String keyword) { + return this.schemaNode.has(keyword); + } + + /** + * Find the schema node for $ref attribute. + * + * @param ref String + * @return JsonNode + */ + public JsonNode getRefSchemaNode(String ref) { + Schema schema = findSchemaResourceRoot(); + JsonNode node = schema.getSchemaNode(); + + String jsonPointer = ref; + if (schema.getId() != null && ref.startsWith(schema.getId())) { + String refValue = ref.substring(schema.getId().length()); + jsonPointer = refValue; + } + if (jsonPointer.startsWith("#/")) { + jsonPointer = jsonPointer.substring(1); + } + + if (jsonPointer.startsWith("/")) { + try { + jsonPointer = URLDecoder.decode(jsonPointer, "utf-8"); + } catch (UnsupportedEncodingException e) { + // ignored + } + + node = node.at(jsonPointer); + if (node.isMissingNode()) { + node = handleNullNode(ref, schema); + } + } + return node; + } + + public Schema getRefSchema(NodePath fragment) { + if (PathType.JSON_POINTER.equals(fragment.getPathType())) { + // Json Pointer + return getSubSchema(fragment); + } else { + // Anchor + String base = this.getSchemaLocation().getAbsoluteIri() != null ? this.schemaLocation.getAbsoluteIri().toString() : ""; + String anchor = base + "#" + fragment; + Schema result = this.schemaContext.getSchemaResources().get(anchor); + if (result == null) { + result = this.schemaContext.getDynamicAnchors().get(anchor); + } + if (result == null) { + throw new SchemaException("Unable to find anchor "+anchor); + } + return result; + } + } + + /** + * Gets the sub schema given the json pointer fragment. + * + * @param fragment the json pointer fragment + * @return the schema + */ + public Schema getSubSchema(NodePath fragment) { + Schema document = findSchemaResourceRoot(); + Schema parent = document; + Schema subSchema = null; + JsonNode parentNode = parent.getSchemaNode(); + SchemaLocation schemaLocation = document.getSchemaLocation(); + int nameCount = fragment.getNameCount(); + for (int x = 0; x < nameCount; x++) { + /* + * The sub schema is created by iterating through the parents in order to + * maintain the lexical parent schema context. + * + * If this is created directly from the schema node pointed to by the json + * pointer, the lexical context is lost and this will affect $ref resolution due + * to $id changes in the lexical scope. + */ + Object segment = fragment.getElement(x); + JsonNode subSchemaNode = getNode(parentNode, segment); + if (subSchemaNode != null) { + if (segment instanceof Number && parentNode.isArray()) { + int index = ((Number) segment).intValue(); + schemaLocation = schemaLocation.append(index); + } else { + schemaLocation = schemaLocation.append(segment.toString()); + } + /* + * The parent schema context is used to create as there can be changes in + * $schema is later drafts which means the schema context can change. + */ + // This may need a redesign see Issue 939 and 940 + String id = parent.getSchemaContext().resolveSchemaId(subSchemaNode); +// if (!("definitions".equals(segment.toString()) || "$defs".equals(segment.toString()) +// )) { + if (id != null || x == nameCount - 1) { + subSchema = parent.getSchemaContext().newSchema(schemaLocation, subSchemaNode, + parent); + parent = subSchema; + schemaLocation = subSchema.getSchemaLocation(); + } + parentNode = subSchemaNode; + } else { + /* + * This means that the fragment wasn't found in the document. + * + * In Draft 4-7 the $id indicates a base uri change and not a schema resource so this might not be the right document. + * + * See test for draft4\extra\classpath\schema.json + */ + Schema found = document.findSchemaResourceRoot().fetchSubSchemaNode(this.schemaContext); + if (found != null) { + found = found.getSubSchema(fragment); + } + if (found == null) { + Error error = Error.builder() + .keyword(KeywordType.REF.getValue()).messageKey("internal.unresolvedRef") + .message("Reference {0} cannot be resolved") + .instanceLocation(schemaLocation.getFragment()) + .schemaLocation(schemaLocation) + .evaluationPath(null) + .arguments(fragment).build(); + throw new InvalidSchemaRefException(error); + } + return found; + } + } + return subSchema; + } + + protected JsonNode getNode(Object propertyOrIndex) { + return getNode(this.schemaNode, propertyOrIndex); + } + + protected JsonNode getNode(JsonNode node, Object propertyOrIndex) { + return JsonNodes.get(node, propertyOrIndex); + } + + public Schema findLexicalRoot() { + Schema ancestor = this; + while (ancestor.getId() == null) { + if (null == ancestor.getParentSchema()) break; + ancestor = ancestor.getParentSchema(); + } + return ancestor; + } + + /** + * Finds the root of the schema resource. + *

+ * This is either the schema document root or the subschema resource root. + * + * @return the root of the schema + */ + public Schema findSchemaResourceRoot() { + Schema ancestor = this; + while (!ancestor.isSchemaResourceRoot()) { + ancestor = ancestor.getParentSchema(); + } + return ancestor; + } + + /** + * Determines if this schema resource is a schema resource root. + *

+ * This is either the schema document root or the subschema resource root. + * + * @return if this schema is a schema resource root + */ + public boolean isSchemaResourceRoot() { + if (getId() != null) { + return true; + } + if (getParentSchema() == null) { + return true; + } + // The schema should not cross + return !Objects.equals(getSchemaLocation().getAbsoluteIri(), + getParentSchema().getSchemaLocation().getAbsoluteIri()); + } + + public String getId() { + return this.id; + } + + public Schema findAncestor() { + Schema ancestor = this; + if (this.getParentSchema() != null) { + ancestor = this.getParentSchema().findAncestor(); + } + return ancestor; + } + + private JsonNode handleNullNode(String ref, Schema schema) { + Schema subSchema = schema.fetchSubSchemaNode(this.schemaContext); + if (subSchema != null) { + return subSchema.getRefSchemaNode(ref); + } + return null; + } + + /** + * Please note that the key in {@link #validators} map is the evaluation path. + */ + private List read(JsonNode schemaNode) { + List validators; + if (schemaNode.isBoolean()) { + validators = new ArrayList<>(1); + if (schemaNode.booleanValue()) { + KeywordValidator validator = this.schemaContext.newValidator(getSchemaLocation().append("true"), + "true", schemaNode, this); + validators.add(validator); + } else { + KeywordValidator validator = this.schemaContext.newValidator(getSchemaLocation().append("false"), + "false", schemaNode, this); + validators.add(validator); + } + } else { + KeywordValidator refValidator = null; + + Iterator> iterator = schemaNode.fields(); + validators = new ArrayList<>(schemaNode.size()); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + String pname = entry.getKey(); + JsonNode nodeToUse = entry.getValue(); + + SchemaLocation schemaPath = getSchemaLocation().append(pname); + + if ("$recursiveAnchor".equals(pname)) { + if (!nodeToUse.isBoolean()) { + Error error = Error.builder().keyword("$recursiveAnchor") + .messageKey("internal.invalidRecursiveAnchor") + .message( + "The value of a $recursiveAnchor must be a Boolean literal but is {0}") + .instanceLocation(null) + .evaluationPath(null) + .schemaLocation(schemaPath) + .arguments(nodeToUse.getNodeType().toString()) + .build(); + throw new SchemaException(error); + } + this.recursiveAnchor = nodeToUse.booleanValue(); + } + + KeywordValidator validator = this.schemaContext.newValidator(schemaPath, + pname, nodeToUse, this); + if (validator != null) { + validators.add(validator); + if ("unevaluatedProperties".equals(pname)) { + this.unevaluatedPropertiesPresent = true; + } else if ("unevaluatedItems".equals(pname)) { + this.unevaluatedItemsPresent = true; + } + + if ("$ref".equals(pname)) { + refValidator = validator; + } + } + + } + + // Ignore siblings for older drafts + if (null != refValidator && getSchemaContext().getDialect().getSpecificationVersion().getOrder() < DRAFT_2019_09_VALUE) { + validators.clear(); + validators.add(refValidator); + } + } + if (validators.size() > 1) { + Collections.sort(validators, VALIDATOR_SORT); + } + return validators; + } + + /** + * A comparator that sorts validators, such that 'properties' comes before 'required', + * so that we can apply default values before validating required. + */ + private static final Comparator VALIDATOR_SORT = (lhs, rhs) -> { + String lhsName = lhs.getKeyword(); + String rhsName = rhs.getKeyword(); + + if (lhsName.equals(rhsName)) return 0; + + // Discriminator needs to run first to set state in the execution context + if (lhsName.equals("discriminator")) return -1; + if (rhsName.equals("discriminator")) return 1; + + if (lhsName.equals("type")) return -1; + if (rhsName.equals("type")) return 1; + + if (lhsName.equals("properties")) return -1; + if (rhsName.equals("properties")) return 1; + if (lhsName.equals("patternProperties")) return -1; + if (rhsName.equals("patternProperties")) return 1; + if (lhsName.equals("unevaluatedItems")) return 1; + if (rhsName.equals("unevaluatedItems")) return -1; + if (lhsName.equals("unevaluatedProperties")) return 1; + if (rhsName.equals("unevaluatedProperties")) return -1; + + return 0; // retain original schema definition order + }; + + /************************ START OF VALIDATE METHODS **********************************/ + + @Override + public void validate(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, NodePath instanceLocation) { + List validators = getValidators(); // Load the validators before checking the flags + executionContext.evaluationSchema.addLast(this); + boolean unevaluatedPropertiesPresent = executionContext.unevaluatedPropertiesPresent; + boolean unevaluatedItemsPresent = executionContext.unevaluatedItemsPresent; + if (this.unevaluatedPropertiesPresent) { + executionContext.unevaluatedPropertiesPresent = this.unevaluatedPropertiesPresent; + } + if (this.unevaluatedItemsPresent) { + executionContext.unevaluatedItemsPresent = this.unevaluatedItemsPresent; + } + try { + int currentErrors = executionContext.getErrors().size(); + for (KeywordValidator v : validators) { + executionContext.evaluationPathAddLast(v.getKeyword()); + executionContext.evaluationSchemaPath.addLast(v.getKeyword()); + try { + v.validate(executionContext, jsonNode, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationSchemaPath.removeLast(); + } + } + if (executionContext.getErrors().size() > currentErrors) { + // Failed with assertion set result and drop all annotations from this schema + // and all subschemas + List annotations = executionContext.getAnnotations().asMap().get(instanceLocation); + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.getEvaluationPath().startsWith(executionContext.getEvaluationPath())) { + annotation.setValid(false); + } + } + } + + //executionContext.getInstanceResults().setResult(instanceLocation, getSchemaLocation(), executionContext.getEvaluationPath(), false); + } + } finally { + executionContext.evaluationSchema.removeLast(); + executionContext.unevaluatedPropertiesPresent = unevaluatedPropertiesPresent; + executionContext.unevaluatedItemsPresent = unevaluatedItemsPresent; + } + } + + /** + * Validate the given root JsonNode, starting at the root of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param rootNode the root node + * @return A list of Error if there is any validation error, or an + * empty list if there is no error. + */ + public List validate(JsonNode rootNode) { + return validate(rootNode, OutputFormat.DEFAULT); + } + + /** + * Validate the given root JsonNode, starting at the root of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param rootNode the root node + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(JsonNode rootNode, ExecutionContextCustomizer executionCustomizer) { + return validate(rootNode, OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validate the given root JsonNode, starting at the root of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param rootNode the root node + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(JsonNode rootNode, Consumer executionCustomizer) { + return validate(rootNode, OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validates the given root JsonNode, starting at the root of the data path. The + * output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param rootNode the root node + * @param format the formatter + * @return the result + */ + public T validate(JsonNode rootNode, OutputFormat format) { + return validate(rootNode, format, (ExecutionContextCustomizer) null); + } + + /** + * Validates the given root JsonNode, starting at the root of the data path. The + * output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param rootNode the root node + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(JsonNode rootNode, OutputFormat format, ExecutionContextCustomizer executionCustomizer) { + return validate(createExecutionContext(), rootNode, format, executionCustomizer); + } + + /** + * Validates the given root JsonNode, starting at the root of the data path. The + * output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param rootNode the root node + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(JsonNode rootNode, OutputFormat format, Consumer executionCustomizer) { + return validate(createExecutionContext(), rootNode, format, (executionContext, schemaContext) -> executionCustomizer.accept(executionContext)); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @return A list of Error if there is any validation error, or an + * empty list if there is no error. + */ + public List validate(String input, InputFormat inputFormat) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(String input, InputFormat inputFormat, ExecutionContextCustomizer executionCustomizer) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(String input, InputFormat inputFormat, Consumer executionCustomizer) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @return the result + */ + public T validate(String input, InputFormat inputFormat, OutputFormat format) { + return validate(deserialize(input, inputFormat), format, (ExecutionContextCustomizer) null); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(String input, InputFormat inputFormat, OutputFormat format, ExecutionContextCustomizer executionCustomizer) { + return validate(createExecutionContext(), deserialize(input, inputFormat), format, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(String input, InputFormat inputFormat, OutputFormat format, Consumer executionCustomizer) { + return validate(createExecutionContext(), deserialize(input, inputFormat), format, (executionContext, schemaContext) -> executionCustomizer.accept(executionContext)); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @return A list of Error if there is any validation error, or an + * empty list if there is no error. + */ + public List validate(AbsoluteIri input, InputFormat inputFormat) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(AbsoluteIri input, InputFormat inputFormat, ExecutionContextCustomizer executionCustomizer) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(AbsoluteIri input, InputFormat inputFormat, Consumer executionCustomizer) { + return validate(deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @return the result + */ + public T validate(AbsoluteIri input, InputFormat inputFormat, OutputFormat format) { + return validate(deserialize(input, inputFormat), format, (ExecutionContextCustomizer) null); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(AbsoluteIri input, InputFormat inputFormat, OutputFormat format, ExecutionContextCustomizer executionCustomizer) { + return validate(createExecutionContext(), deserialize(input, inputFormat), format, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(AbsoluteIri input, InputFormat inputFormat, OutputFormat format, Consumer executionCustomizer) { + return validate(createExecutionContext(), deserialize(input, inputFormat), format, (executionContext, schemaContext) -> executionCustomizer.accept(executionContext)); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @return A list of Error if there is any validation error, or an + * empty list if there is no error. + */ + public List validate(ExecutionContext executionContext, String input, InputFormat inputFormat) { + return validate(executionContext, deserialize(input, inputFormat), OutputFormat.DEFAULT); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(ExecutionContext executionContext, String input, InputFormat inputFormat, ExecutionContextCustomizer executionCustomizer) { + return validate(executionContext, deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validate the given input string using the input format, starting at the root + * of the data path. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @param executionCustomizer the execution customizer + * @return the assertions + */ + public List validate(ExecutionContext executionContext, String input, InputFormat inputFormat, Consumer executionCustomizer) { + return validate(executionContext, deserialize(input, inputFormat), OutputFormat.DEFAULT, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @return the result + */ + public T validate(ExecutionContext executionContext, String input, InputFormat inputFormat, OutputFormat format) { + return validate(executionContext, deserialize(input, inputFormat), format, (ExecutionContextCustomizer) null); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(ExecutionContext executionContext, String input, InputFormat inputFormat, OutputFormat format, ExecutionContextCustomizer executionCustomizer) { + return validate(executionContext, deserialize(input, inputFormat), format, executionCustomizer); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param executionContext the execution context + * @param input the input + * @param inputFormat the inputFormat + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(ExecutionContext executionContext, String input, InputFormat inputFormat, OutputFormat format, Consumer executionCustomizer) { + return validate(executionContext, deserialize(input, inputFormat), format, (execContext, schemaContext) -> executionCustomizer.accept(execContext)); + } + + /** + * Validates to a format. + * + * @param the result type + * @param executionContext the execution context + * @param node the node + * @param format the format + * @return the result + */ + public T validate(ExecutionContext executionContext, JsonNode node, OutputFormat format) { + return validate(executionContext, node, format, (ExecutionContextCustomizer) null); + } + + /** + * Validates the given input string using the input format, starting at the root + * of the data path. The output will be formatted using the formatter specified. + *

+ * Note that since Draft 2019-09 by default format generates only annotations + * and not assertions. + *

+ * Use {@link ExecutionConfig.Builder#formatAssertionsEnabled(Boolean)} to override + * the default. + * + * @param the result type + * @param executionContext the execution context + * @param node the node + * @param format the formatter + * @param executionCustomizer the execution customizer + * @return the result + */ + public T validate(ExecutionContext executionContext, JsonNode node, OutputFormat format, Consumer executionCustomizer) { + return validate(executionContext, node, format, (execContext, schemaContext) -> executionCustomizer.accept(execContext)); + } + + /** + * Validates to a format. + * + * @param the result type + * @param executionContext the execution context + * @param node the node + * @param format the format + * @param executionCustomizer the customizer + * @return the result + */ + public T validate(ExecutionContext executionContext, JsonNode node, OutputFormat format, + ExecutionContextCustomizer executionCustomizer) { + format.customize(executionContext, this.schemaContext); + if (executionCustomizer != null) { + executionCustomizer.customize(executionContext, this.schemaContext); + } + try { + validate(executionContext, node); + } catch (FailFastAssertionException e) { + executionContext.setErrors(e.getErrors()); + } + return format.format(this, executionContext, this.schemaContext); + } + + /** + * Deserialize string to JsonNode. + * + * @param input the input + * @param inputFormat the format + * @return the JsonNode. + */ + private JsonNode deserialize(String input, InputFormat inputFormat) { + try { + return this.getSchemaContext().getSchemaRegistry().readTree(input, inputFormat); + } catch (IOException e) { + throw new UncheckedIOException("Invalid input", e); + } + } + + /** + * Loads the resource from the input iri and deserialize to JsonNode. + * + * @param input the input + * @param inputFormat the format + * @return the JsonNode. + */ + private JsonNode deserialize(AbsoluteIri input, InputFormat inputFormat) { + try { + InputStreamSource result = getInputResource(input); + if (result == null) { + throw new UncheckedIOException(new FileNotFoundException(input.toString() + " not found")); + } + try (InputStream inputStream = result.getInputStream()) { + return this.getSchemaContext().getSchemaRegistry().readTree(inputStream, inputFormat); + } + } catch (IOException e) { + throw new UncheckedIOException("Invalid input", e); + } + } + + /** + * Loads the resource from the input iri. + * + * @param input the input + * @return the input stream source + */ + private InputStreamSource getInputResource(AbsoluteIri input) { + InputStreamSource result = null; + List resourceLoaders = this.getSchemaContext().getSchemaRegistry().getSchemaLoader() + .getResourceLoaders(); + for (ResourceLoader loader : resourceLoaders) { + result = loader.getResource(input); + if (result != null) { + return result; + } + } + return ClasspathResourceLoader.getInstance().getResource(input); + } + + /************************ END OF VALIDATE METHODS **********************************/ + + /*********************** START OF WALK METHODS **********************************/ + + /** + * Walk the JSON node. + * + * @param executionContext the execution context + * @param node the input + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * + * @return the validation result + */ + public Result walk(ExecutionContext executionContext, JsonNode node, boolean validate, + ExecutionContextCustomizer executionCustomizer) { + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, OutputFormat.RESULT, + executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param the result type + * @param executionContext the execution context + * @param node the input + * @param outputFormat the output format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * + * @return the validation result + */ + public T walk(ExecutionContext executionContext, JsonNode node, OutputFormat outputFormat, boolean validate, + ExecutionContextCustomizer executionCustomizer) { + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, outputFormat, executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param executionContext the execution context + * @param node the input + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * + * @return the validation result + */ + public Result walk(ExecutionContext executionContext, JsonNode node, boolean validate, + Consumer executionCustomizer) { + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, OutputFormat.RESULT, + executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param the result type + * @param executionContext the execution context + * @param node the input + * @param outputFormat the output format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * + * @return the validation result + */ + public T walk(ExecutionContext executionContext, JsonNode node, OutputFormat outputFormat, boolean validate, + Consumer executionCustomizer) { + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, outputFormat, executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param executionContext the execution context + * @param node the input + * @param validate true to validate the input against the schema + * + * @return the validation result + */ + public Result walk(ExecutionContext executionContext, JsonNode node, boolean validate) { + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, OutputFormat.RESULT, + (ExecutionContextCustomizer) null); + } + + /** + * Walk the input. + * + * @param executionContext the execution context + * @param input the input + * @param inputFormat the input format + * @param validate true to validate the input against the schema + * @return the validation result + */ + public Result walk(ExecutionContext executionContext, String input, InputFormat inputFormat, + boolean validate) { + JsonNode node = deserialize(input, inputFormat); + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, OutputFormat.RESULT, + (ExecutionContextCustomizer) null); + } + + /** + * Walk the input. + * + * @param the result type + * @param executionContext the execution context + * @param input the input + * @param inputFormat the input format + * @param outputFormat the output format + * @param validate true to validate the input against the schema + * @return the validation result + */ + public T walk(ExecutionContext executionContext, String input, InputFormat inputFormat, + OutputFormat outputFormat, boolean validate) { + JsonNode node = deserialize(input, inputFormat); + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, outputFormat, + (ExecutionContextCustomizer) null); + } + + /** + * Walk the input. + * + * @param executionContext the execution context + * @param input the input + * @param inputFormat the input format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public Result walk(ExecutionContext executionContext, String input, InputFormat inputFormat, + boolean validate, ExecutionContextCustomizer executionCustomizer) { + JsonNode node = deserialize(input, inputFormat); + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, OutputFormat.RESULT, executionCustomizer); + } + + /** + * Walk the input. + * + * @param the result type + * @param executionContext the execution context + * @param input the input + * @param inputFormat the input format + * @param outputFormat the output format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public T walk(ExecutionContext executionContext, String input, InputFormat inputFormat, + OutputFormat outputFormat, boolean validate, ExecutionContextCustomizer executionCustomizer) { + JsonNode node = deserialize(input, inputFormat); + return walkAtNodeInternal(executionContext, node, node, atRoot(), validate, outputFormat, executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param node the input + * @param validate true to validate the input against the schema + * @return the validation result + */ + public Result walk(JsonNode node, boolean validate) { + return walk(createExecutionContext(), node, validate); + } + + /** + * Walk the JSON node. + * + * @param node the input + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public Result walk(JsonNode node, boolean validate, ExecutionContextCustomizer executionCustomizer) { + return walk(createExecutionContext(), node, validate, executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param node the input + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public Result walk(JsonNode node, boolean validate, Consumer executionCustomizer) { + return walk(createExecutionContext(), node, validate, executionCustomizer); + } + + /** + * Walk the JSON node. + * + * @param the result type + * @param node the input + * @param validate true to validate the input against the schema + * @param outputFormat the output format + * @return the validation result + */ + public T walk(JsonNode node, OutputFormat outputFormat, boolean validate) { + return walk(createExecutionContext(), node, outputFormat, validate, (ExecutionContextCustomizer) null); + } + + /** + * Walk the input. + * + * @param input the input + * @param inputFormat the input format + * @param validate true to validate the input against the schema + * @return the validation result + */ + public Result walk(String input, InputFormat inputFormat, boolean validate) { + return walk(createExecutionContext(), deserialize(input, inputFormat), validate); + } + + /** + * Walk the input. + * + * @param input the input + * @param inputFormat the input format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public Result walk(String input, InputFormat inputFormat, boolean validate, + ExecutionContextCustomizer executionCustomizer) { + return walk(createExecutionContext(), deserialize(input, inputFormat), validate, executionCustomizer); + } + + /** + * Walk the input. + * + * @param input the input + * @param inputFormat the input format + * @param validate true to validate the input against the schema + * @param executionCustomizer the customizer + * @return the validation result + */ + public Result walk(String input, InputFormat inputFormat, boolean validate, + Consumer executionCustomizer) { + return walk(createExecutionContext(), deserialize(input, inputFormat), validate, executionCustomizer); + } + + /** + * Walk at the node. + * + * @param executionContext the execution content + * @param node the current node + * @param rootNode the root node + * @param instanceLocation the instance location + * @param validate true to validate the input against the schema + * @return the validation result + */ + public Result walkAtNode(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean validate) { + return walkAtNodeInternal(executionContext, node, rootNode, instanceLocation, validate, OutputFormat.RESULT, + (ExecutionContextCustomizer) null); + } + + private T walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean validate, OutputFormat format, Consumer executionCustomizer) { + return walkAtNodeInternal(executionContext, node, rootNode, instanceLocation, validate, format, + (executeContext, schemaContext) -> executionCustomizer.accept(executeContext)); + } + + private T walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean validate, OutputFormat format, + ExecutionContextCustomizer executionCustomizer) { + if (executionCustomizer != null) { + executionCustomizer.customize(executionContext, this.schemaContext); + } + // Walk through the schema. + executionContext.evaluationPath = atRoot(); + walk(executionContext, node, rootNode, instanceLocation, validate); + return format.format(this, executionContext, this.schemaContext); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + // Walk through all the JSONWalker's. + List validators = getValidators(); // Load the validators before checking the flags + executionContext.evaluationSchema.addLast(this); + boolean unevaluatedPropertiesPresent = executionContext.unevaluatedPropertiesPresent; + boolean unevaluatedItemsPresent = executionContext.unevaluatedItemsPresent; + if (this.unevaluatedPropertiesPresent) { + executionContext.unevaluatedPropertiesPresent = this.unevaluatedPropertiesPresent; + } + if (this.unevaluatedItemsPresent) { + executionContext.unevaluatedItemsPresent = this.unevaluatedItemsPresent; + } + try { + int currentErrors = executionContext.getErrors().size(); + for (KeywordValidator validator : validators) { + try { + // Call all the pre-walk listeners. If at least one of the pre walk listeners + // returns SKIP, then skip the walk. + if (executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, + validator.getKeyword(), node, rootNode, instanceLocation, + this, validator)) { + executionContext.evaluationPathAddLast(validator.getKeyword()); + executionContext.evaluationSchemaPath.addLast(validator.getKeyword()); + try { + validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationSchemaPath.removeLast(); + } + } + } finally { + // Call all the post-walk listeners. + executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, + validator.getKeyword(), node, rootNode, instanceLocation, + this, validator, + executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); + } + } + } finally { + executionContext.evaluationSchema.removeLast(); + executionContext.unevaluatedPropertiesPresent = unevaluatedPropertiesPresent; + executionContext.unevaluatedItemsPresent = unevaluatedItemsPresent; + } + } + + /************************ END OF WALK METHODS **********************************/ + @Override + public String toString() { + return getSchemaNode().toString(); + } + + public List getValidators() { + if (this.validators == null) { + this.validators = Collections.unmodifiableList(read(getSchemaNode())); + } + return this.validators; + } + + /** + * Initializes the validators' {@link com.networknt.schema.Schema} instances. + * For avoiding issues with concurrency, in 1.0.49 the {@link com.networknt.schema.Schema} instances affiliated with + * validators were modified to no more preload the schema and lazy loading is used instead. + *

This comes with the issue that this way you cannot rely on validating important schema features, in particular + * $ref resolution at instantiation from {@link com.networknt.schema.SchemaRegistry}.

+ *

By calling initializeValidators you can enforce preloading of the {@link com.networknt.schema.Schema} + * instances of the validators.

+ */ + public void initializeValidators() { + if (!this.validatorsLoaded) { + /* + * This is set to true here to prevent recursive cyclic loading of the validators + */ + this.validatorsLoaded = true; + try { + for (final KeywordValidator validator : getValidators()) { + validator.preloadSchema(); + } + } catch (RuntimeException e) { + /* + * As the preload may throw an exception for + * instance if the remote host is unavailable and we may want to be able to try + * again. + */ + this.validatorsLoaded = false; + throw e; + } + } + } + + public boolean isRecursiveAnchor() { + return this.recursiveAnchor; + } + + /** + * Creates an execution context. + * + * @return the execution context + */ + public ExecutionContext createExecutionContext() { + SchemaRegistryConfig config = schemaContext.getSchemaRegistryConfig(); + // Copy execution config defaults from validation config + ExecutionConfig executionConfig = ExecutionConfig.builder() + .locale(config.getLocale()) + .formatAssertionsEnabled(config.getFormatAssertionsEnabled()) + .failFast(config.isFailFast()).build(); + ExecutionContext executionContext = new ExecutionContext(executionConfig); + if(config.getExecutionContextCustomizer() != null) { + config.getExecutionContextCustomizer().customize(executionContext, schemaContext); + } + return executionContext; + } +} diff --git a/src/main/java/com/networknt/schema/SchemaContext.java b/src/main/java/com/networknt/schema/SchemaContext.java new file mode 100644 index 000000000..783ff91ac --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaContext.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.keyword.KeywordValidator; + +/** + * The schema context associated with a schema and all its validators. + */ +public class SchemaContext { + private final Dialect dialect; + private final SchemaRegistry schemaRegistry; + private final ConcurrentMap schemaReferences; + private final ConcurrentMap schemaResources; + private final ConcurrentMap dynamicAnchors; + + /** + * When set to true, support for discriminators is enabled for validations of + * oneOf, anyOf and allOf as described on GitHub. + * + * When enabled, the validation of anyOf and allOf in + * polymorphism will respect OpenAPI 3 style discriminators as described in the + * OpenAPI + * 3.0.3 spec. The presence of a discriminator configuration on the schema + * will lead to the following changes in the behavior: + *
    + *
  • for oneOf the spec is unfortunately very vague. Whether + * oneOf semantics should be affected by discriminators or not is + * not even 100% clear within the members of the OAS steering committee. + * Therefore oneOf at the moment ignores discriminators
  • + *
  • for anyOf the validation will choose one of the candidate + * schemas for validation based on the discriminator property value and will + * pass validation when this specific schema passes. This is in particular + * useful when the payload could match multiple candidates in the + * anyOf list and could lead to ambiguity. Example: type B has all + * mandatory properties of A and adds more mandatory ones. Whether the payload + * is an A or B is determined via the discriminator property name. A payload + * indicating it is an instance of B then requires passing the validation of B + * and passing the validation of A would not be sufficient anymore.
  • + *
  • for allOf use cases with discriminators defined on the + * copied-in parent type, it is possible to automatically validate against a + * subtype. Example: some schema specifies that there is a field of type A. A + * carries a discriminator field and B inherits from A. Then B is automatically + * a candidate for validation as well and will be chosen in case the + * discriminator property matches
  • + *
+ * + * @param openAPI3StyleDiscriminators whether discriminators should be used. + * Defaults to false + * @since 1.0.51 + */ + private final boolean discriminatorKeywordEnabled; + + /** + * When a field is set as nullable in the OpenAPI specification, the schema + * validator validates that it is nullable however continues with validation + * against the nullable field + *

+ * If handleNullableField is set to true && incoming field is nullable && value + * is field: null --> succeed If handleNullableField is set to false && incoming + * field is nullable && value is field: null --> it is up to the type validator + * using the SchemaValidator to handle it. + */ + private final boolean nullableKeywordEnabled; + + public SchemaContext(Dialect dialect, + SchemaRegistry schemaRegistry) { + this(dialect, schemaRegistry, new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>()); + } + + public SchemaContext(Dialect dialect, SchemaRegistry schemaRegistry, + ConcurrentMap schemaReferences, + ConcurrentMap schemaResources, ConcurrentMap dynamicAnchors) { + if (dialect == null) { + throw new IllegalArgumentException("Dialect must not be null"); + } + if (schemaRegistry == null) { + throw new IllegalArgumentException("SchemaRegistry must not be null"); + } + this.dialect = dialect; + this.schemaRegistry = schemaRegistry; + this.schemaReferences = schemaReferences; + this.schemaResources = schemaResources; + this.dynamicAnchors = dynamicAnchors; + + if (dialect.getKeywords().containsKey("discriminator")) { + this.discriminatorKeywordEnabled = true; + } else { + this.discriminatorKeywordEnabled = false; + } + + if (dialect.getKeywords().containsKey("nullable")) { + this.nullableKeywordEnabled = true; + } else { + this.nullableKeywordEnabled = false; + } + } + + public Schema newSchema(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema) { + return getSchemaRegistry().create(this, schemaLocation, schemaNode, parentSchema); + } + + public KeywordValidator newValidator(SchemaLocation schemaLocation, + String keyword /* keyword */, JsonNode schemaNode, Schema parentSchema) { + return this.dialect.newValidator(this, schemaLocation, keyword, schemaNode, parentSchema); + } + + public String resolveSchemaId(JsonNode schemaNode) { + return this.dialect.readId(schemaNode); + } + + public SchemaRegistry getSchemaRegistry() { + return this.schemaRegistry; + } + + public SchemaRegistryConfig getSchemaRegistryConfig() { + return this.schemaRegistry.getSchemaRegistryConfig(); + } + + /** + * Gets the schema references identified by the ref uri. + * + * @return the schema references + */ + public ConcurrentMap getSchemaReferences() { + return this.schemaReferences; + } + + /** + * Gets the schema resources identified by id. + * + * @return the schema resources + */ + public ConcurrentMap getSchemaResources() { + return this.schemaResources; + } + + /** + * Gets the dynamic anchors. + * + * @return the dynamic anchors + */ + public ConcurrentMap getDynamicAnchors() { + return this.dynamicAnchors; + } + + public Dialect getDialect() { + return this.dialect; + } + + public boolean isDiscriminatorKeywordEnabled() { + return discriminatorKeywordEnabled; + } + + public boolean isNullableKeywordEnabled() { + return nullableKeywordEnabled; + } +} diff --git a/src/main/java/com/networknt/schema/SchemaException.java b/src/main/java/com/networknt/schema/SchemaException.java new file mode 100644 index 000000000..fe94a0ef3 --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaException.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import java.util.Collections; +import java.util.List; + +/** + * Represents an error when processing the Schema. + */ +public class SchemaException extends RuntimeException { + private static final long serialVersionUID = -7805792737596582110L; + private final Error error; + + public SchemaException(Error error) { + this.error = error; + } + + public SchemaException(String message) { + super(message); + this.error = null; + } + + public SchemaException(Throwable throwable) { + super(throwable); + this.error = null; + } + + @Override + public String getMessage() { + return this.error != null ? this.error.getMessage() : super.getMessage(); + } + + public Error getError() { + return this.error; + } + + public List getErrors() { + if (error == null) { + return Collections.emptyList(); + } + return Collections.singletonList(error); + } +} diff --git a/src/main/java/com/networknt/schema/SchemaIdValidator.java b/src/main/java/com/networknt/schema/SchemaIdValidator.java new file mode 100644 index 000000000..723136ccb --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaIdValidator.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Validator for validating the correctness of $id. + */ +public interface SchemaIdValidator { + /** + * Validates if the $id value is valid. + * + * @param id the $id or id + * @param rootSchema true if this is a root schema + * @param schemaLocation the schema location + * @param resolvedSchemaLocation the schema location after resolving with the id + * @param schemaContext the schema context for instance to get the + * meta schema + * @return true if valid + */ + boolean validate(String id, boolean rootSchema, SchemaLocation schemaLocation, + SchemaLocation resolvedSchemaLocation, SchemaContext schemaContext); + + SchemaIdValidator DEFAULT = new DefaultSchemaIdValidator(); + + /** + * Implementation of {@link SchemaIdValidator}. + *

+ * Note that this does not strictly follow the specification. + *

+ * This allows an $id that isn't an absolute-IRI on the root schema, but it must + * resolve to an absolute-IRI given a base-IRI. + *

+ * This also allows non-empty fragments. + */ + class DefaultSchemaIdValidator implements SchemaIdValidator { + @Override + public boolean validate(String id, boolean rootSchema, SchemaLocation schemaLocation, + SchemaLocation resolvedSchemaLocation, SchemaContext schemaContext) { + if (hasNoContext(schemaLocation)) { + // The following are non-standard + if (isFragment(id) || startsWithSlash(id)) { + return true; + } + } + return resolvedSchemaLocation.getAbsoluteIri() != null + && isAbsoluteIri(resolvedSchemaLocation.getAbsoluteIri().toString()); + } + + protected boolean startsWithSlash(String id) { + return id.startsWith("/"); + } + + protected boolean isFragment(String id) { + return id.startsWith("#"); + } + + protected boolean hasNoContext(SchemaLocation schemaLocation) { + return schemaLocation.getAbsoluteIri() == null || schemaLocation.toString().startsWith("#"); + } + + protected boolean isAbsoluteIri(String iri) { + if (!iri.contains(":")) { + return false; // quick check + } + try { + new URI(iri); + } catch (URISyntaxException e) { + return false; + } + return true; + } + } +} diff --git a/src/main/java/com/networknt/schema/SchemaLocation.java b/src/main/java/com/networknt/schema/SchemaLocation.java new file mode 100644 index 000000000..82f7cbe7c --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaLocation.java @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import com.networknt.schema.path.NodePath; +import com.networknt.schema.path.PathType; +import com.networknt.schema.utils.Strings; + +/** + * The schema location is the canonical IRI of the schema object plus a JSON + * Pointer fragment indicating the subschema that produced a result. In contrast + * with the evaluation path, the schema location MUST NOT include by-reference + * applicators such as $ref or $dynamicRef. + */ +public class SchemaLocation { + private static final NodePath JSON_POINTER = new NodePath(PathType.JSON_POINTER); + private static final NodePath ANCHOR = new NodePath(PathType.URI_REFERENCE); + + /** + * Represents a relative schema location to the current document. + */ + public static final SchemaLocation DOCUMENT = new SchemaLocation(null, JSON_POINTER); + + private final AbsoluteIri absoluteIri; + private final NodePath fragment; + + private volatile String value = null; // computed lazily + + /** + * Constructs a new {@link SchemaLocation}. + * + * @param absoluteIri canonical absolute IRI of the schema object + * @param fragment the fragment + */ + public SchemaLocation(AbsoluteIri absoluteIri, NodePath fragment) { + this.absoluteIri = absoluteIri; + this.fragment = fragment; + } + + /** + * Constructs a new {@link SchemaLocation}. + * + * @param absoluteIri canonical absolute IRI of the schema object + */ + public SchemaLocation(AbsoluteIri absoluteIri) { + this(absoluteIri, JSON_POINTER); + } + + /** + * Gets the canonical absolute IRI of the schema object. + *

+ * This is a unique identifier indicated by the $id property or id property in + * Draft 4 and earlier. This does not have to be network accessible. + * + * @return the canonical absolute IRI of the schema object. + */ + public AbsoluteIri getAbsoluteIri() { + return this.absoluteIri; + } + + /** + * Gets the fragment. + * + * @return the fragment + */ + public NodePath getFragment() { + return this.fragment; + } + + /** + * Appends the token to the fragment. + * + * @param token the segment + * @return a new schema location with the segment + */ + public SchemaLocation append(String token) { + return new SchemaLocation(this.absoluteIri, this.fragment.append(token)); + } + + /** + * Appends the index to the fragment. + * + * @param index the segment + * @return a new schema location with the segment + */ + public SchemaLocation append(int index) { + return new SchemaLocation(this.absoluteIri, this.fragment.append(index)); + } + + /** + * Parses a string representing an IRI of the schema location. + * + * @param iri the IRI + * @return the schema location + */ + public static SchemaLocation of(String iri) { + if (iri == null) { + return null; + } + if ("#".equals(iri)) { + return DOCUMENT; + } + AbsoluteIri absoluteIri = null; + NodePath fragment = JSON_POINTER; + int index = iri.indexOf('#'); + if (index == -1) { + absoluteIri = AbsoluteIri.of(iri); + } else { + absoluteIri = AbsoluteIri.of(iri.substring(0, index)); + if (iri.length() > index + 1) { + fragment = Fragment.of(iri.substring(index + 1)); + } + } + return new SchemaLocation(absoluteIri, fragment); + } + + /** + * Resolves against a absolute IRI reference or fragment. + * + * @param absoluteIriReferenceOrFragment to resolve + * @return the resolved schema location + */ + public SchemaLocation resolve(String absoluteIriReferenceOrFragment) { + if (absoluteIriReferenceOrFragment == null) { + return this; + } + if ("#".equals(absoluteIriReferenceOrFragment)) { + return new SchemaLocation(this.getAbsoluteIri(), JSON_POINTER); + } + NodePath fragment = JSON_POINTER; + int index = absoluteIriReferenceOrFragment.indexOf('#'); + AbsoluteIri absoluteIri = this.getAbsoluteIri(); + String part0 = index == -1 ? absoluteIriReferenceOrFragment + : absoluteIriReferenceOrFragment.substring(0, index); + if (absoluteIri != null) { + if (!part0.isEmpty()) { + absoluteIri = absoluteIri.resolve(part0); + } + } else { + absoluteIri = AbsoluteIri.of(part0); + } + if (index != -1) { + if (absoluteIriReferenceOrFragment.length() > index + 1) { + String part1 = absoluteIriReferenceOrFragment.substring(index + 1); + if (!part1.isEmpty()) { + fragment = Fragment.of(part1); + } + } + } + return new SchemaLocation(absoluteIri, fragment); + } + + /** + * Resolves against a absolute IRI reference or fragment. + * + * @param schemaLocation the parent + * @param absoluteIriReferenceOrFragment to resolve + * @return the resolved schema location + */ + public static String resolve(SchemaLocation schemaLocation, String absoluteIriReferenceOrFragment) { + if ("#".equals(absoluteIriReferenceOrFragment)) { + return schemaLocation.getAbsoluteIri().toString() + "#"; + } + int index = absoluteIriReferenceOrFragment.indexOf('#'); + AbsoluteIri absoluteIri = schemaLocation.getAbsoluteIri(); + String part0 = index == -1 ? absoluteIriReferenceOrFragment + : absoluteIriReferenceOrFragment.substring(0, index); + String resolved = part0; + if (absoluteIri != null) { + if (!part0.isEmpty()) { + resolved = absoluteIri.resolve(part0).toString(); + } else { + resolved = absoluteIri.toString(); + } + } + String part1 = ""; + if (index != -1) { + if (absoluteIriReferenceOrFragment.length() > index + 1) { + part1 = absoluteIriReferenceOrFragment.substring(index + 1); + } + } + if (!part1.isEmpty()) { + resolved = resolved + "#" + part1; + } else { + resolved = resolved + "#"; + } + return resolved; + } + + /** + * The fragment can be a JSON pointer to the document or an anchor. + */ + public static class Fragment { + /** + * Parses a string representing a fragment. + * + * @param fragmentString the fragment + * @return the path + */ + public static NodePath of(String fragmentString) { + if (fragmentString.startsWith("#")) { + fragmentString = fragmentString.substring(1); + } + NodePath fragment = JSON_POINTER; +// String[] fragmentParts = fragmentString.split("/"); + String[] fragmentParts = Strings.split(fragmentString, '/'); + + boolean jsonPointer = false; + if (fragmentString.startsWith("/")) { + // json pointer + jsonPointer = true; + } else { + // anchor + fragment = ANCHOR; + } + + int index = -1; + for (int fragmentPartIndex = 0; fragmentPartIndex < fragmentParts.length; fragmentPartIndex++) { + if (fragmentPartIndex == 0 && jsonPointer) { + continue; + } + String fragmentPart = fragmentParts[fragmentPartIndex]; + for (int x = 0; x < fragmentPart.length(); x++) { + char ch = fragmentPart.charAt(x); + if (ch >= '0' && ch <= '9') { + if (x == 0) { + index = 0; + } else { + index = index * 10; + } + index += (ch - '0'); + } else { + index = -1; // Not an index + break; + } + } + if (index != -1) { + fragment = fragment.append(index); + } else { + String fragmentPartString = fragmentPart; + if (PathType.JSON_POINTER.equals(fragment.getPathType())) { + if (fragmentPartString.contains("~")) { + fragmentPartString = fragmentPartString.replace("~1", "/"); + fragmentPartString = fragmentPartString.replace("~0", "~"); + } + if (fragmentPartString.contains("%")) { + try { + fragmentPartString = URLDecoder.decode(fragmentPartString, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + // Do nothing + } + } + } + fragment = fragment.append(fragmentPartString); + } + } + return fragment; + } + + /** + * Determine if the string is a fragment. + * + * @param fragmentString to evaluate + * @return true if it is a fragment + */ + public static boolean isFragment(String fragmentString) { + return fragmentString.startsWith("#"); + } + + /** + * Determine if the string is a JSON Pointer fragment. + * + * @param fragmentString to evaluate + * @return true if it is a JSON Pointer fragment + */ + public static boolean isJsonPointerFragment(String fragmentString) { + return fragmentString.startsWith("#/"); + } + + /** + * Determine if the string is an anchor fragment. + * + * @param fragmentString to evaluate + * @return true if it is an anchor fragment + */ + public static boolean isAnchorFragment(String fragmentString) { + return isFragment(fragmentString) && !isDocumentFragment(fragmentString) + && !isJsonPointerFragment(fragmentString); + } + + /** + * Determine if the string is a fragment referencing the document. + * + * @param fragmentString to evaluate + * @return true if it is a fragment + */ + public static boolean isDocumentFragment(String fragmentString) { + return "#".equals(fragmentString); + } + } + + /** + * Returns a builder for building {@link SchemaLocation}. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for building {@link SchemaLocation}. + */ + public static class Builder { + private AbsoluteIri absoluteIri; + private NodePath fragment = JSON_POINTER; + + /** + * Sets the canonical absolute IRI of the schema object. + *

+ * This is a unique identifier indicated by the $id property or id property in + * Draft 4 and earlier. This does not have to be network accessible. + * + * @param absoluteIri the canonical IRI of the schema object + * @return the builder + */ + protected Builder absoluteIri(AbsoluteIri absoluteIri) { + this.absoluteIri = absoluteIri; + return this; + } + + /** + * Sets the canonical absolute IRI of the schema object. + *

+ * This is a unique identifier indicated by the $id property or id property in + * Draft 4 and earlier. This does not have to be network accessible. + * + * @param absoluteIri the canonical IRI of the schema object + * @return the builder + */ + protected Builder absoluteIri(String absoluteIri) { + return absoluteIri(AbsoluteIri.of(absoluteIri)); + } + + /** + * Sets the fragment. + * + * @param fragment the fragment + * @return the builder + */ + protected Builder fragment(NodePath fragment) { + this.fragment = fragment; + return this; + } + + /** + * Sets the fragment. + * + * @param fragment the fragment + * @return the builder + */ + protected Builder fragment(String fragment) { + return fragment(Fragment.of(fragment)); + } + + /** + * Builds a {@link SchemaLocation}. + * + * @return the schema location + */ + public SchemaLocation build() { + return new SchemaLocation(absoluteIri, fragment); + } + + } + + @Override + public String toString() { + if (this.value == null) { + if (this.absoluteIri != null && this.fragment == null) { + this.value = this.absoluteIri.toString(); + } else { + StringBuilder result = new StringBuilder(); + if (this.absoluteIri != null) { + result.append(this.absoluteIri); + } + result.append("#"); + if (this.fragment != null) { + result.append(this.fragment); + } + this.value = result.toString(); + } + } + return this.value; + } + + @Override + public int hashCode() { + return Objects.hash(fragment, absoluteIri); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SchemaLocation other = (SchemaLocation) obj; + return Objects.equals(fragment, other.fragment) && Objects.equals(absoluteIri, other.absoluteIri); + } +} diff --git a/src/main/java/com/networknt/schema/SchemaRef.java b/src/main/java/com/networknt/schema/SchemaRef.java new file mode 100644 index 000000000..e3193d9ce --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaRef.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.function.Supplier; + +/** + * Use this object instead a Schema for references. + */ +public class SchemaRef { + + private final Supplier schemaSupplier; + + public SchemaRef(Supplier schema) { + this.schemaSupplier = schema; + } + + public Schema getSchema() { + return this.schemaSupplier.get(); + } +} diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java new file mode 100644 index 000000000..0b8af9432 --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -0,0 +1,829 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.BasicDialectRegistry; +import com.networknt.schema.dialect.DefaultDialectRegistry; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.DialectId; +import com.networknt.schema.dialect.DialectRegistry; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.ResourceLoaders; +import com.networknt.schema.resource.SchemaIdResolvers; +import com.networknt.schema.resource.SchemaLoader; +import com.networknt.schema.serialization.BasicNodeReader; +import com.networknt.schema.serialization.DefaultNodeReader; +import com.networknt.schema.serialization.NodeReader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Registry for loading and registering {@link Schema} instances. + *

+ * This can be created with withDefaultDialect(Version) or withDialect(Dialect). + *

+ * The registry should be cached for performance. + *

+ * A different registry should be used when loading unrelated schemas. + *

+ * SchemaRegistry instances are thread-safe provided its configuration is not + * modified. + */ +public class SchemaRegistry { + private static final Logger logger = LoggerFactory.getLogger(SchemaRegistry.class); + + public static class Builder { + private String defaultDialectId; + private DialectRegistry dialectRegistry = null; + private NodeReader nodeReader = null; + private SchemaLoader schemaLoader = null; + private boolean schemaCacheEnabled = true; + private SchemaRegistryConfig schemaRegistryConfig = null; + + /** + * Sets the json node reader to read the data. + *

+ * A location aware object reader can be created using + * NodeReader.builder().locationAware().build(). + * + * @param nodeReader the object reader + * @return the builder + */ + public Builder nodeReader(NodeReader nodeReader) { + this.nodeReader = nodeReader; + return this; + } + + /** + * Sets the json node reader to read the data. + * + *

+         * A location aware object reader can be created using
+         * schemaRegistryBuilder.nodeReader(nodeReader -> nodeReader.locationAware()).
+         * 
+ * + * A json ObjectMapper can be set using + * + *
+         * schemaRegistryBuilder.nodeReader(nodeReader -> nodeReader.jsonMapper(objectMapper))
+         * 
+ * + * @param customizer + * @return the builder + */ + public Builder nodeReader(Consumer customizer) { + DefaultNodeReader.Builder builder = NodeReader.builder(); + customizer.accept(builder); + this.nodeReader = builder.build(); + return this; + } + + public Builder defaultDialectId(String defaultDialectId) { + this.defaultDialectId = defaultDialectId; + return this; + } + + public Builder dialectRegistry(DialectRegistry dialectRegistry) { + this.dialectRegistry = dialectRegistry; + return this; + } + + public Builder schemaCacheEnabled(boolean schemaCacheEnabled) { + this.schemaCacheEnabled = schemaCacheEnabled; + return this; + } + + public Builder schemaLoader(SchemaLoader schemaLoader) { + this.schemaLoader = schemaLoader; + return this; + } + + public Builder schemaLoader(Consumer customizer) { + SchemaLoader.Builder builder = null; + if (this.schemaLoader != null) { + builder = SchemaLoader.builder(this.schemaLoader); + } else { + builder = SchemaLoader.builder(); + } + customizer.accept(builder); + this.schemaLoader = builder.build(); + return this; + } + + public Builder resourceLoaders(Consumer customizer) { + SchemaLoader.Builder builder = null; + if (this.schemaLoader != null) { + builder = SchemaLoader.builder(this.schemaLoader); + } else { + builder = SchemaLoader.builder(); + } + customizer.accept(builder.getResourceLoadersBuilder()); + this.schemaLoader = builder.build(); + return this; + } + + public Builder schemaIdResolvers(Consumer customizer) { + SchemaLoader.Builder builder = null; + if (this.schemaLoader != null) { + builder = SchemaLoader.builder(this.schemaLoader); + } else { + builder = SchemaLoader.builder(); + } + customizer.accept(builder.getSchemaIdResolversBuilder()); + this.schemaLoader = builder.build(); + return this; + } + + public Builder schemaRegistryConfig(SchemaRegistryConfig schemaRegistryConfig) { + this.schemaRegistryConfig = schemaRegistryConfig; + return this; + } + + /** + * Sets the schema data by absolute IRI. + * + * @param schemas the map of IRI to schema data + * @return the builder + */ + public Builder schemas(Map schemas) { + return this.resourceLoaders(resourceLoaders -> resourceLoaders.resources(schemas)); + } + + /** + * Sets the schema data by absolute IRI function. + * + * @param schemas the function that returns schema data given IRI + * @return the builder + */ + public Builder schemas(Function schemas) { + return this.resourceLoaders(resourceLoaders -> resourceLoaders.resources(schemas)); + } + + /** + * Sets the schema data by using two mapping functions. + *

+ * Firstly to map the IRI to an object. If the object is null no mapping is + * performed. + *

+ * Next to map the object to the schema data. + * + * @param the type of the object + * @param mapIriToObject the mapping of IRI to object + * @param mapObjectToData the mappingof object to schema data + * @return the builder + */ + public Builder schemas(Function mapIriToObject, Function mapObjectToData) { + return this.resourceLoaders(resourceLoaders -> resourceLoaders.resources(mapIriToObject, mapObjectToData)); + } + + public SchemaRegistry build() { + return new SchemaRegistry(nodeReader, defaultDialectId, schemaLoader, schemaCacheEnabled, + dialectRegistry, schemaRegistryConfig); + } + } + + private final NodeReader nodeReader; + private final String defaultDialectId; + private final SchemaLoader schemaLoader; + private final ConcurrentMap schemaCache = new ConcurrentHashMap<>(); + private final boolean schemaCacheEnabled; + private final DialectRegistry dialectRegistry; + private final SchemaRegistryConfig schemaRegistryConfig; + + private SchemaRegistry(NodeReader nodeReader, String defaultDialectId, SchemaLoader schemaLoader, + boolean schemaCacheEnabled, DialectRegistry dialectRegistry, SchemaRegistryConfig schemaRegistryConfig) { + if (defaultDialectId == null || defaultDialectId.trim().isEmpty()) { + throw new IllegalArgumentException("defaultDialectId must not be null or empty"); + } + this.nodeReader = nodeReader != null ? nodeReader : BasicNodeReader.getInstance(); + this.defaultDialectId = defaultDialectId; + this.schemaLoader = schemaLoader != null ? schemaLoader : SchemaLoader.getDefault(); + this.schemaCacheEnabled = schemaCacheEnabled; + this.dialectRegistry = dialectRegistry != null ? dialectRegistry : new DefaultDialectRegistry(); + this.schemaRegistryConfig = schemaRegistryConfig != null ? schemaRegistryConfig + : SchemaRegistryConfig.getInstance(); + } + + /** + * Builder without keywords or formats. + *

+ * Typically {@link #builder(SchemaRegistry)} or + * {@link #withDefaultDialect(SpecificationVersion)} or + * {@link #withDialect(Dialect)} would be used instead. + * + * @return a builder instance without any keywords or formats - usually not what + * one needs. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new schema registry with a default schema dialect. The schema + * dialect will only be used if the input does not specify a $schema. + *

+ * This uses a dialect registry that contains all the supported standard + * specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft + * 2020-12. + * + * @param specificationVersion the default dialect id corresponding to the + * specification version used when the schema does + * not specify the $schema keyword + * @return the factory + */ + public static SchemaRegistry withDefaultDialect(SpecificationVersion specificationVersion) { + return withDefaultDialect(specificationVersion, null); + } + + /** + * Creates a new schema registry with a default schema dialect. The schema + * dialect will only be used if the input does not specify a $schema. + *

+ * This uses a dialect registry that contains all the supported standard + * specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft + * 2020-12. + * + * @param specificationVersion the default dialect id corresponding to the + * specification version used when the schema does + * not specify the $schema keyword + * @param customizer to customize the registry + * @return the factory + */ + public static SchemaRegistry withDefaultDialect(SpecificationVersion specificationVersion, + Consumer customizer) { + Dialect dialect = Specification.getDialect(specificationVersion); + return withDefaultDialectId(dialect.getId(), customizer); + } + + /** + * Creates a new schema registry with a default schema dialect. The schema + * dialect will only be used if the input does not specify a $schema. + *

+ * This uses a dialect registry that contains all the supported standard + * specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft + * 2020-12. + * + * @param dialectId the default dialect id used when the schema does not + * specify the $schema keyword + * @param customizer to customize the registry + * @return the factory + */ + public static SchemaRegistry withDefaultDialectId(String dialectId, Consumer customizer) { + SchemaRegistry.Builder builder = builder().defaultDialectId(dialectId); + if (customizer != null) { + customizer.accept(builder); + } + return builder.build(); + } + + + /** + * Creates a new schema registry with a default schema dialect. The schema + * dialect will only be used if the input does not specify a $schema. + *

+ * This uses a dialect registry that contains all the supported standard + * specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft + * 2020-12. + * + * @param dialect the default dialect used when the schema does not specify the + * $schema keyword + * @return the factory + */ + public static SchemaRegistry withDefaultDialect(Dialect dialect) { + return withDefaultDialect(dialect, null); + } + + /** + * Creates a new schema registry with a default schema dialect. The schema + * dialect will only be used if the input does not specify a $schema. + *

+ * This uses a dialect registry that contains all the supported standard + * specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft + * 2020-12. + * + * @param dialect the default dialect used when the schema does not specify + * the $schema keyword + * @param customizer to customize the registry + * @return the factory + */ + public static SchemaRegistry withDefaultDialect(Dialect dialect, Consumer customizer) { + SchemaRegistry.Builder builder = builder().defaultDialectId(dialect.getId()) + .dialectRegistry(new DefaultDialectRegistry(dialect)); + if (customizer != null) { + customizer.accept(builder); + } + return builder.build(); + } + + /** + * Gets a new schema registry that supports a specific dialect only. + *

+ * Schemas that do not specify dialect using $schema will use the dialect. + *

+ * This uses a dialect registry that only contains this dialect and will throw + * an exception for unknown dialects. + * + * @param dialect the dialect + * @return the schema registry + */ + public static SchemaRegistry withDialect(Dialect dialect) { + return withDialect(dialect, null); + } + + /** + * Gets a new schema registry that supports a list of specific dialects only. + *

+ * Schemas that do not specify dialect using $schema will use the first dialect + * on the list. + *

+ * This uses a dialect registry that only contains the list of dialects and will + * throw an exception for unknown dialects. + * + * @param dialect the dialect + * @param customizer to customize the registry + * @return the schema registry + */ + public static SchemaRegistry withDialect(Dialect dialect, Consumer customizer) { + SchemaRegistry.Builder builder = builder().defaultDialectId(dialect.getId()) + .dialectRegistry(new BasicDialectRegistry(dialect)); + if (customizer != null) { + customizer.accept(builder); + } + return builder.build(); + } + + /** + * Gets a new schema registry that supports a list of specific dialects only. + *

+ * Schemas that do not specify dialect using $schema will use the first dialect + * on the list. + *

+ * This uses a dialect registry that only contains the list of dialects and will + * throw an exception for unknown dialects. + * + * @param dialects the dialects with the first being the default dialect + * @return the schema registry + */ + public static SchemaRegistry withDialects(List dialects) { + return withDialects(dialects, null); + } + + /** + * Gets a new schema registry that supports a specific dialect only. + *

+ * Schemas that do not specify dialect using $schema will use the dialect. + *

+ * This uses a dialect registry that only contains this dialect and will throw + * an exception for unknown dialects. + * + * @param dialects the dialects with the first being the default dialect + * @param customizer to customize the registry + * @return the schema registry + */ + public static SchemaRegistry withDialects(List dialects, Consumer customizer) { + SchemaRegistry.Builder builder = builder().defaultDialectId(dialects.get(0).getId()) + .dialectRegistry(new BasicDialectRegistry(dialects)); + if (customizer != null) { + customizer.accept(builder); + } + return builder.build(); + } + + + /** + * Builder from an existing {@link SchemaRegistry}. + *

+ * + * SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09)); + * + * + * @param blueprint the existing factory + * @return the builder + */ + public static Builder builder(SchemaRegistry blueprint) { + Builder builder = builder().schemaLoader(blueprint.schemaLoader) + .defaultDialectId(blueprint.defaultDialectId) + .nodeReader(blueprint.nodeReader) + .dialectRegistry(blueprint.dialectRegistry) + .schemaRegistryConfig(blueprint.schemaRegistryConfig); + return builder; + } + + /** + * Gets the schema loader. + * + * @return the schema loader + */ + public SchemaLoader getSchemaLoader() { + return this.schemaLoader; + } + + /** + * Creates a schema from initial input. + * + * @param schemaUri the schema location + * @param schemaNode the schema data node + * @return the schema + */ + protected Schema newSchema(SchemaLocation schemaUri, JsonNode schemaNode) { + final SchemaContext schemaContext = createSchemaContext(schemaNode); + Schema jsonSchema = doCreate(schemaContext, getSchemaLocation(schemaUri), + schemaNode, null, false); + preload(jsonSchema); + return jsonSchema; + } + + /** + * Preloads the json schema if the configuration option is set. + * + * @param schema the schema to preload + * @param config containing the configuration option + */ + private void preload(Schema schema) { + if (this.getSchemaRegistryConfig().isPreloadSchema()) { + try { + /* + * Attempt to preload and resolve $refs for performance. + */ + schema.initializeValidators(); + } catch (Exception e) { + /* + * Do nothing here to allow the schema to be cached even if the remote $ref + * cannot be resolved at this time. If the developer wants to ensure that all + * remote $refs are currently resolvable they need to call initializeValidators + * themselves. + */ + } + } + } + + public Schema create(SchemaContext schemaContext, SchemaLocation schemaLocation, + JsonNode schemaNode, Schema parentSchema) { + return doCreate(schemaContext, schemaLocation, schemaNode, parentSchema, false); + } + + private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, + JsonNode schemaNode, Schema parentSchema, boolean suppressSubSchemaRetrieval) { + return Schema.from(withDialect(schemaContext, schemaNode), schemaLocation, schemaNode, + parentSchema, suppressSubSchemaRetrieval); + } + + /** + * Determines the schema context to use for the schema given the parent schema + * context. + *

+ * This is typically the same schema context unless the schema has a different + * $schema from the parent. + *

+ * If the schema does not define a $schema, the parent should be used. + * + * @param schemaContext the parent schema context + * @param schemaNode the schema node + * @return the schema context to use + */ + private SchemaContext withDialect(SchemaContext schemaContext, JsonNode schemaNode) { + Dialect dialect = getDialect(schemaNode, schemaContext.getSchemaRegistryConfig()); + if (dialect != null && !dialect.getId().equals(schemaContext.getDialect().getId())) { + return new SchemaContext(dialect, schemaContext.getSchemaRegistry(), schemaContext.getSchemaReferences(), + schemaContext.getSchemaResources(), schemaContext.getDynamicAnchors()); + } + return schemaContext; + } + + /** + * Gets the base IRI from the schema retrieval IRI if present otherwise return + * one with a null base IRI. + *

+ * Note that the resolving of the $id or id in the schema node will take place + * in the Schema constructor. + * + * @param schemaLocation the schema retrieval uri + * @return the schema location + */ + protected SchemaLocation getSchemaLocation(SchemaLocation schemaLocation) { + return schemaLocation != null ? schemaLocation : SchemaLocation.DOCUMENT; + } + + protected SchemaContext createSchemaContext(final JsonNode schemaNode) { + final Dialect dialect = getDialectOrDefault(schemaNode); + return new SchemaContext(dialect, this); + } + + private Dialect getDialect(final JsonNode schemaNode, SchemaRegistryConfig config) { + final JsonNode iriNode = schemaNode.get("$schema"); + if (iriNode != null && iriNode.isTextual()) { + return getDialect(iriNode.textValue()); + } + return null; + } + + private Dialect getDialectOrDefault(final JsonNode schemaNode) { + final JsonNode iriNode = schemaNode.get("$schema"); + if (iriNode != null && !iriNode.isNull() && !iriNode.isTextual()) { + throw new SchemaException("Unknown dialect: " + iriNode); + } + final String iri = iriNode == null || iriNode.isNull() ? defaultDialectId : iriNode.textValue(); + return getDialect(iri); + } + + /** + * Gets the dialect that is available to the registry. + * + * @param dialectId the IRI of the meta-schema + * @return the meta-schema + */ + public Dialect getDialect(String dialectId) { + String key = normalizeDialectId(dialectId); + return dialectRegistry.getDialect(key, this); + } + + JsonNode readTree(String content, InputFormat inputFormat) throws IOException { + return this.nodeReader.readTree(content, inputFormat); + } + + JsonNode readTree(InputStream content, InputFormat inputFormat) throws IOException { + return this.nodeReader.readTree(content, inputFormat); + } + + /** + * Gets the schema. + *

+ * Using this is not recommended as there is potentially no base IRI for + * resolving references to the absolute IRI. + * + * @param schema the schema data as a string + * @return the schema + */ + public Schema getSchema(final String schema) { + return getSchema(schema, InputFormat.JSON); + } + + /** + * Gets the schema. + *

+ * Using this is not recommended as there is potentially no base IRI for + * resolving references to the absolute IRI. + * + * @param schema the schema data as a string + * @param inputFormat input format + * @return the schema + */ + public Schema getSchema(final String schema, InputFormat inputFormat) { + try { + final JsonNode schemaNode = readTree(schema, inputFormat); + return newSchema(null, schemaNode); + } catch (IOException ioe) { + logger.error("Failed to load json schema!", ioe); + throw new SchemaException(ioe); + } + } + + /** + * Gets the schema. + *

+ * Using this is not recommended as there is potentially no base IRI for + * resolving references to the absolute IRI. + * + * @param schemaStream the input stream with the schema data + * @return the schema + */ + public Schema getSchema(final InputStream schemaStream) { + return getSchema(schemaStream, InputFormat.JSON); + } + + /** + * Gets the schema. + *

+ * Using this is not recommended as there is potentially no base IRI for + * resolving references to the absolute IRI. + * + * @param schemaStream the input stream with the schema data + * @param inputFormat input format + * @return the schema + */ + public Schema getSchema(final InputStream schemaStream, InputFormat inputFormat) { + try { + final JsonNode schemaNode = readTree(schemaStream, inputFormat); + return newSchema(null, schemaNode); + } catch (IOException ioe) { + logger.error("Failed to load json schema!", ioe); + throw new SchemaException(ioe); + } + } + + /** + * Gets the schema. + * + * @param schemaUri the absolute IRI of the schema which can map to the + * retrieval IRI. + * @return the schema + */ + public Schema getSchema(final SchemaLocation schemaUri) { + Schema schema = loadSchema(schemaUri); + preload(schema); + return schema; + } + + /** + * Loads the schema. + * + * @param schemaUri the absolute IRI of the schema which can map to the + * retrieval IRI. + * @return the schema + */ + public Schema loadSchema(final SchemaLocation schemaUri) { + if (schemaCacheEnabled) { + // ConcurrentHashMap computeIfAbsent does not allow calls that result in a + // recursive update to the map. + // The getMapperSchema potentially recurses to call back to getSchema again + Schema cachedUriSchema = schemaCache.get(schemaUri); + if (cachedUriSchema == null) { + synchronized (this) { // acquire lock on shared registry object to prevent deadlock + cachedUriSchema = schemaCache.get(schemaUri); + if (cachedUriSchema == null) { + cachedUriSchema = getMappedSchema(schemaUri); + if (cachedUriSchema != null) { + schemaCache.put(schemaUri, cachedUriSchema); + } + } + } + } + return cachedUriSchema; + } + return getMappedSchema(schemaUri); + } + + protected Schema getMappedSchema(final SchemaLocation schemaUri) { + InputStreamSource inputStreamSource = this.schemaLoader.getSchemaResource(schemaUri.getAbsoluteIri()); + if (inputStreamSource != null) { + try (InputStream inputStream = inputStreamSource.getInputStream()) { + if (inputStream == null) { + throw new IOException("Cannot load schema at " + schemaUri); + } + final JsonNode schemaNode; + if (isYaml(schemaUri)) { + schemaNode = readTree(inputStream, InputFormat.YAML); + } else { + schemaNode = readTree(inputStream, InputFormat.JSON); + } + + final Dialect dialect = getDialectOrDefault(schemaNode); + if (schemaUri.getFragment() == null || schemaUri.getFragment().getNameCount() == 0) { + // Schema without fragment + SchemaContext schemaContext = new SchemaContext(dialect, this); + return doCreate(schemaContext, schemaUri, schemaNode, null, + true /* retrieved via id, resolving will not change anything */); + } else { + // Schema with fragment pointing to sub schema + final SchemaContext schemaContext = createSchemaContext(schemaNode); + SchemaLocation documentLocation = new SchemaLocation(schemaUri.getAbsoluteIri()); + Schema document = doCreate(schemaContext, documentLocation, schemaNode, null, + false); + return document.getRefSchema(schemaUri.getFragment()); + } + } catch (IOException e) { + logger.error("Failed to load json schema from {}", schemaUri.getAbsoluteIri(), e); + SchemaException exception = new SchemaException( + "Failed to load json schema from " + schemaUri.getAbsoluteIri()); + exception.initCause(e); + throw exception; + } + } else { + throw new SchemaException(new FileNotFoundException(schemaUri.getAbsoluteIri().toString())); + } + } + + /** + * Gets the schema. + * + * @param schemaUri the absolute IRI of the schema which can map to the + * retrieval IRI. + * @return the schema + */ + public Schema getSchema(final URI schemaUri) { + return getSchema(SchemaLocation.of(schemaUri.toString())); + } + + /** + * Gets the schema. + * + * @param schemaUri the absolute IRI of the schema which can map to the + * retrieval IRI. + * @param jsonNode the node + * @return the schema + */ + public Schema getSchema(final URI schemaUri, final JsonNode jsonNode) { + return newSchema(SchemaLocation.of(schemaUri.toString()), jsonNode); + } + + /** + * Gets the schema. + * + * @param schemaUri the base absolute IRI + * @param jsonNode the node + * @return the schema + */ + public Schema getSchema(final SchemaLocation schemaUri, final JsonNode jsonNode) { + return newSchema(schemaUri, jsonNode); + } + + /** + * Gets the schema. + *

+ * Using this is not recommended as there is potentially no base IRI for + * resolving references to the absolute IRI. + *

+ * Prefer {@link #getSchema(SchemaLocation, JsonNode)} instead to ensure the + * base IRI if no id is present. + * + * @param jsonNode the node + * @return the schema + */ + public Schema getSchema(final JsonNode jsonNode) { + return newSchema(null, jsonNode); + } + + /** + * Gets the schema registry config. + * + * @return the schema registry config + */ + public SchemaRegistryConfig getSchemaRegistryConfig() { + return this.schemaRegistryConfig; + } + + private boolean isYaml(final SchemaLocation schemaUri) { + final String schemeSpecificPart = schemaUri.getAbsoluteIri().toString(); + final int idx = schemeSpecificPart.lastIndexOf('.'); + + if (idx == -1) { + // no extension; assume json + return false; + } + + final String extension = schemeSpecificPart.substring(idx); + return (".yml".equals(extension) || ".yaml".equals(extension)); + } + + /** + * Normalizes the standard JSON schema dialects. + *

+ * This should not normalize any other unrecognized dialects. + * + * @param id the $schema identifier + * @return the normalized uri + */ + static protected String normalizeDialectId(String id) { + boolean found = false; + for (SpecificationVersion flag : SpecificationVersion.values()) { + if (flag.getDialectId().equals(id)) { + found = true; + break; + } + } + if (!found) { + if (id.contains("://json-schema.org/draft")) { + // unnormalized $schema + if (id.contains("/draft-07/")) { + id = DialectId.DRAFT_7; + } else if (id.contains("/draft/2019-09/")) { + id = DialectId.DRAFT_2019_09; + } else if (id.contains("/draft/2020-12/")) { + id = DialectId.DRAFT_2020_12; + } else if (id.contains("/draft-04/")) { + id = DialectId.DRAFT_4; + } else if (id.contains("/draft-06/")) { + id = DialectId.DRAFT_6; + } + } + } + return id; + } +} diff --git a/src/main/java/com/networknt/schema/SchemaRegistryConfig.java b/src/main/java/com/networknt/schema/SchemaRegistryConfig.java new file mode 100644 index 000000000..65f049d4a --- /dev/null +++ b/src/main/java/com/networknt/schema/SchemaRegistryConfig.java @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache LicenseBuilder Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.networknt.schema.i18n.DefaultMessageSource; +import com.networknt.schema.i18n.MessageSource; +import com.networknt.schema.path.PathType; +import com.networknt.schema.regex.ECMAScriptRegularExpressionFactory; +import com.networknt.schema.regex.JDKRegularExpressionFactory; +import com.networknt.schema.regex.RegularExpressionFactory; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * Configuration for SchemaRegistry that applies to all the schemas its + * validators that is managed by the SchemaRegistry. + */ +public class SchemaRegistryConfig { + private static class Holder { + private static final SchemaRegistryConfig INSTANCE = SchemaRegistryConfig.builder().build(); + } + + /** + * Gets the default config instance. + * + * @return the config + */ + public static SchemaRegistryConfig getInstance() { + return Holder.INSTANCE; + } + + /** + * The execution context customizer that runs by default for all schemas. + */ + private final ExecutionContextCustomizer executionContextCustomizer; + + /** + * Controls if schemas loaded from refs will be cached and reused for subsequent runs. + */ + private final boolean cacheRefs; + + /** + * When set to true, "messages" provided in schema are used for forming validation errors + * else default messages are used + */ + private final String errorMessageKeyword; + + /** + * When set to true, validator process is stop immediately when a very first + * validation error is discovered. + */ + private final boolean failFast; + + /** + * Since Draft 2019-09 format assertions are not enabled by default. + */ + private final Boolean formatAssertionsEnabled; + + /** + * The Locale to consider when loading validation messages from the default resource bundle. + */ + private final Locale locale; + + /** + * When set to true, can interpret round doubles as integers + */ + private final boolean losslessNarrowing; + + /** + * The message source to use for generating localised messages. + */ + private final MessageSource messageSource; + + /** + * The approach used to generate paths in reported messages, logs and errors. Default is the legacy "JSONPath-like" approach. + */ + private final PathType pathType; + + /** + * Controls if the schema will automatically be preloaded. + */ + private final boolean preloadSchema; + + /** + * Used to create {@link com.networknt.schema.regex.RegularExpression}. + */ + private final RegularExpressionFactory regularExpressionFactory; + + /** + * Used to validate the acceptable $id values. + */ + private final SchemaIdValidator schemaIdValidator; + + /** + * Contains a mapping of how strict a keyword's validators should be. + * Defaults to {@literal true}. + *

+ * Each validator has its own understanding of what constitutes strict + * and permissive. + */ + private final Map strictness; + + /** + * when validate type, if TYPE_LOOSE = true, will try to convert string to + * different types to match the type defined in schema. + */ + private boolean typeLoose; + + protected SchemaRegistryConfig(boolean cacheRefs, + String errorMessageKeyword, ExecutionContextCustomizer executionContextCustomizer, boolean failFast, + Boolean formatAssertionsEnabled, + Locale locale, boolean losslessNarrowing, + MessageSource messageSource, PathType pathType, + boolean preloadSchema, + RegularExpressionFactory regularExpressionFactory, SchemaIdValidator schemaIdValidator, + Map strictness, boolean typeLoose) { + super(); + this.cacheRefs = cacheRefs; + this.errorMessageKeyword = errorMessageKeyword; + this.executionContextCustomizer = executionContextCustomizer; + this.failFast = failFast; + this.formatAssertionsEnabled = formatAssertionsEnabled; + this.locale = locale; + this.losslessNarrowing = losslessNarrowing; + this.messageSource = messageSource; + this.pathType = pathType; + this.preloadSchema = preloadSchema; + this.regularExpressionFactory = regularExpressionFactory; + this.schemaIdValidator = schemaIdValidator; + this.strictness = strictness; + this.typeLoose = typeLoose; + } + + public ExecutionContextCustomizer getExecutionContextCustomizer() { + return this.executionContextCustomizer; + } + + /** + * Gets the format assertion enabled flag. + *

+ * This defaults to null meaning that it will follow the defaults of the + * specification. + *

+ * Since draft 2019-09 this will default to false unless enabled by using the + * $vocabulary keyword. + * + * @return the format assertions enabled flag + */ + public Boolean getFormatAssertionsEnabled() { + return formatAssertionsEnabled; + } + + /** + * Get the locale to consider when generating localised messages (default is the + * JVM default). + *

+ * This locale is on a schema basis and will be used as the default locale for + * {@link com.networknt.schema.ExecutionConfig}. + * + * @return The locale. + */ + public Locale getLocale() { + if (this.locale == null) { + // This should not be cached as it can be changed using Locale#setDefault(Locale) + return Locale.getDefault(); + } + return this.locale; + } + + /** + * Get the message source to use for generating localised messages. + * + * @return the message source + */ + public MessageSource getMessageSource() { + if (this.messageSource == null) { + return DefaultMessageSource.getInstance(); + } + return this.messageSource; + } + + /** + * Get the approach used to generate paths in messages, logs and errors. + * + * @return The path generation approach. + */ + public PathType getPathType() { + return this.pathType; + } + + /** + * Gets the regular expression factory. + *

+ * This defaults to the JDKRegularExpressionFactory and the implementations + * require inclusion of optional org.jruby.joni:joni or org.graalvm.js:js dependencies. + * + * @return the factory + */ + public RegularExpressionFactory getRegularExpressionFactory() { + return regularExpressionFactory; + } + + /** + * Gets the schema id validator to validate $id. + * + * @return the validator + */ + public SchemaIdValidator getSchemaIdValidator() { + return schemaIdValidator; + } + + /** + * Gets if schemas loaded from refs will be cached and reused for subsequent + * runs. + * + * @return true if schemas loaded from refs should be cached + */ + public boolean isCacheRefs() { + return cacheRefs; + } + + public String getErrorMessageKeyword() { + return this.errorMessageKeyword; + } + + public boolean isFailFast() { + return this.failFast; + } + + public boolean isLosslessNarrowing() { + return this.losslessNarrowing; + } + + /** + * Gets if the schema should be preloaded. + * + * @return true if it should be preloaded + */ + public boolean isPreloadSchema() { + return preloadSchema; + } + + /** + * Answers whether a keyword's validators may relax their analysis. The + * default is to perform strict checking. One must explicitly allow a + * validator to be more permissive. + *

+ * Each validator has its own understanding of what is permissive and + * strict. Consult the keyword's documentation for details. + * + * @param keyword the keyword to adjust (not null) + * @return Whether to perform a strict validation. + */ + public boolean isStrict(String keyword) { + return isStrict(keyword, Boolean.TRUE); + } + + /** + * Determines if the validator should perform strict checking. + * + * @param keyword the keyword + * @param defaultValue the default value + * @return whether to perform a strict validation + */ + public boolean isStrict(String keyword, Boolean defaultValue) { + return this.strictness.getOrDefault(Objects.requireNonNull(keyword, "keyword cannot be null"), defaultValue); + } + + /** + * Returns whether types are interpreted in a loose manner. + *

+ * If set to true, a single value can be interpreted as a size 1 array. Strings + * may also be interpreted as number, integer or boolean. + * + * @return true if type are interpreted in a loose manner + */ + public boolean isTypeLoose() { + return this.typeLoose; + } + + /** + * Creates a builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Copies values from a configuration to a new builder. + * + * @param config the configuration + * @return the builder + */ + public static Builder builder(SchemaRegistryConfig config) { + Builder builder = new Builder(); + builder.cacheRefs = config.cacheRefs; + builder.errorMessageKeyword = config.errorMessageKeyword; + builder.executionContextCustomizer = config.executionContextCustomizer; + builder.failFast = config.failFast; + builder.formatAssertionsEnabled = config.formatAssertionsEnabled; + builder.locale = config.locale; + builder.losslessNarrowing = config.losslessNarrowing; + builder.messageSource = config.messageSource; + builder.pathType = config.pathType; + builder.preloadSchema = config.preloadSchema; + builder.regularExpressionFactory = config.regularExpressionFactory; + builder.schemaIdValidator = config.schemaIdValidator; + builder.strictness = config.strictness; + builder.typeLoose = config.typeLoose; + return builder; + } + + /** + * Builder for {@link SchemaRegistryConfig}. + */ + public static class Builder extends BuilderSupport { + @Override + protected Builder self() { + return this; + } + } + + /** + * Builder for {@link SchemaRegistryConfig}. + */ + public static abstract class BuilderSupport { + protected boolean cacheRefs = true; + protected String errorMessageKeyword = null; + protected ExecutionContextCustomizer executionContextCustomizer = null; + protected boolean failFast = false; + protected Boolean formatAssertionsEnabled = null; + protected Locale locale = null; // This must be null to use Locale.getDefault() as the default can be changed + protected boolean losslessNarrowing = false; + protected MessageSource messageSource = null; + protected PathType pathType = PathType.JSON_POINTER; + protected boolean preloadSchema = true; + protected RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance(); + protected SchemaIdValidator schemaIdValidator = SchemaIdValidator.DEFAULT; + protected Map strictness = new HashMap<>(0); + protected boolean typeLoose = false; + + protected abstract T self(); + + /** + * Sets if schemas loaded from refs will be cached and reused for subsequent runs. + *

+ * Defaults to true. + * + * @param cacheRefs true to cache + * @return the builder + */ + public T cacheRefs(boolean cacheRefs) { + this.cacheRefs = cacheRefs; + return self(); + } + /** + * Sets the error message keyword for setting custom messages in the schema. + *

+ * Defaults to null meaning custom messages are not enabled. + * + * @param errorMessageKeyword to use for custom messages in the schema + * @return the builder + */ + public T errorMessageKeyword(String errorMessageKeyword) { + this.errorMessageKeyword = errorMessageKeyword; + return self(); + } + /** + * Sets the execution context customizer that is run before each run. + * + * @param executionContextCustomizer the customizer + * @return the builder + */ + public T executionContextCustomizer(ExecutionContextCustomizer executionContextCustomizer) { + this.executionContextCustomizer = executionContextCustomizer; + return self(); + } + + /** + * Sets if the validation should immediately return once a validation error has + * occurred. This can improve performance if inputs are invalid but cannot + * return all error messages to the caller. + *

+ * Defaults to false. + * + * @param failFast true to enable + * @return the builder + */ + public T failFast(boolean failFast) { + this.failFast = failFast; + return self(); + } + + /** + * Sets if format assertions are enabled. If format assertions are not enabled + * the format keyword will behave like an annotation and not attempt to validate + * if the inputs are valid. + *

+ * Defaults to not enabling format assertions for Draft 2019-09 and above and + * enabling format assertions for Draft 7 and below. + * + * @param formatAssertionsEnabled true to enable + * @return the builder + */ + public T formatAssertionsEnabled(Boolean formatAssertionsEnabled) { + this.formatAssertionsEnabled = formatAssertionsEnabled; + return self(); + } + + /** + * Set the locale to consider when generating localized messages. + *

+ * Note that this locale is set on a schema registry basis. To configure the + * schema on a per execution basis use + * {@link com.networknt.schema.ExecutionConfig.Builder#locale(Locale)}. + *

+ * Defaults to use {@link Locale#getDefault()}. + * + * @param locale The locale. + * @return the builder + */ + public T locale(Locale locale) { + this.locale = locale; + return self(); + } + /** + * Sets whether lossless narrowing of other numeric types to integer is performed. + *

+ * Note that this likely only has a visible effect for dialects written before Draft 6. + *

+ * Since Draft 6 for example 1.0 is treated as an integer even without this enabled. + * + * @param losslessNarrowing true to enable + * @return the builder + */ + public T losslessNarrowing(boolean losslessNarrowing) { + this.losslessNarrowing = losslessNarrowing; + return self(); + } + /** + * Sets the message source to use for generating localised messages. + * + * @param messageSource the message source + * @return the builder + */ + public T messageSource(MessageSource messageSource) { + this.messageSource = messageSource; + return self(); + } + /** + * Sets the path type to use when reporting the instance location of errors. + *

+ * Defaults to {@link PathType#JSON_POINTER}. + * + * @param pathType the path type + * @return the path type + */ + public T pathType(PathType pathType) { + this.pathType = pathType; + return self(); + } + /** + * Sets if the schema should be preloaded. + *

+ * Defaults to true. + * + * @param preloadSchema true to preload + * @return the builder + */ + public T preloadSchema(boolean preloadSchema) { + this.preloadSchema = preloadSchema; + return self(); + } + + /** + * Sets the regular expression factory. + *

+ * Defaults to the {@link JDKRegularExpressionFactory} + *

+ * The {@link ECMAScriptRegularExpressionFactory} requires the inclusion of + * optional org.jruby.joni:joni or org.graalvm.js:js dependencies. + * + * @see JDKRegularExpressionFactory + * @see ECMAScriptRegularExpressionFactory + * @param regularExpressionFactory the factory + * @return the builder + */ + public T regularExpressionFactory(RegularExpressionFactory regularExpressionFactory) { + this.regularExpressionFactory = regularExpressionFactory; + return self(); + } + /** + * Sets the schema id validator to use. + *

+ * Defaults to {@link SchemaIdValidator#DEFAULT}. + * + * @param schemaIdValidator the builder + * @return the builder + */ + public T schemaIdValidator(SchemaIdValidator schemaIdValidator) { + this.schemaIdValidator = schemaIdValidator; + return self(); + } + public T strict(Map strict) { + this.strictness = strict; + return self(); + } + public T strict(String keyword, boolean strict) { + this.strictness.put(Objects.requireNonNull(keyword, "keyword cannot be null"), strict); + return self(); + } + public T typeLoose(boolean typeLoose) { + this.typeLoose = typeLoose; + return self(); + } + + public SchemaRegistryConfig build() { + return new SchemaRegistryConfig(cacheRefs, errorMessageKeyword, executionContextCustomizer, failFast, + formatAssertionsEnabled, locale, losslessNarrowing, messageSource, pathType, + preloadSchema, regularExpressionFactory, schemaIdValidator, strictness, typeLoose); + } + + } +} diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java deleted file mode 100644 index b822e612f..000000000 --- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.walk.JsonSchemaWalkListener; - -public class SchemaValidatorsConfig { - /** - * when validate type, if TYPE_LOOSE = true, will try to convert string to different types to match the type defined in - * schema. - */ - private boolean typeLoose; - - /** - * When set to true, validator process is stop immediately when a very first validation error is discovered. - */ - private boolean failFast; - - /** - * When set to true, walker sets nodes that are missing or NullNode to the default value, if any, and mutate the input json. - */ - private ApplyDefaultsStrategy applyDefaultsStrategy; - - /** - * When set to true, use ECMA-262 compatible validator - */ - private boolean ecma262Validator; - - /** - * When set to true, use Java-specific semantics rather than native JavaScript semantics - */ - private boolean javaSemantics; - - /** - * When set to true, can interpret round doubles as integers - */ - private boolean losslessNarrowing; - - /** - * When set to true, support for discriminators is enabled for validations of oneOf, anyOf and allOf as described - * on GitHub. - */ - private boolean openAPI3StyleDiscriminators = false; - - /** - * Map of public, normally internet accessible schema URLs to alternate locations; this allows for offline - * validation of schemas that refer to public URLs. This is merged with any mappings the {@link JsonSchemaFactory} - * may have been built with. - */ - private Map uriMappings = new HashMap(); - - /** - * When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable - * however continues with validation against the nullable field - *

- * If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed - * If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type - * validator using the SchemaValidator to handle it. - */ - private boolean handleNullableField = true; - - // This is just a constant for listening to all Keywords. - public static final String ALL_KEYWORD_WALK_LISTENER_KEY = "com.networknt.AllKeywordWalkListener"; - - private final Map> keywordWalkListenersMap = new HashMap>(); - - private final List propertyWalkListeners = new ArrayList(); - - private final List itemWalkListeners = new ArrayList(); - - private CollectorContext collectorContext; - - private boolean loadCollectors = true; - - public boolean isTypeLoose() { - return typeLoose; - } - - public void setTypeLoose(boolean typeLoose) { - this.typeLoose = typeLoose; - } - - /** - * When enabled, {@link JsonValidator#validate(JsonNode, JsonNode, String)} - * or {@link JsonValidator#validate(JsonNode)} doesn't return any {@link Set}<{@link ValidationMessage}>, - * instead a {@link JsonSchemaException} is thrown as soon as a validation errors is discovered. - * - * @param failFast boolean - */ - public void setFailFast(final boolean failFast) { - this.failFast = failFast; - } - - public boolean isFailFast() { - return this.failFast; - } - - public void setApplyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) { - this.applyDefaultsStrategy = applyDefaultsStrategy; - } - - public ApplyDefaultsStrategy getApplyDefaultsStrategy() { - return applyDefaultsStrategy; - } - - public Map getUriMappings() { - // return a copy of the mappings - return new HashMap(uriMappings); - } - - public void setUriMappings(Map uriMappings) { - this.uriMappings = uriMappings; - } - - public boolean isHandleNullableField() { - return handleNullableField; - } - - public void setHandleNullableField(boolean handleNullableField) { - this.handleNullableField = handleNullableField; - } - - public boolean isEcma262Validator() { - return ecma262Validator; - } - - public void setEcma262Validator(boolean ecma262Validator) { - this.ecma262Validator = ecma262Validator; - } - - public boolean isJavaSemantics() { - return javaSemantics; - } - - public void setJavaSemantics(boolean javaSemantics) { - this.javaSemantics = javaSemantics; - } - - public void addKeywordWalkListener(JsonSchemaWalkListener keywordWalkListener) { - if (keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) { - List keywordWalkListeners = new ArrayList(); - keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, keywordWalkListeners); - } - keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).add(keywordWalkListener); - } - - public void addKeywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) { - if (keywordWalkListenersMap.get(keyword) == null) { - List keywordWalkListeners = new ArrayList(); - keywordWalkListenersMap.put(keyword, keywordWalkListeners); - } - keywordWalkListenersMap.get(keyword).add(keywordWalkListener); - } - - public void addKeywordWalkListeners(List keywordWalkListeners) { - if (keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) { - List ikeywordWalkListeners = new ArrayList(); - keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, ikeywordWalkListeners); - } - keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).addAll(keywordWalkListeners); - } - - public void addKeywordWalkListeners(String keyword, List keywordWalkListeners) { - if (keywordWalkListenersMap.get(keyword) == null) { - List ikeywordWalkListeners = new ArrayList(); - keywordWalkListenersMap.put(keyword, ikeywordWalkListeners); - } - keywordWalkListenersMap.get(keyword).addAll(keywordWalkListeners); - } - - public void addPropertyWalkListeners(List propertyWalkListeners) { - this.propertyWalkListeners.addAll(propertyWalkListeners); - } - - public void addPropertyWalkListener(JsonSchemaWalkListener propertyWalkListener) { - this.propertyWalkListeners.add(propertyWalkListener); - } - - public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) { - this.itemWalkListeners.add(itemWalkListener); - } - - public void addItemWalkListeners(List itemWalkListeners) { - this.itemWalkListeners.addAll(itemWalkListeners); - } - - public List getPropertyWalkListeners() { - return this.propertyWalkListeners; - } - - public Map> getKeywordWalkListenersMap() { - return this.keywordWalkListenersMap; - } - - public List getArrayItemWalkListeners() { - return this.itemWalkListeners; - } - - public SchemaValidatorsConfig() { - } - - public CollectorContext getCollectorContext() { - return collectorContext; - } - - public void setCollectorContext(CollectorContext collectorContext) { - this.collectorContext = collectorContext; - } - - public boolean isLosslessNarrowing() { - return losslessNarrowing; - } - - public void setLosslessNarrowing(boolean losslessNarrowing) { - this.losslessNarrowing = losslessNarrowing; - } - - /** - * Indicates whether OpenAPI 3 style discriminators should be supported - * @return true in case discriminators are enabled - * @since 1.0.51 - */ - public boolean isOpenAPI3StyleDiscriminators() { - return openAPI3StyleDiscriminators; - } - - /** - * When enabled, the validation of anyOf and allOf in polymorphism will respect - * OpenAPI 3 style discriminators as described in the - * OpenAPI 3.0.3 spec. - * The presence of a discriminator configuration on the schema will lead to the following changes in the behavior: - *

    - *
  • for oneOf the spec is unfortunately very vague. Whether oneOf semantics should be - * affected by discriminators or not is not even 100% clear within the members of the OAS steering committee. Therefore - * oneOf at the moment ignores discriminators
  • - *
  • for anyOf the validation will choose one of the candidate schemas for validation based on the - * discriminator property value and will pass validation when this specific schema passes. This is in particular useful - * when the payload could match multiple candidates in the anyOf list and could lead to ambiguity. Example: - * type B has all mandatory properties of A and adds more mandatory ones. Whether the payload is an A or B is determined - * via the discriminator property name. A payload indicating it is an instance of B then requires passing the validation - * of B and passing the validation of A would not be sufficient anymore.
  • - *
  • for allOf use cases with discriminators defined on the copied-in parent type, it is possible to - * automatically validate against a subtype. Example: some schema specifies that there is a field of type A. A carries - * a discriminator field and B inherits from A. Then B is automatically a candidate for validation as well and will be - * chosen in case the discriminator property matches
  • - *
- * @param openAPI3StyleDiscriminators whether or not discriminators should be used. Defaults to false - * @since 1.0.51 - */ - public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators) { - this.openAPI3StyleDiscriminators = openAPI3StyleDiscriminators; - } - - public void setLoadCollectors(boolean loadCollectors) { - this.loadCollectors = loadCollectors; - } - - public boolean doLoadCollectors() { - return loadCollectors; - } -} diff --git a/src/main/java/com/networknt/schema/SpecVersion.java b/src/main/java/com/networknt/schema/SpecVersion.java deleted file mode 100644 index d67294181..000000000 --- a/src/main/java/com/networknt/schema/SpecVersion.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import java.util.EnumSet; -import java.util.Set; - -public class SpecVersion { - - public enum VersionFlag { - - V4(1 << 0), - V6(1 << 1), - V7(1 << 2), - V201909(1 << 3); - - - private final long versionFlagValue; - - VersionFlag(long versionFlagValue) { - this.versionFlagValue = versionFlagValue; - } - - public long getVersionFlagValue() { - return versionFlagValue; - } - } - - - /** - * Translates a numeric version code into a Set of VersionFlag enums - * - * @param versionValue long - * @return EnumSet representing a version - */ - public EnumSet getVersionFlags(long versionValue) { - EnumSet versionFlags = EnumSet.noneOf(VersionFlag.class); - for (VersionFlag flag : VersionFlag.values()) { - long flagValue = flag.versionFlagValue; - if ((flagValue & versionValue) == flagValue) { - versionFlags.add(flag); - } - } - return versionFlags; - } - - - /** - * Translates a set of VersionFlag enums into a long version code - * - * @param flags set of versionFlags - * @return numeric representation of the spec version - */ - public long getVersionValue(Set flags) { - long value = 0; - for (VersionFlag flag : flags) { - value = value | flag.versionFlagValue; - } - return value; - } -} diff --git a/src/main/java/com/networknt/schema/SpecVersionDetector.java b/src/main/java/com/networknt/schema/SpecVersionDetector.java deleted file mode 100644 index 400008aab..000000000 --- a/src/main/java/com/networknt/schema/SpecVersionDetector.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * This class is used to detect schema version - * - * @author Subhajitdas298 - * @since 25/06/20 - */ -public class SpecVersionDetector { - - // Schema tag - private static final String SCHEMA_TAG = "$schema"; - - /** - * Detects schema version based on the schema tag - * - * @param jsonNode Json Node to read from - * @return Spec version - */ - public static SpecVersion.VersionFlag detect(JsonNode jsonNode) { - if (!jsonNode.has(SCHEMA_TAG)) - throw new JsonSchemaException("Schema tag not present"); - - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = true; - - String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(jsonNode.get(SCHEMA_TAG).asText(), forceHttps, removeEmptyFragmentSuffix); - if (schemaUri.equals(JsonMetaSchema.getV4().getUri())) - return SpecVersion.VersionFlag.V4; - else if (schemaUri.equals(JsonMetaSchema.getV6().getUri())) - return SpecVersion.VersionFlag.V6; - else if (schemaUri.equals(JsonMetaSchema.getV7().getUri())) - return SpecVersion.VersionFlag.V7; - else if (schemaUri.equals(JsonMetaSchema.getV201909().getUri())) - return SpecVersion.VersionFlag.V201909; - else - throw new JsonSchemaException("Unrecognizable schema"); - } - -} diff --git a/src/main/java/com/networknt/schema/Specification.java b/src/main/java/com/networknt/schema/Specification.java new file mode 100644 index 000000000..67cd6a61d --- /dev/null +++ b/src/main/java/com/networknt/schema/Specification.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.DialectId; +import com.networknt.schema.dialect.Dialects; + +/** + * The version of the JSON Schema specification that defines the standard + * dialects. + */ +public class Specification { + + /** + * Gets the standard dialect given the specification version. + *

+ * This should only be used if the standard dialect is required, otherwise the + * dialect should be retrieved from the dialect registry. + * + * @param version the schema specification version + * @return the dialect or null if not found + */ + public static Dialect getDialect(SpecificationVersion version) { + if (null == version) { + return null; + } + switch (version) { + case DRAFT_2020_12: + return Dialects.getDraft202012(); + case DRAFT_2019_09: + return Dialects.getDraft201909(); + case DRAFT_7: + return Dialects.getDraft7(); + case DRAFT_6: + return Dialects.getDraft6(); + case DRAFT_4: + return Dialects.getDraft4(); + default: + return null; + } + } + + /** + * Gets the standard dialect given the dialect id. + *

+ * This should only be used if the standard dialect is required, otherwise the + * dialect should be retrieved from the dialect registry. + * + * @param dialectId the schema specification version + * @return the dialect or null if not found + */ + public static Dialect getDialect(String dialectId) { + if (null == dialectId) { + return null; + } + switch (dialectId) { + case DialectId.DRAFT_2020_12: + return Dialects.getDraft202012(); + case DialectId.DRAFT_2019_09: + return Dialects.getDraft201909(); + case DialectId.DRAFT_7: + return Dialects.getDraft7(); + case DialectId.DRAFT_6: + return Dialects.getDraft6(); + case DialectId.DRAFT_4: + return Dialects.getDraft4(); + default: + return null; + } + } +} diff --git a/src/main/java/com/networknt/schema/SpecificationVersion.java b/src/main/java/com/networknt/schema/SpecificationVersion.java new file mode 100644 index 000000000..33bd3752e --- /dev/null +++ b/src/main/java/com/networknt/schema/SpecificationVersion.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import java.util.Optional; + +import com.networknt.schema.dialect.DialectId; + +/** + * The version of the JSON Schema specification that defines the standard + * dialects. + */ +public enum SpecificationVersion { + /** + * Draft 4. + */ + DRAFT_4(4, DialectId.DRAFT_4), + /** + * Draft 6. + */ + DRAFT_6(6, DialectId.DRAFT_6), + /** + * Draft 7. + */ + DRAFT_7(7, DialectId.DRAFT_7), + /** + * Draft 2019-09. + */ + DRAFT_2019_09(8, DialectId.DRAFT_2019_09), + /** + * Draft 2020-12. + */ + DRAFT_2020_12(9, DialectId.DRAFT_2020_12); + + private final int order; + private final String dialectId; + + SpecificationVersion(int order, String dialectId) { + this.order = order; + this.dialectId = dialectId; + } + + /** + * Gets the dialect id used for the $schema keyword. The dialect id is an IRI + * that identifies the meta schema used to validate the dialect. + * + * @return the dialect id + */ + public String getDialectId() { + return this.dialectId; + } + + /** + * Gets the unique release order of the specification version used that + * indicates when the specification was released. Lower numbers indicate the + * specification was released earlier. + * + * @return the order when the specification was released + */ + public int getOrder() { + return this.order; + } + + /** + * Gets the specification version that matches the dialect id indicated by + * $schema keyword. The dialect id is an IRI that identifies the meta schema + * used to validate the dialect. + * + * @param dialectId the dialect id specified by $schema keyword + * @return the specification version if it matches the dialect id + */ + public static Optional fromDialectId(String dialectId) { + for (SpecificationVersion version : SpecificationVersion.values()) { + if (version.dialectId.equals(dialectId)) { + return Optional.of(version); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/networknt/schema/SpecificationVersionRange.java b/src/main/java/com/networknt/schema/SpecificationVersionRange.java new file mode 100644 index 000000000..846872f51 --- /dev/null +++ b/src/main/java/com/networknt/schema/SpecificationVersionRange.java @@ -0,0 +1,32 @@ +package com.networknt.schema; + +import java.util.Arrays; +import java.util.EnumSet; + +/** + * SpecificationVersionRange. + */ +public enum SpecificationVersionRange { + NONE(new SpecificationVersion[] { }), + ALL_VERSIONS(new SpecificationVersion[] { SpecificationVersion.DRAFT_4, SpecificationVersion.DRAFT_6, SpecificationVersion.DRAFT_7, SpecificationVersion.DRAFT_2019_09, SpecificationVersion.DRAFT_2020_12 }), + MIN_DRAFT_6(new SpecificationVersion[] { SpecificationVersion.DRAFT_6, SpecificationVersion.DRAFT_7, SpecificationVersion.DRAFT_2019_09, SpecificationVersion.DRAFT_2020_12 }), + DRAFT_6_TO_DRAFT_7(new SpecificationVersion[] { SpecificationVersion.DRAFT_6, SpecificationVersion.DRAFT_7 }), + MIN_DRAFT_7(new SpecificationVersion[] { SpecificationVersion.DRAFT_7, SpecificationVersion.DRAFT_2019_09, SpecificationVersion.DRAFT_2020_12 }), + MAX_DRAFT_7(new SpecificationVersion[] { SpecificationVersion.DRAFT_4, SpecificationVersion.DRAFT_6, SpecificationVersion.DRAFT_7 }), + MAX_DRAFT_2019_09(new SpecificationVersion[] { SpecificationVersion.DRAFT_4, SpecificationVersion.DRAFT_6, SpecificationVersion.DRAFT_7, SpecificationVersion.DRAFT_2019_09 }), + MIN_DRAFT_2019_09(new SpecificationVersion[] { SpecificationVersion.DRAFT_2019_09, SpecificationVersion.DRAFT_2020_12 }), + MIN_DRAFT_2020_12(new SpecificationVersion[] { SpecificationVersion.DRAFT_2020_12 }), + DRAFT_2019_09(new SpecificationVersion[] { SpecificationVersion.DRAFT_2019_09 }), + DRAFT_7(new SpecificationVersion[] { SpecificationVersion.DRAFT_7 }); + + private final EnumSet versions; + + SpecificationVersionRange(SpecificationVersion[] versionFlags) { + this.versions = EnumSet.noneOf(SpecificationVersion.class); + this.versions.addAll(Arrays.asList(versionFlags)); + } + + public EnumSet getVersions() { + return this.versions; + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/ThreadInfo.java b/src/main/java/com/networknt/schema/ThreadInfo.java deleted file mode 100644 index e09a86c62..000000000 --- a/src/main/java/com/networknt/schema/ThreadInfo.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import java.util.HashMap; -import java.util.Map; - -public class ThreadInfo { - - private static ThreadLocal> threadLocal = new ThreadLocal>() { - protected java.util.Map initialValue() { - return new HashMap(); - } - - ; - }; - - public static Object get(String key) { - return threadLocal.get().get(key); - } - - public static void set(String key, Object value) { - Map threadLocalMap = threadLocal.get(); - threadLocalMap.put(key, value); - } - - public static void remove(String key) { - Map threadLocalMap = threadLocal.get(); - threadLocalMap.remove(key); - } - -} diff --git a/src/main/java/com/networknt/schema/TrueValidator.java b/src/main/java/com/networknt/schema/TrueValidator.java deleted file mode 100644 index 54f33f21b..000000000 --- a/src/main/java/com/networknt/schema/TrueValidator.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; - -public class TrueValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(TrueValidator.class); - - public TrueValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.TRUE, validationContext); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - // For the true validator, it is always valid which means there is no ValidationMessage. - return Collections.emptySet(); - } -} diff --git a/src/main/java/com/networknt/schema/TypeFactory.java b/src/main/java/com/networknt/schema/TypeFactory.java deleted file mode 100644 index d44ff9c93..000000000 --- a/src/main/java/com/networknt/schema/TypeFactory.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -public class TypeFactory { - public static JsonType getSchemaNodeType(JsonNode node) { - //Single Type Definition - if (node.isTextual()) { - String type = node.textValue(); - if ("object".equals(type)) { - return JsonType.OBJECT; - } - if ("array".equals(type)) { - return JsonType.ARRAY; - } - if ("string".equals(type)) { - return JsonType.STRING; - } - if ("number".equals(type)) { - return JsonType.NUMBER; - } - if ("integer".equals(type)) { - return JsonType.INTEGER; - } - if ("boolean".equals(type)) { - return JsonType.BOOLEAN; - } - if ("any".equals(type)) { - return JsonType.ANY; - } - if ("null".equals(type)) { - return JsonType.NULL; - } - } - - //Union Type Definition - if (node.isArray()) { - return JsonType.UNION; - } - - return JsonType.UNKNOWN; - } - - public static JsonType getValueNodeType(JsonNode node, SchemaValidatorsConfig config) { - if (node.isContainerNode()) { - if (node.isObject()) - return JsonType.OBJECT; - if (node.isArray()) - return JsonType.ARRAY; - return JsonType.UNKNOWN; - } - - if (node.isValueNode()) { - if (node.isTextual()) - return JsonType.STRING; - if (node.isIntegralNumber()) - return JsonType.INTEGER; - if (node.isNumber()) - if (config != null && config.isJavaSemantics() && node.canConvertToExactIntegral()) - return JsonType.INTEGER; - else if (config != null && config.isLosslessNarrowing() && node.canConvertToExactIntegral()) - return JsonType.INTEGER; - else - return JsonType.NUMBER; - if (node.isBoolean()) - return JsonType.BOOLEAN; - if (node.isNull()) - return JsonType.NULL; - return JsonType.UNKNOWN; - } - - return JsonType.UNKNOWN; - } - -} diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java deleted file mode 100644 index 50950830e..000000000 --- a/src/main/java/com/networknt/schema/TypeValidator.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import com.fasterxml.jackson.databind.node.TextNode; -import com.networknt.schema.utils.JsonNodeUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Set; - -public class TypeValidator extends BaseJsonValidator implements JsonValidator { - private static final String TYPE = "type"; - private static final String ENUM = "enum"; - private static final String REF = "$ref"; - - private static final Logger logger = LoggerFactory.getLogger(TypeValidator.class); - - private JsonType schemaType; - private JsonSchema parentSchema; - private UnionTypeValidator unionTypeValidator; - private final ValidationContext validationContext; - - public TypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext); - schemaType = TypeFactory.getSchemaNodeType(schemaNode); - this.parentSchema = parentSchema; - this.validationContext = validationContext; - if (schemaType == JsonType.UNION) { - unionTypeValidator = new UnionTypeValidator(schemaPath, schemaNode, parentSchema, validationContext); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public JsonType getSchemaType() { - return schemaType; - } - - public boolean equalsToSchemaType(JsonNode node) { - JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); - // in the case that node type is not the same as schema type, try to convert node to the - // same type of schema. In REST API, query parameters, path parameters and headers are all - // string type and we must convert, otherwise, all schema validations will fail. - if (nodeType != schemaType) { - if (schemaType == JsonType.ANY) { - return true; - } - - if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) { - return true; - } - - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - if(JsonType.NULL.equals(nodeType) ){ - if ((state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), validationContext.getConfig())) || - JsonNodeUtil.isNodeNullable(this.getParentSchema().getSchemaNode())) { - return true; - } - } - - // Skip the type validation when the schema is an enum object schema. Since the current type - // of node itself can be used for type validation. - if (isEnumObjectSchema(parentSchema)) { - return true; - } - if (validationContext.getConfig().isTypeLoose()) { - // if typeLoose is true, everything can be a size 1 array - if (schemaType == JsonType.ARRAY) { - return true; - } - if (nodeType == JsonType.STRING) { - if (schemaType == JsonType.INTEGER) { - if (isInteger(node.textValue())) { - return true; - } - } else if (schemaType == JsonType.BOOLEAN) { - if (isBoolean(node.textValue())) { - return true; - } - } else if (schemaType == JsonType.NUMBER) { - if (isNumeric(node.textValue())) { - return true; - } - } - } - } - - return false; - } - return true; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (schemaType == JsonType.UNION) { - return unionTypeValidator.validate(node, rootNode, at); - } - - //if (!equalsToSchemaType(node)) { - if(!JsonNodeUtil.equalsToSchemaType(node,schemaType, parentSchema, validationContext.getConfig())){ - JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); - return Collections.singleton(buildValidationMessage(at, nodeType.toString(), schemaType.toString())); - } - return Collections.emptySet(); - } - - public static boolean isInteger(String str) { - if (str == null || str.equals("")) { - return false; - } - - // all code below could be replaced with - //return str.matrch("[-+]?(?:0|[1-9]\\d*)") - int i = 0; - if (str.charAt(0) == '-' || str.charAt(0) == '+') { - if (str.length() == 1) { - return false; - } - i = 1; - } - for (; i < str.length(); i++) { - char c = str.charAt(i); - if (c < '0' || c > '9') { - return false; - } - } - return true; - } - - public static boolean isBoolean(String s) { - return "true".equals(s) || "false".equals(s); - } - - public static boolean isNumeric(String str) { - if (str == null || str.equals("")) { - return false; - } - - // all code below could be replaced with - //return str.matrch("[-+]?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?") - int i = 0; - int len = str.length(); - - if (str.charAt(i) == MINUS || str.charAt(i) == PLUS) { - if (str.length() == 1) { - return false; - } - i = 1; - } - - char c = str.charAt(i++); - - if (c == CHAR_0) { - // TODO: if leading zeros are supported (counter to JSON spec) handle it here - if (i < len) { - c = str.charAt(i++); - if (c != DOT && c != CHAR_E && c != CHAR_e) { - return false; - } - } - } else if (CHAR_1 <= c && c <= CHAR_9) { - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } else { - return false; - } - - if (c == DOT) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } - - if (c == CHAR_E || c == CHAR_e) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - if (c == PLUS || c == MINUS) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - } - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } - - return i >= len && (CHAR_0 <= c && c <= CHAR_9); - } - - private static final char CHAR_0 = '0'; - private static final char CHAR_1 = '1'; - private static final char CHAR_9 = '9'; - private static final char MINUS = '-'; - private static final char PLUS = '+'; - private static final char DOT = '.'; - private static final char CHAR_E = 'E'; - private static final char CHAR_e = 'e'; - - /** - * Check if the type of the JsonNode's value is number based on the - * status of typeLoose flag. - * - * @param node the JsonNode to check - * @param config the SchemaValidatorsConfig to depend on - * @return boolean to indicate if it is a number - */ - public static boolean isNumber(JsonNode node, SchemaValidatorsConfig config) { - if (node.isNumber()) { - return true; - } else if (config.isTypeLoose()) { - if (TypeFactory.getValueNodeType(node, config) == JsonType.STRING) { - return isNumeric(node.textValue()); - } - } - return false; - } - - private static boolean isEnumObjectSchema(JsonSchema jsonSchema) { - // There are three conditions for enum object schema - // 1. The current schema contains key "type", and the value is object - // 2. The current schema contains key "enum", and the value is an array - // 3. The parent schema if refer from components, which means the corresponding enum object class would be generated - JsonNode typeNode = null; - JsonNode enumNode = null; - JsonNode refNode = null; - - if (jsonSchema != null) { - if (jsonSchema.getSchemaNode() != null) { - typeNode = jsonSchema.getSchemaNode().get(TYPE); - enumNode = jsonSchema.getSchemaNode().get(ENUM); - } - if (jsonSchema.getParentSchema() != null && jsonSchema.getParentSchema().getSchemaNode() != null) { - refNode = jsonSchema.getParentSchema().getSchemaNode().get(REF); - } - } - if (typeNode != null && enumNode != null && refNode != null) { - return TypeFactory.getSchemaNodeType(typeNode) == JsonType.OBJECT && enumNode.isArray(); - } - return false; - } -} diff --git a/src/main/java/com/networknt/schema/UUIDValidator.java b/src/main/java/com/networknt/schema/UUIDValidator.java deleted file mode 100644 index 1bb980c47..000000000 --- a/src/main/java/com/networknt/schema/UUIDValidator.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -public class UUIDValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(UUIDValidator.class); - - private final String formatName; - - - private static final String regex = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"; - private final ValidationContext validationContext; - - public UUIDValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UUID, validationContext); - this.formatName = formatName; - parseErrorCode(getValidatorType().getErrorCodeKey()); - this.validationContext = validationContext; - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return errors; - } - if (!isUUID(node.textValue())) { - errors.add(buildValidationMessage(at, node.textValue(), formatName)); - } - return Collections.unmodifiableSet(errors); - } - - public boolean isUUID(String s) { - return s.matches(regex); - } -} diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java deleted file mode 100644 index cb7326de5..000000000 --- a/src/main/java/com/networknt/schema/UnionTypeValidator.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -public class UnionTypeValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(UnionTypeValidator.class); - - private final List schemas = new ArrayList(); - private final String error; - - private final ValidationContext validationContext; - - public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE, validationContext); - this.validationContext = validationContext; - StringBuilder errorBuilder = new StringBuilder(); - - String sep = ""; - errorBuilder.append('['); - - if (!schemaNode.isArray()) - throw new JsonSchemaException("Expected array for type property on Union Type Definition."); - - int i = 0; - for (JsonNode n : schemaNode) { - JsonType t = TypeFactory.getSchemaNodeType(n); - errorBuilder.append(sep).append(t); - sep = ", "; - - if (n.isObject()) - schemas.add(new JsonSchema(validationContext, ValidatorTypeCode.TYPE.getValue(), parentSchema.getCurrentUri(), n, parentSchema)); - else - schemas.add(new TypeValidator(schemaPath + "/" + i, n, parentSchema, validationContext)); - - i++; - } - - errorBuilder.append(']'); - - error = errorBuilder.toString(); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); - - boolean valid = false; - - for (JsonValidator schema : schemas) { - Set errors = schema.validate(node, rootNode, at); - if (errors == null || errors.isEmpty()) { - valid = true; - break; - } - } - - if (!valid) { - return Collections.singleton(buildValidationMessage(at, nodeType.toString(), error)); - } - - return Collections.emptySet(); - } - - @Override - public void preloadJsonSchema() { - for (final JsonValidator validator : schemas) { - validator.preloadJsonSchema(); - } - } -} diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java deleted file mode 100644 index e34489090..000000000 --- a/src/main/java/com/networknt/schema/UniqueItemsValidator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public class UniqueItemsValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(UniqueItemsValidator.class); - - private boolean unique = false; - - public UniqueItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS, validationContext); - if (schemaNode.isBoolean()) { - unique = schemaNode.booleanValue(); - } - - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - if (unique) { - Set set = new HashSet(); - for (JsonNode n : node) { - if (!set.add(n)) { - return Collections.singleton(buildValidationMessage(at)); - } - } - } - - return Collections.emptySet(); - } - -} diff --git a/src/main/java/com/networknt/schema/ValidationContext.java b/src/main/java/com/networknt/schema/ValidationContext.java deleted file mode 100644 index 150cd51e3..000000000 --- a/src/main/java/com/networknt/schema/ValidationContext.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import java.util.HashMap; -import java.util.Map; -import java.util.Stack; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.schema.uri.URIFactory; -import com.networknt.schema.urn.URNFactory; - -public class ValidationContext { - private final URIFactory uriFactory; - private final URNFactory urnFactory; - private final JsonMetaSchema metaSchema; - private final JsonSchemaFactory jsonSchemaFactory; - private SchemaValidatorsConfig config; - private final Map refParsingInProgress = new HashMap(); - private final Stack discriminatorContexts = new Stack(); - - public ValidationContext(URIFactory uriFactory, URNFactory urnFactory, JsonMetaSchema metaSchema, - JsonSchemaFactory jsonSchemaFactory, SchemaValidatorsConfig config) { - if (uriFactory == null) { - throw new IllegalArgumentException("URIFactory must not be null"); - } - if (metaSchema == null) { - throw new IllegalArgumentException("JsonMetaSchema must not be null"); - } - if (jsonSchemaFactory == null) { - throw new IllegalArgumentException("JsonSchemaFactory must not be null"); - } - this.uriFactory = uriFactory; - this.urnFactory = urnFactory; - this.metaSchema = metaSchema; - this.jsonSchemaFactory = jsonSchemaFactory; - this.config = config; - } - - public JsonValidator newValidator(String schemaPath, String keyword /* keyword */, JsonNode schemaNode, - JsonSchema parentSchema, String customMessage) { - return metaSchema.newValidator(this, schemaPath, keyword, schemaNode, parentSchema, customMessage); - } - - public String resolveSchemaId(JsonNode schemaNode) { - return metaSchema.readId(schemaNode); - } - - public URIFactory getURIFactory() { - return this.uriFactory; - } - - public URNFactory getURNFactory() { - return this.urnFactory; - } - - public JsonSchemaFactory getJsonSchemaFactory() { - return jsonSchemaFactory; - } - - public SchemaValidatorsConfig getConfig() { - if (config == null) { - config = new SchemaValidatorsConfig(); - } - return config; - } - - public void setConfig(SchemaValidatorsConfig config) { - this.config = config; - } - - public void setReferenceParsingInProgress(String refValue, JsonSchemaRef ref) { - refParsingInProgress.put(refValue, ref); - } - - public JsonSchemaRef getReferenceParsingInProgress(String refValue) { - return refParsingInProgress.get(refValue); - } - - public DiscriminatorContext getCurrentDiscriminatorContext() { - if (!discriminatorContexts.empty()) { - return discriminatorContexts.peek(); - } - return null; // this is the case when we get on a schema that has a discriminator, but it's not used in anyOf - } - - public void enterDiscriminatorContext(final DiscriminatorContext ctx, String at) { - discriminatorContexts.push(ctx); - } - - public void leaveDiscriminatorContextImmediately(String at) { - discriminatorContexts.pop(); - } - - protected JsonMetaSchema getMetaSchema() { - return metaSchema; - } - - public static class DiscriminatorContext { - private final Map discriminators = new HashMap(); - - private boolean discriminatorMatchFound = false; - - public void registerDiscriminator(final String schemaPath, final ObjectNode discriminator) { - discriminators.put(schemaPath, discriminator); - } - - public ObjectNode getDiscriminatorForPath(final String schemaPath) { - return discriminators.get(schemaPath); - } - - public void markMatch() { - discriminatorMatchFound = true; - } - - public boolean isDiscriminatorMatchFound() { - return discriminatorMatchFound; - } - - /** - * Returns true if we have a discriminator active. In this case no valid match in anyOf should lead to validation failure - * - * @return true in case there are discriminator candidates - */ - public boolean isActive() { - return !discriminators.isEmpty(); - } - } -} diff --git a/src/main/java/com/networknt/schema/ValidationMessage.java b/src/main/java/com/networknt/schema/ValidationMessage.java deleted file mode 100644 index b68ec5623..000000000 --- a/src/main/java/com/networknt/schema/ValidationMessage.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import org.apache.commons.lang3.StringUtils; - -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Map; - -public class ValidationMessage { - private String type; - private String code; - private String path; - private String[] arguments; - private Map details; - private String message; - - ValidationMessage() { - } - - public String getCode() { - return code; - } - - void setCode(String code) { - this.code = code; - } - - public String getPath() { - return path; - } - - void setPath(String path) { - this.path = path; - } - - public String[] getArguments() { - return arguments; - } - - void setArguments(String[] arguments) { - this.arguments = arguments; - } - - void setDetails(Map details) { - this.details = details; - } - - public Map getDetails() { - return details; - } - - public String getMessage() { - return message; - } - - void setMessage(String message) { - this.message = message; - } - - @Override - public String toString() { - return message; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ValidationMessage that = (ValidationMessage) o; - - if (type != null ? !type.equals(that.type) : that.type != null) return false; - if (code != null ? !code.equals(that.code) : that.code != null) return false; - if (path != null ? !path.equals(that.path) : that.path != null) return false; - if (details != null ? !details.equals(that.details) : that.details != null) return false; - if (!Arrays.equals(arguments, that.arguments)) return false; - return !(message != null ? !message.equals(that.message) : that.message != null); - - } - - @Override - public int hashCode() { - int result = type != null ? type.hashCode() : 0; - result = 31 * result + (code != null ? code.hashCode() : 0); - result = 31 * result + (path != null ? path.hashCode() : 0); - result = 31 * result + (details != null ? details.hashCode() : 0); - result = 31 * result + (arguments != null ? Arrays.hashCode(arguments) : 0); - result = 31 * result + (message != null ? message.hashCode() : 0); - return result; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public static ValidationMessage of(String type, ErrorMessageType errorMessageType, String at, String... arguments) { - ValidationMessage.Builder builder = new ValidationMessage.Builder(); - builder.code(errorMessageType.getErrorCode()).path(at).arguments(arguments) - .format(errorMessageType.getMessageFormat()).type(type) - .customMessage(errorMessageType.getCustomMessage()); - return builder.build(); - } - - public static ValidationMessage of(String type, ErrorMessageType errorMessageType, String at, Map details) { - ValidationMessage.Builder builder = new ValidationMessage.Builder(); - builder.code(errorMessageType.getErrorCode()).path(at).details(details) - .format(errorMessageType.getMessageFormat()).type(type); - return builder.build(); - } - - public static class Builder { - private String type; - private String code; - private String path; - private String[] arguments; - private Map details; - private MessageFormat format; - private String customMessage; - - public Builder type(String type) { - this.type = type; - return this; - } - - public Builder code(String code) { - this.code = code; - return this; - } - - public Builder path(String path) { - this.path = path; - return this; - } - - public Builder arguments(String... arguments) { - this.arguments = arguments; - return this; - } - - public Builder details(Map details) { - this.details = details; - return this; - } - - public Builder format(MessageFormat format) { - this.format = format; - return this; - } - - public Builder customMessage(String customMessage) { - this.customMessage = customMessage; - return this; - } - - public ValidationMessage build() { - ValidationMessage msg = new ValidationMessage(); - msg.setType(type); - msg.setCode(code); - msg.setPath(path); - msg.setArguments(arguments); - msg.setDetails(details); - - if (format != null) { - String[] objs = new String[(arguments == null ? 0 : arguments.length) + 1]; - objs[0] = path; - if (arguments != null) { - for (int i = 1; i < objs.length; i++) { - objs[i] = arguments[i - 1]; - } - } - if(StringUtils.isNotBlank(customMessage)) { - msg.setMessage(customMessage); - } else { - msg.setMessage(format.format(objs)); - } - - } - - return msg; - } - } -} diff --git a/src/main/java/com/networknt/schema/ValidationResult.java b/src/main/java/com/networknt/schema/ValidationResult.java deleted file mode 100644 index 1b247fbfb..000000000 --- a/src/main/java/com/networknt/schema/ValidationResult.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import java.util.Set; - -public class ValidationResult { - - private Set validationMessages; - - private CollectorContext collectorContext; - - public ValidationResult(Set validationMessages, CollectorContext collectorContext) { - super(); - this.validationMessages = validationMessages; - this.collectorContext = collectorContext; - } - - public Set getValidationMessages() { - return validationMessages; - } - - public CollectorContext getCollectorContext() { - return collectorContext; - } - -} diff --git a/src/main/java/com/networknt/schema/Validator.java b/src/main/java/com/networknt/schema/Validator.java new file mode 100644 index 000000000..09434d990 --- /dev/null +++ b/src/main/java/com/networknt/schema/Validator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.walk.Walker; + +/** + * A processor that checks an instance node belonging to an instance document + * against a schema. + */ +public interface Validator extends Walker { + /** + * Validate the instance node which belongs to the instance document at the + * instance location. + * + * @param executionContext the execution context + * @param instanceNode the instance node being processed + * @param instance the instance document that the instance node belongs + * to + * @param instanceLocation the location of the instance node being processed + */ + void validate(ExecutionContext executionContext, JsonNode instanceNode, JsonNode instance, + NodePath instanceLocation); + + /** + * This is default implementation of walk method. Its job is to call the + * validate method if shouldValidateSchema is enabled. + */ + @Override + default void walk(ExecutionContext executionContext, JsonNode instanceNode, JsonNode instance, + NodePath instanceLocation, boolean shouldValidateSchema) { + if (instanceNode == null) { + // Note that null is not the same as NullNode + return; + } + if (shouldValidateSchema) { + validate(executionContext, instanceNode, instance, instanceLocation); + } + } + + /** + * The schema location is the canonical URI of the schema object plus a JSON + * Pointer fragment indicating the subschema that produced a result. In contrast + * with the evaluation path, the schema location MUST NOT include by-reference + * applicators such as $ref or $dynamicRef. + * + * @return the schema location + */ + SchemaLocation getSchemaLocation(); +} diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java deleted file mode 100644 index e9620b83e..000000000 --- a/src/main/java/com/networknt/schema/ValidatorState.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -public class ValidatorState { - - public static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState"; - - /** - * Flag set when a node has matched Works in conjunction with the next flag: - * isComplexValidator, to be used for complex validators such as oneOf, for ex - */ - private boolean matchedNode = true; - - /** - * Flag set if complex validators such as oneOf, for ex, neeed to have their - * properties validated. The PropertiesValidator is not aware generally of a - * complex validator is being validated or a simple poperty tree - */ - private boolean isComplexValidator = false; - - /** - * Flag to check if walking is enabled. - */ - private boolean isWalkEnabled = false; - - /** - * Flag to check if validation is enabled while walking. - */ - private boolean isValidationEnabled = false; - - public void setMatchedNode(boolean matchedNode) { - this.matchedNode = matchedNode; - } - - public boolean hasMatchedNode() { - return matchedNode; - } - - public boolean isComplexValidator() { - return isComplexValidator; - } - - public void setComplexValidator(boolean isComplexValidator) { - this.isComplexValidator = isComplexValidator; - } - - public boolean isWalkEnabled() { - return isWalkEnabled; - } - - public void setWalkEnabled(boolean isWalkEnabled) { - this.isWalkEnabled = isWalkEnabled; - } - - public boolean isValidationEnabled() { - return isValidationEnabled; - } - - public void setValidationEnabled(boolean isValidationEnabled) { - this.isValidationEnabled = isValidationEnabled; - } - -} diff --git a/src/main/java/com/networknt/schema/ValidatorTypeCode.java b/src/main/java/com/networknt/schema/ValidatorTypeCode.java deleted file mode 100644 index 6bcba7565..000000000 --- a/src/main/java/com/networknt/schema/ValidatorTypeCode.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; - -import java.lang.reflect.Constructor; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public enum ValidatorTypeCode implements Keyword, ErrorMessageType { - ADDITIONAL_PROPERTIES("additionalProperties", "1001", new MessageFormat(I18nSupport.getString("additionalProperties")), AdditionalPropertiesValidator.class, 15), // v4|v6|v7|v201909 - ALL_OF("allOf", "1002", new MessageFormat(I18nSupport.getString("allOf")), AllOfValidator.class, 15), - ANY_OF("anyOf", "1003", new MessageFormat(I18nSupport.getString("anyOf")), AnyOfValidator.class, 15), - CROSS_EDITS("crossEdits", "1004", new MessageFormat(I18nSupport.getString("crossEdits")), null, 15), - DEPENDENCIES("dependencies", "1007", new MessageFormat(I18nSupport.getString("dependencies")), DependenciesValidator.class, 15), - EDITS("edits", "1005", new MessageFormat(I18nSupport.getString("edits")), null, 15), - ENUM("enum", "1008", new MessageFormat(I18nSupport.getString("enum")), EnumValidator.class, 15), - FORMAT("format", "1009", new MessageFormat(I18nSupport.getString("format")), null, 15) { - @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) - throws Exception { - throw new UnsupportedOperationException("Use FormatKeyword instead"); - } - }, - ITEMS("items", "1010", new MessageFormat(I18nSupport.getString("items")), ItemsValidator.class, 15), - MAXIMUM("maximum", "1011", new MessageFormat(I18nSupport.getString("maximum")), MaximumValidator.class, 15), - MAX_ITEMS("maxItems", "1012", new MessageFormat(I18nSupport.getString("maxItems")), MaxItemsValidator.class, 15), - MAX_LENGTH("maxLength", "1013", new MessageFormat(I18nSupport.getString("maxLength")), MaxLengthValidator.class, 15), - MAX_PROPERTIES("maxProperties", "1014", new MessageFormat(I18nSupport.getString("maxProperties")), MaxPropertiesValidator.class, 15), - MINIMUM("minimum", "1015", new MessageFormat(I18nSupport.getString("minimum")), MinimumValidator.class, 15), - MIN_ITEMS("minItems", "1016", new MessageFormat(I18nSupport.getString("minItems")), MinItemsValidator.class, 15), - MIN_LENGTH("minLength", "1017", new MessageFormat(I18nSupport.getString("minLength")), MinLengthValidator.class, 15), - MIN_PROPERTIES("minProperties", "1018", new MessageFormat(I18nSupport.getString("minProperties")), MinPropertiesValidator.class, 15), - MULTIPLE_OF("multipleOf", "1019", new MessageFormat(I18nSupport.getString("multipleOf")), MultipleOfValidator.class, 15), - NOT_ALLOWED("notAllowed", "1033", new MessageFormat(I18nSupport.getString("notAllowed")), NotAllowedValidator.class, 15), - NOT("not", "1020", new MessageFormat(I18nSupport.getString("not")), NotValidator.class, 15), - ONE_OF("oneOf", "1022", new MessageFormat(I18nSupport.getString("oneOf")), OneOfValidator.class, 15), - PATTERN_PROPERTIES("patternProperties", "1024", new MessageFormat(I18nSupport.getString("patternProperties")), PatternPropertiesValidator.class, 15), - PATTERN("pattern", "1023", new MessageFormat(I18nSupport.getString("pattern")), PatternValidator.class, 15), - PROPERTIES("properties", "1025", new MessageFormat(I18nSupport.getString("properties")), PropertiesValidator.class, 15), - READ_ONLY("readOnly", "1032", new MessageFormat(I18nSupport.getString("readOnly")), ReadOnlyValidator.class, 15), - REF("$ref", "1026", new MessageFormat(I18nSupport.getString("$ref")), RefValidator.class, 15), - REQUIRED("required", "1028", new MessageFormat(I18nSupport.getString("required")), RequiredValidator.class, 15), - TYPE("type", "1029", new MessageFormat(I18nSupport.getString("type")), TypeValidator.class, 15), - UNION_TYPE("unionType", "1030", new MessageFormat(I18nSupport.getString("unionType")), UnionTypeValidator.class, 15), - UNIQUE_ITEMS("uniqueItems", "1031", new MessageFormat(I18nSupport.getString("uniqueItems")), UniqueItemsValidator.class, 15), - DATETIME("date-time", "1034", new MessageFormat("{0}: {1} is an invalid {2}"), null, 15), - UUID("uuid", "1035", new MessageFormat(I18nSupport.getString("uuid")), null, 15), - ID("id", "1036", new MessageFormat(I18nSupport.getString("id")), null, 15), - IF_THEN_ELSE("if", "1037", null, IfValidator.class, 12), // V7|V201909 - EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", new MessageFormat(I18nSupport.getString("exclusiveMaximum")), ExclusiveMaximumValidator.class, 14), // V6|V7|V201909 - EXCLUSIVE_MINIMUM("exclusiveMinimum", "1039", new MessageFormat(I18nSupport.getString("exclusiveMinimum")), ExclusiveMinimumValidator.class, 14), - TRUE("true", "1040", null, TrueValidator.class, 14), - FALSE("false", "1041", new MessageFormat(I18nSupport.getString("false")), FalseValidator.class, 14), - CONST("const", "1042", new MessageFormat(I18nSupport.getString("const")), ConstValidator.class, 14), - CONTAINS("contains", "1043", new MessageFormat(I18nSupport.getString("contains")), ContainsValidator.class, 14), - PROPERTYNAMES("propertyNames", "1044", new MessageFormat(I18nSupport.getString("propertyNames")), PropertyNamesValidator.class, 14), - DEPENDENT_REQUIRED("dependentRequired", "1045", new MessageFormat(I18nSupport.getString("dependentRequired")), DependentRequired.class, 8), // V201909 - DEPENDENT_SCHEMAS("dependentSchemas", "1046", new MessageFormat(I18nSupport.getString("dependentSchemas")), DependentSchemas.class, 8); // V201909 - - private static Map constants = new HashMap(); - private static SpecVersion specVersion = new SpecVersion(); - - static { - for (ValidatorTypeCode c : values()) { - constants.put(c.value, c); - } - } - - private final String value; - private final String errorCode; - private final MessageFormat messageFormat; - private String customMessage; - private final String errorCodeKey; - private final Class validator; - private final long versionCode; - - - private ValidatorTypeCode(String value, String errorCode, MessageFormat messageFormat, Class validator, long versionCode) { - this.value = value; - this.errorCode = errorCode; - this.messageFormat = messageFormat; - this.errorCodeKey = value + "ErrorCode"; - this.validator = validator; - this.versionCode = versionCode; - this.customMessage = null; - } - - public static List getNonFormatKeywords(SpecVersion.VersionFlag versionFlag) { - final List result = new ArrayList(); - for (ValidatorTypeCode keyword : values()) { - if (!FORMAT.equals(keyword) && specVersion.getVersionFlags(keyword.versionCode).contains(versionFlag)) { - result.add(keyword); - } - } - return result; - } - - public static ValidatorTypeCode fromValue(String value) { - ValidatorTypeCode constant = constants.get(value); - if (constant == null) { - throw new IllegalArgumentException(value); - } else { - return constant; - } - } - - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws Exception { - if (validator == null) { - throw new UnsupportedOperationException("No suitable validator for " + getValue()); - } - // if the config version is not match the validator - @SuppressWarnings("unchecked") - Constructor c = ((Class) validator).getConstructor( - new Class[]{String.class, JsonNode.class, JsonSchema.class, ValidationContext.class}); - return c.newInstance(schemaPath + "/" + getValue(), schemaNode, parentSchema, validationContext); - } - - @Override - public String toString() { - return this.value; - } - - public String getValue() { - return value; - } - - public String getErrorCode() { - return errorCode; - } - - public MessageFormat getMessageFormat() { - return messageFormat; - } - - public void setCustomMessage(String message) { - this.customMessage = message; - } - - public String getCustomMessage() { - return customMessage; - } - - public String getErrorCodeKey() { - return errorCodeKey; - } - - public long getVersionCode() { - return versionCode; - } -} diff --git a/src/main/java/com/networknt/schema/annotation/Annotation.java b/src/main/java/com/networknt/schema/annotation/Annotation.java new file mode 100644 index 000000000..7d59579d0 --- /dev/null +++ b/src/main/java/com/networknt/schema/annotation/Annotation.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.annotation; + +import java.util.Objects; + +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.path.NodePath; + +/** + * The annotation. + */ +public class Annotation { + private final String keyword; + private final NodePath instanceLocation; + private final SchemaLocation schemaLocation; + private final NodePath evaluationPath; + private final Object value; + private boolean valid = true; // If not valid it means dropped + + public Annotation(String keyword, NodePath instanceLocation, SchemaLocation schemaLocation, + NodePath evaluationPath, Object value) { + super(); + this.keyword = keyword; + this.instanceLocation = instanceLocation; + this.schemaLocation = schemaLocation; + this.evaluationPath = evaluationPath; + this.value = value; + } + + /** + * The keyword that produces the annotation. + * + * @return the keyword + */ + public String getKeyword() { + return keyword; + } + + /** + * The instance location to which it is attached, as a JSON Pointer. + * + * @return the instance location + */ + public NodePath getInstanceLocation() { + return instanceLocation; + } + + /** + * The schema location of the attaching keyword, as a IRI and JSON Pointer + * fragment. + * + * @return the schema location + */ + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + /** + * The evaluation path, indicating how reference keywords such as "$ref" were + * followed to reach the absolute schema location. + * + * @return the evaluation path + */ + public NodePath getEvaluationPath() { + return evaluationPath; + } + + /** + * The attached value(s). + * + * @param the value type + * @return the value + */ + @SuppressWarnings("unchecked") + public T getValue() { + return (T) value; + } + + public boolean isValid() { + return valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + @Override + public String toString() { + return "Annotation [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation + + ", instanceLocation=" + instanceLocation + ", keyword=" + keyword + ", value=" + value + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(evaluationPath, instanceLocation, keyword, schemaLocation, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Annotation other = (Annotation) obj; + return Objects.equals(evaluationPath, other.evaluationPath) + && Objects.equals(instanceLocation, other.instanceLocation) && Objects.equals(keyword, other.keyword) + && Objects.equals(schemaLocation, other.schemaLocation) && Objects.equals(value, other.value); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String keyword; + private NodePath instanceLocation; + private SchemaLocation schemaLocation; + private NodePath evaluationPath; + private Object value; + + public Builder keyword(Keyword keyword) { + this.keyword = keyword.getValue(); + return this; + } + + public Builder keyword(String keyword) { + this.keyword = keyword; + return this; + } + + public Builder instanceLocation(NodePath instanceLocation) { + this.instanceLocation = instanceLocation; + return this; + } + + public Builder schemaLocation(SchemaLocation schemaLocation) { + this.schemaLocation = schemaLocation; + return this; + } + + public Builder evaluationPath(NodePath evaluationPath) { + this.evaluationPath = evaluationPath; + return this; + } + + public Builder value(Object value) { + this.value = value; + return this; + } + + public Annotation build() { + return new Annotation(keyword, instanceLocation, schemaLocation, evaluationPath, value); + } + } + +} diff --git a/src/main/java/com/networknt/schema/annotation/AnnotationPredicate.java b/src/main/java/com/networknt/schema/annotation/AnnotationPredicate.java new file mode 100644 index 000000000..c9268e17c --- /dev/null +++ b/src/main/java/com/networknt/schema/annotation/AnnotationPredicate.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.annotation; + +import java.util.function.Predicate; + +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * A predicate for filtering annotations. + */ +public class AnnotationPredicate implements Predicate { + final Predicate instanceLocationPredicate; + final Predicate evaluationPathPredicate; + final Predicate schemaLocationPredicate; + final Predicate keywordPredicate; + final Predicate valuePredicate; + + /** + * Initialize a new instance of this class. + * + * @param instanceLocationPredicate for instanceLocation + * @param evaluationPathPredicate for evaluationPath + * @param schemaLocationPredicate for schemaLocation + * @param keywordPredicate for keyword + * @param valuePredicate for value + */ + protected AnnotationPredicate(Predicate instanceLocationPredicate, + Predicate evaluationPathPredicate, Predicate schemaLocationPredicate, + Predicate keywordPredicate, Predicate valuePredicate) { + super(); + this.instanceLocationPredicate = instanceLocationPredicate; + this.evaluationPathPredicate = evaluationPathPredicate; + this.schemaLocationPredicate = schemaLocationPredicate; + this.keywordPredicate = keywordPredicate; + this.valuePredicate = valuePredicate; + } + + @Override + public boolean test(Annotation t) { + return ((valuePredicate == null || valuePredicate.test(t.getValue())) + && (keywordPredicate == null || keywordPredicate.test(t.getKeyword())) + && (instanceLocationPredicate == null || instanceLocationPredicate.test(t.getInstanceLocation())) + && (evaluationPathPredicate == null || evaluationPathPredicate.test(t.getEvaluationPath())) + && (schemaLocationPredicate == null || schemaLocationPredicate.test(t.getSchemaLocation()))); + } + + /** + * Gets the predicate to filter on instanceLocation. + * + * @return the predicate + */ + public Predicate getInstanceLocationPredicate() { + return instanceLocationPredicate; + } + + /** + * Gets the predicate to filter on evaluationPath. + * + * @return the predicate + */ + public Predicate getEvaluationPathPredicate() { + return evaluationPathPredicate; + } + + /** + * Gets the predicate to filter on schemaLocation. + * + * @return the predicate + */ + public Predicate getSchemaLocationPredicate() { + return schemaLocationPredicate; + } + + /** + * Gets the predicate to filter on keyword. + * + * @return the predicate + */ + public Predicate getKeywordPredicate() { + return keywordPredicate; + } + + /** + * Gets the predicate to filter on value. + * + * @return the predicate + */ + public Predicate getValuePredicate() { + return valuePredicate; + } + + /** + * Creates a new builder to create the predicate. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for building a {@link AnnotationPredicate}. + */ + public static class Builder { + Predicate instanceLocationPredicate; + Predicate evaluationPathPredicate; + Predicate schemaLocationPredicate; + Predicate keywordPredicate; + Predicate valuePredicate; + + public Builder instanceLocation(Predicate instanceLocationPredicate) { + this.instanceLocationPredicate = instanceLocationPredicate; + return this; + } + + public Builder evaluationPath(Predicate evaluationPathPredicate) { + this.evaluationPathPredicate = evaluationPathPredicate; + return this; + } + + public Builder schema(Predicate schemaLocationPredicate) { + this.schemaLocationPredicate = schemaLocationPredicate; + return this; + } + + public Builder keyword(Predicate keywordPredicate) { + this.keywordPredicate = keywordPredicate; + return this; + } + + public Builder value(Predicate valuePredicate) { + this.valuePredicate = valuePredicate; + return this; + } + + public AnnotationPredicate build() { + return new AnnotationPredicate(instanceLocationPredicate, evaluationPathPredicate, + schemaLocationPredicate, keywordPredicate, valuePredicate); + } + } +} diff --git a/src/main/java/com/networknt/schema/annotation/Annotations.java b/src/main/java/com/networknt/schema/annotation/Annotations.java new file mode 100644 index 000000000..581f73b8f --- /dev/null +++ b/src/main/java/com/networknt/schema/annotation/Annotations.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.annotation; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * The JSON Schema annotations. + * + * @see Details + * of annotation collection + */ +public class Annotations { + + /** + * Stores the annotations. + *

+ * instancePath to annotation + */ + private final Map> values = new LinkedHashMap<>(); + + /** + * Gets the annotations. + *

+ * instancePath to annotation + * + * @return the annotations + */ + public Map> asMap() { + return this.values; + } + + /** + * Puts the annotation. + * + * @param annotation the annotation + */ + public void put(Annotation annotation) { + this.values.computeIfAbsent(annotation.getInstanceLocation(), (k) -> new ArrayList<>()).add(annotation); + + } + + @Override + public String toString() { + return Formatter.format(this.values); + } + + /** + * Formatter for pretty printing the annotations. + */ + public static class Formatter { + /** + * Formats the annotations. + * + * @param annotations the annotations + * @return the formatted JSON + */ + public static String format(Map> annotations) { + Map>> results = new LinkedHashMap<>(); + for (List list : annotations.values()) { + for (Annotation annotation : list) { + String keyword = annotation.getKeyword(); + String instancePath = annotation.getInstanceLocation().toString(); + String evaluationPath = annotation.getEvaluationPath().toString(); + Map values = results + .computeIfAbsent(instancePath, (key) -> new LinkedHashMap<>()) + .computeIfAbsent(keyword, (key) -> new LinkedHashMap<>()); + values.put(evaluationPath, annotation.getValue()); + } + } + + try { + return JsonMapperFactory.getInstance().writerWithDefaultPrettyPrinter().writeValueAsString(results); + } catch (JsonProcessingException e) { + return ""; + } + } + + } + +} diff --git a/src/main/java/com/networknt/schema/dialect/AbstractDialectRegistry.java b/src/main/java/com/networknt/schema/dialect/AbstractDialectRegistry.java new file mode 100644 index 000000000..7bddaacfa --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/AbstractDialectRegistry.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.dialect; + +import java.util.Map; +import java.util.Map.Entry; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; + +/** + * Abstract {@link DialectRegistry}. + */ +public abstract class AbstractDialectRegistry implements DialectRegistry { + protected Dialect loadDialect(String iri, SchemaRegistry schemaFactory) { + try { + Dialect result = loadDialectBuilder(iri, schemaFactory).build(); + return result; + } catch (InvalidSchemaException e) { + throw e; + } catch (Exception e) { + Error error = Error.builder().message("Failed to load dialect ''{0}''").arguments(iri).build(); + throw new InvalidSchemaException(error, e); + } + } + + protected Dialect.Builder loadDialectBuilder(String iri, SchemaRegistry schemaFactory) { + Schema schema = schemaFactory.getSchema(SchemaLocation.of(iri)); + Dialect.Builder builder = Dialect.builder(iri, schema.getSchemaContext().getDialect()); + SpecificationVersion specification = schema.getSchemaContext().getDialect().getSpecificationVersion(); + if (specification != null) { + if (specification.getOrder() >= SpecificationVersion.DRAFT_2019_09.getOrder()) { + // Process vocabularies + JsonNode vocabulary = schema.getSchemaNode().get("$vocabulary"); + if (vocabulary != null) { + builder.vocabularies(Map::clear); + for (Entry vocabs : vocabulary.properties()) { + builder.vocabulary(vocabs.getKey(), vocabs.getValue().booleanValue()); + } + } + } + } + return builder; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/BasicDialectRegistry.java b/src/main/java/com/networknt/schema/dialect/BasicDialectRegistry.java new file mode 100644 index 000000000..137b78a15 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/BasicDialectRegistry.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.dialect; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import com.networknt.schema.Error; +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.SchemaRegistry; + +/** + * Basic {@link DialectRegistry}. + */ +public class BasicDialectRegistry extends AbstractDialectRegistry { + protected final Function dialects; + + protected BasicDialectRegistry() { + this.dialects = null; + } + + public BasicDialectRegistry(Function dialects) { + this.dialects = dialects; + } + + public BasicDialectRegistry(Dialect dialect) { + this.dialects = dialectId -> dialect.getId().equals(dialectId) ? dialect : null; + } + + public BasicDialectRegistry(Collection dialects) { + Map result = new HashMap<>(); + for (Dialect dialect : dialects) { + result.put(dialect.getId(), dialect); + } + this.dialects = result::get; + } + + @Override + public Dialect getDialect(String dialectId, SchemaRegistry schemaRegistry) { + Dialect dialect = dialects.apply(dialectId); + if (dialect != null) { + return dialect; + } + throw new InvalidSchemaException(Error.builder() + .message("Unknown dialect ''{0}''. Only dialects that are explicitly configured can be used.") + .arguments(dialectId).build()); + } +} diff --git a/src/main/java/com/networknt/schema/dialect/DefaultDialectRegistry.java b/src/main/java/com/networknt/schema/dialect/DefaultDialectRegistry.java new file mode 100644 index 000000000..d66d0f39e --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/DefaultDialectRegistry.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.dialect; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.Specification; + +/** + * Default {@link DialectRegistry}. + */ +public class DefaultDialectRegistry extends BasicDialectRegistry { + private final ConcurrentMap loadedDialects = new ConcurrentHashMap<>(); + + public DefaultDialectRegistry() { + super(); + } + + public DefaultDialectRegistry(Function dialects) { + super(dialects); + } + + public DefaultDialectRegistry(Dialect dialect) { + super(dialect); + } + + public DefaultDialectRegistry(Collection dialects) { + super(dialects); + } + + @Override + public Dialect getDialect(String dialectId, SchemaRegistry schemaFactory) { + if (this.dialects != null) { + Dialect dialect = dialects.apply(dialectId); + if (dialect != null) { + return dialect; + } + } + // Is it a well-known dialect? + Dialect dialect = Specification.getDialect(dialectId); + if (dialect != null) { + return dialect; + } + return loadedDialects.computeIfAbsent(dialectId, id -> loadDialect(id, schemaFactory)); + } + + private static class Holder { + private static final DefaultDialectRegistry INSTANCE = new DefaultDialectRegistry(); + } + + public static DefaultDialectRegistry getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Dialect.java b/src/main/java/com/networknt/schema/dialect/Dialect.java new file mode 100644 index 000000000..3fd4580c8 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Dialect.java @@ -0,0 +1,508 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.dialect; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Format; +import com.networknt.schema.keyword.FormatKeyword; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.KeywordFactory; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.keyword.UnknownKeywordFactory; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.utils.Strings; +import com.networknt.schema.vocabulary.Vocabularies; +import com.networknt.schema.vocabulary.Vocabulary; +import com.networknt.schema.vocabulary.VocabularyRegistry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * A dialect represents the set of keywords and semantics that can be used to + * evaluate a schema. The dialect can be uniquely identified by its IRI which + * points to the meta-schema used to validate schemas written for that dialect. + * The dialect for a particular schema is indicated using the $schema keyword. + */ +public class Dialect { + private static final Logger logger = LoggerFactory.getLogger(Dialect.class); + + /** + * Factory for creating a format keyword. + */ + public interface FormatKeywordFactory { + /** + * Creates a format keyword. + * + * @param formats the formats + * @return the format keyword + */ + FormatKeyword newInstance(Map formats); + } + + /** + * Builder for {@link Dialect}. + */ + public static class Builder { + private String id; + private String idKeyword = "$id"; + private SpecificationVersion specificationVersion = null; + private final Map keywords = new HashMap<>(); + private final Map formats = new HashMap<>(); + private final Map vocabularies = new HashMap<>(); + private FormatKeywordFactory formatKeywordFactory = null; + private VocabularyRegistry vocabularyRegistry = null; + private KeywordFactory unknownKeywordFactory = null; + + public Builder(String id) { + this.id = id; + } + + private Map createKeywordsMap(Map kwords, Map formats) { + boolean formatKeywordPresent = false; + Map map = new HashMap<>(); + for (Map.Entry type : kwords.entrySet()) { + String keywordName = type.getKey(); + Keyword keyword = type.getValue(); + if (KeywordType.FORMAT.getValue().equals(keywordName)) { + if (!(keyword instanceof FormatKeyword) && !KeywordType.FORMAT.equals(keyword)) { + throw new IllegalArgumentException("Overriding the keyword 'format' is not supported. Use the formatKeywordFactory and extend the FormatKeyword."); + } + // Indicate that the format keyword needs to be created + formatKeywordPresent = true; + } else { + map.put(keyword.getValue(), keyword); + } + } + if (formatKeywordPresent) { + final FormatKeyword formatKeyword = formatKeywordFactory != null ? formatKeywordFactory.newInstance(formats) + : new FormatKeyword(formats); + map.put(formatKeyword.getValue(), formatKeyword); + } + return map; + } + + /** + * Sets the format keyword factory. + * + * @param formatKeywordFactory the format keyword factory + * @return the builder + */ + public Builder formatKeywordFactory(FormatKeywordFactory formatKeywordFactory) { + this.formatKeywordFactory = formatKeywordFactory; + return this; + } + + /** + * Sets the vocabulary registry for handling custom vocabularies. + * + * @param vocabularyRegistry the registry + * @return the builder + */ + public Builder vocabularyRegistry(VocabularyRegistry vocabularyRegistry) { + this.vocabularyRegistry = vocabularyRegistry; + return this; + } + + /** + * Sets the keyword factory for handling unknown keywords. + * + * @param unknownKeywordFactory the factory + * @return the builder + */ + public Builder unknownKeywordFactory(KeywordFactory unknownKeywordFactory) { + this.unknownKeywordFactory = unknownKeywordFactory; + return this; + } + + /** + * Customize the formats. + * + * @param customizer the customizer + * @return the builder + */ + public Builder formats(Consumer> customizer) { + customizer.accept(this.formats); + return this; + } + + /** + * Customize the keywords. + * + * @param customizer the customizer + * @return the builder + */ + public Builder keywords(Consumer> customizer) { + customizer.accept(this.keywords); + return this; + } + + /** + * Adds the keyword. + * + * @param keyword the keyword + * @return the builder + */ + public Builder keyword(Keyword keyword) { + this.keywords.put(keyword.getValue(), keyword); + return this; + } + + /** + * Adds the keywords. + * + * @param keywords the keywords + * @return the builder + */ + public Builder keywords(Collection keywords) { + for (Keyword keyword : keywords) { + this.keywords.put(keyword.getValue(), keyword); + } + return this; + } + + /** + * Adds the format. + * + * @param format the format + * @return the builder + */ + public Builder format(Format format) { + this.formats.put(format.getName(), format); + return this; + } + + /** + * Adds the formats. + * + * @param formats the formats + * @return the builder + */ + public Builder formats(Collection formats) { + for (Format format : formats) { + format(format); + } + return this; + } + + /** + * Adds a required vocabulary. + *

+ * Note that an error will be raised if this vocabulary is unknown. + * + * @param vocabulary the vocabulary IRI + * @return the builder + */ + public Builder vocabulary(String vocabulary) { + return vocabulary(vocabulary, true); + } + + /** + * Adds a vocabulary. + * + * @param vocabulary the vocabulary IRI + * @param required true indicates if the vocabulary is not recognized + * processing should stop + * @return the builder + */ + public Builder vocabulary(String vocabulary, boolean required) { + this.vocabularies.put(vocabulary, required); + return this; + } + + /** + * Adds the vocabularies. + * + * @param vocabularies the vocabularies to add + * @return the builder + */ + public Builder vocabularies(Map vocabularies) { + this.vocabularies.putAll(vocabularies); + return this; + } + + /** + * Customize the vocabularies. + * + * @param customizer the customizer + * @return the builder + */ + public Builder vocabularies(Consumer> customizer) { + customizer.accept(this.vocabularies); + return this; + } + + /** + * Sets the specification version. + * + * @param specification the specification version + * @return the builder + */ + public Builder specificationVersion(SpecificationVersion specification) { + this.specificationVersion = specification; + return this; + } + + /** + * Sets the id keyword. + * + * @param idKeyword the id keyword + * @return the builder + */ + public Builder idKeyword(String idKeyword) { + this.idKeyword = idKeyword; + return this; + } + + public Dialect build() { + // create builtin keywords with (custom) formats. + Map keywords = this.keywords; + if (this.specificationVersion != null) { + if (this.specificationVersion.getOrder() >= SpecificationVersion.DRAFT_2019_09.getOrder()) { + keywords = new HashMap<>(this.keywords); + for(Entry entry : this.vocabularies.entrySet()) { + Vocabulary vocabulary = null; + String id = entry.getKey(); + if (this.vocabularyRegistry != null) { + vocabulary = this.vocabularyRegistry.getVocabulary(id); + } + if (vocabulary == null) { + vocabulary = Vocabularies.getVocabulary(id); + } + if (vocabulary != null) { + for (Keyword keyword : vocabulary.getKeywords()) { + // Only add keyword from vocabulary if not already + // ie. allow overriding the keyword by explicitly adding it + keywords.putIfAbsent(keyword.getValue(), keyword); + } + } else if (Boolean.TRUE.equals(entry.getValue())) { + Error error = Error.builder() + .message("Meta-schema ''{0}'' has unknown required vocabulary ''{1}''") + .arguments(this.id, id).build(); + throw new InvalidSchemaException(error); + } + } + } + } + Map result = createKeywordsMap(keywords, this.formats); + return new Dialect(this.id, this.idKeyword, result, this.vocabularies, this.specificationVersion, this); + } + } + + private final String id; + private final String idKeyword; + private final Map keywords; + private final Map vocabularies; + private final SpecificationVersion specificationVersion; + + private final Builder builder; + + Dialect(String dialectId, String idKeyword, Map keywords, Map vocabularies, SpecificationVersion specification, Builder builder) { + if (Strings.isBlank(dialectId)) { + throw new IllegalArgumentException("dialect id must not be null or blank"); + } + if (Strings.isBlank(idKeyword)) { + throw new IllegalArgumentException("idKeyword must not be null or blank"); + } + if (keywords == null) { + throw new IllegalArgumentException("keywords must not be null "); + } + + this.id = dialectId; + this.idKeyword = idKeyword; + this.keywords = keywords; + this.specificationVersion = specification; + this.vocabularies = vocabularies; + this.builder = builder; + } + + /** + * Create a builder without keywords or formats. + * + * @param id the IRI of the dialect that will be defined via this builder. + * @return a builder instance without any keywords or formats - usually not what one needs. + */ + public static Builder builder(String id) { + return new Builder(id); + } + + /** + * Create a builder. + * + * @param id the IRI of your new Dialect that will be defined via + * this builder. + * @param blueprint the Dialect to base your custom Dialect on. + * @return a builder instance preconfigured to be the same as blueprint, but + * with a different uri. + */ + public static Builder builder(String id, Dialect blueprint) { + Builder builder = builder(blueprint); + builder.id = id; + return builder; + } + + /** + * Create a builder. + * + * @param blueprint the Dialect to base your custom Dialect on. + * @return a builder instance preconfigured to be the same as blueprint + */ + public static Builder builder(Dialect blueprint) { + Map vocabularies = new HashMap<>(blueprint.getVocabularies()); + return builder(blueprint.getId()) + .idKeyword(blueprint.idKeyword) + .keywords(blueprint.builder.keywords.values()) + .formats(blueprint.builder.formats.values()) + .specificationVersion(blueprint.getSpecificationVersion()) + .vocabularies(vocabularies) + .vocabularyRegistry(blueprint.builder.vocabularyRegistry) + .formatKeywordFactory(blueprint.builder.formatKeywordFactory) + .unknownKeywordFactory(blueprint.builder.unknownKeywordFactory) + ; + } + + public String getIdKeyword() { + return this.idKeyword; + } + + public String readId(JsonNode schemaNode) { + return readText(schemaNode, this.idKeyword); + } + + public String readAnchor(JsonNode schemaNode) { + boolean supportsAnchor = this.keywords.containsKey("$anchor"); + if (supportsAnchor) { + return readText(schemaNode, "$anchor"); + } + return null; + } + + public String readDynamicAnchor(JsonNode schemaNode) { + boolean supportsDynamicAnchor = this.keywords.containsKey("$dynamicAnchor"); + if (supportsDynamicAnchor) { + return readText(schemaNode, "$dynamicAnchor"); + } + return null; + } + + private static String readText(JsonNode node, String field) { + JsonNode fieldNode = node.get(field); + return fieldNode == null ? null : fieldNode.textValue(); + } + + public String getId() { + return this.id; + } + + public Map getKeywords() { + return this.keywords; + } + + public Map getVocabularies() { + return this.vocabularies; + } + + public SpecificationVersion getSpecificationVersion() { + return this.specificationVersion; + } + + /** + * Creates a new validator of the keyword. + * + * @param schemaContext the schema context + * @param schemaLocation the schema location + * @param keyword the keyword + * @param schemaNode the schema node + * @param parentSchema the parent schema + * @return the validator + */ + public KeywordValidator newValidator(SchemaContext schemaContext, SchemaLocation schemaLocation, + String keyword, JsonNode schemaNode, Schema parentSchema) { + try { + Keyword kw = this.keywords.get(keyword); + if (kw == null) { + if (keyword.equals(schemaContext.getSchemaRegistryConfig().getErrorMessageKeyword())) { + return null; + } + if (schemaContext.isNullableKeywordEnabled() && "nullable".equals(keyword)) { + return null; + } + if (KeywordType.DISCRIMINATOR.getValue().equals(keyword) + && schemaContext.isDiscriminatorKeywordEnabled()) { + return KeywordType.DISCRIMINATOR.newValidator(schemaLocation, schemaNode, + parentSchema, schemaContext); + } + kw = this.builder.unknownKeywordFactory != null + ? this.builder.unknownKeywordFactory.getKeyword(keyword, schemaContext) + : UnknownKeywordFactory.getInstance().getKeyword(keyword, schemaContext); + if (kw == null) { + return null; + } + } + return kw.newValidator(schemaLocation, schemaNode, parentSchema, schemaContext); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof SchemaException) { + logger.error("Error:", e); + throw (SchemaException) e.getTargetException(); + } + logger.warn("Could not load validator {}", keyword); + throw new SchemaException(e.getTargetException()); + } catch (SchemaException e) { + throw e; + } catch (Exception e) { + logger.warn("Could not load validator {}", keyword); + throw new SchemaException(e); + } + } + + @Override + public String toString() { + return this.id; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Dialect other = (Dialect) obj; + return Objects.equals(id, other.id); + } +} diff --git a/src/main/java/com/networknt/schema/dialect/DialectId.java b/src/main/java/com/networknt/schema/dialect/DialectId.java new file mode 100644 index 000000000..d5e910c6e --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/DialectId.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.dialect; + +/** + * The dialect id is an IRI that points to the meta-schema that can be used to + * validate schemas written for that dialect. The dialect used for a particular + * schema is indicated using the $schema keyword. + */ +public class DialectId { + /** + * Draft 4. + */ + public static final String DRAFT_4 = "http://json-schema.org/draft-04/schema#"; + + /** + * Draft 6. + */ + public static final String DRAFT_6 = "http://json-schema.org/draft-06/schema#"; + + /** + * Draft 7. + */ + public static final String DRAFT_7 = "http://json-schema.org/draft-07/schema#"; + + /** + * Draft 2019-09. + */ + public static final String DRAFT_2019_09 = "https://json-schema.org/draft/2019-09/schema"; + + /** + * Draft 2020-12. + */ + public static final String DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"; + + /** + * OpenAPI 3.0. + */ + public static final String OPENAPI_3_0 = "https://spec.openapis.org/oas/3.0/dialect"; + + /** + * OpenAPI 3.1 + */ + public static final String OPENAPI_3_1 = "https://spec.openapis.org/oas/3.1/dialect/base"; +} diff --git a/src/main/java/com/networknt/schema/dialect/DialectRegistry.java b/src/main/java/com/networknt/schema/dialect/DialectRegistry.java new file mode 100644 index 000000000..4c1d7d883 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/DialectRegistry.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.dialect; + +import com.networknt.schema.SchemaRegistry; + +/** + * Registry for {@link Dialect} that can be retrieved using the dialect id which + * is the IRI that indicates the meta-schema that can be used to validate the + * schema conforms to the dialect. + */ +@FunctionalInterface +public interface DialectRegistry { + /** + * Gets the dialect given the dialect id which is the IRI that indicates the + * meta-schema that can be used to validate the schema conforms to the dialect. + * + * @param dialectId the dialect id of the dialect which IRI that indicates + * the meta-schema that can be used to validate the schema + * conforms to the dialect + * @param schemaRegistry the schema registry to fetch and load unknown dialect's + * meta-schema + * @return the dialect + */ + Dialect getDialect(String dialectId, SchemaRegistry schemaRegistry); +} diff --git a/src/main/java/com/networknt/schema/dialect/Dialects.java b/src/main/java/com/networknt/schema/dialect/Dialects.java new file mode 100644 index 000000000..9095565e7 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Dialects.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.dialect; + +/** + * The dialects. + */ +public class Dialects { + /** + * Draft 4. + * + * @return the Draft 4 dialect + */ + public static Dialect getDraft4() { + return Draft4.getInstance(); + } + + /** + * Draft 6. + * + * @return the Draft 6 dialect + */ + public static Dialect getDraft6() { + return Draft6.getInstance(); + } + + /** + * Draft 7. + * + * @return the Draft 7 dialect + */ + public static Dialect getDraft7() { + return Draft7.getInstance(); + } + + /** + * Draft 2019-09. + * + * @return the Draft 2019-09 dialect + */ + public static Dialect getDraft201909() { + return Draft201909.getInstance(); + } + + /** + * Draft 2020-12. + * + * @return the Draft 2020-12 dialect + */ + public static Dialect getDraft202012() { + return Draft202012.getInstance(); + } + + /** + * OpenAPI 3.0. + * + * @return the OpenAPI 3.0 dialect + */ + public static Dialect getOpenApi30() { + return OpenApi30.getInstance(); + } + + /** + * OpenAPI 3.1. + * + * @return the OpenAPI 3.1 dialect + */ + public static Dialect getOpenApi31() { + return OpenApi31.getInstance(); + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Draft201909.java b/src/main/java/com/networknt/schema/dialect/Draft201909.java new file mode 100644 index 000000000..67f45119e --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Draft201909.java @@ -0,0 +1,51 @@ +package com.networknt.schema.dialect; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Draft 2019-09 dialect. + */ +public class Draft201909 { + private static final String ID = DialectId.DRAFT_2019_09; + private static final String ID_KEYWORD = "$id"; + private static final Map VOCABULARY; + + static { + Map vocabulary = new HashMap<>(); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/core", true); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/applicator", true); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/validation", true); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/meta-data", true); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/format", false); + vocabulary.put("https://json-schema.org/draft/2019-09/vocab/content", true); + VOCABULARY = vocabulary; + } + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_2019_09) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_2019_09)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Collections.singletonList( + new NonValidationKeyword("definitions") + )) + .vocabularies(VOCABULARY) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Draft202012.java b/src/main/java/com/networknt/schema/dialect/Draft202012.java new file mode 100644 index 000000000..e7f9cbcff --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Draft202012.java @@ -0,0 +1,52 @@ +package com.networknt.schema.dialect; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Draft 2020-12 dialect. + */ +public class Draft202012 { + private static final String ID = DialectId.DRAFT_2020_12; + private static final String ID_KEYWORD = "$id"; + private static final Map VOCABULARY; + + static { + Map vocabulary = new HashMap<>(); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/core", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/applicator", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/unevaluated", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/validation", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/meta-data", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/format-annotation", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/content", true); + VOCABULARY = vocabulary; + } + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_2020_12) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_2020_12)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Collections.singletonList( + new NonValidationKeyword("definitions") + )) + .vocabularies(VOCABULARY) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Draft4.java b/src/main/java/com/networknt/schema/dialect/Draft4.java new file mode 100644 index 000000000..5a55098f1 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Draft4.java @@ -0,0 +1,46 @@ +package com.networknt.schema.dialect; + +import java.util.Arrays; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Draft 4 dialect. + */ +public class Draft4 { + private static final String ID = DialectId.DRAFT_4; + private static final String ID_KEYWORD = "id"; + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_4) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_4)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Arrays.asList( + new NonValidationKeyword("$schema"), + new NonValidationKeyword("id"), + new AnnotationKeyword("title"), + new AnnotationKeyword("description"), + new AnnotationKeyword("default"), + new NonValidationKeyword("definitions"), + new NonValidationKeyword("additionalItems"), + new AnnotationKeyword("exampleSetFlag"), + new NonValidationKeyword("exclusiveMinimum"), // exclusiveMinimum boolean handled by minimum validator + new NonValidationKeyword("exclusiveMaximum") // exclusiveMaximum boolean handled by maximum validator + )) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Draft6.java b/src/main/java/com/networknt/schema/dialect/Draft6.java new file mode 100644 index 000000000..0c6463139 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Draft6.java @@ -0,0 +1,45 @@ +package com.networknt.schema.dialect; + +import java.util.Arrays; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Draft 6 dialect. + */ +public class Draft6 { + private static final String ID = DialectId.DRAFT_6; + // Draft 6 uses "$id" + private static final String ID_KEYWORD = "$id"; + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_6) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_6)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Arrays.asList( + new NonValidationKeyword("$schema"), + new NonValidationKeyword("$id"), + new AnnotationKeyword("title"), + new AnnotationKeyword("description"), + new AnnotationKeyword("default"), + new NonValidationKeyword("additionalItems"), + new NonValidationKeyword("definitions"), + new AnnotationKeyword("examples") + )) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/Draft7.java b/src/main/java/com/networknt/schema/dialect/Draft7.java new file mode 100644 index 000000000..edbdc3ab5 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/Draft7.java @@ -0,0 +1,46 @@ +package com.networknt.schema.dialect; + +import java.util.Arrays; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Draft 7 dialect. + */ +public class Draft7 { + private static final String ID = DialectId.DRAFT_7; + private static final String ID_KEYWORD = "$id"; + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_7) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_7)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Arrays.asList( + new NonValidationKeyword("$schema"), + new NonValidationKeyword("$id"), + new AnnotationKeyword("title"), + new AnnotationKeyword("description"), + new AnnotationKeyword("default"), + new NonValidationKeyword("definitions"), + new NonValidationKeyword("$comment"), + new AnnotationKeyword("examples"), + new NonValidationKeyword("then"), + new NonValidationKeyword("else"), + new NonValidationKeyword("additionalItems"))) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/OpenApi30.java b/src/main/java/com/networknt/schema/dialect/OpenApi30.java new file mode 100644 index 000000000..62ef933e2 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/OpenApi30.java @@ -0,0 +1,71 @@ +package com.networknt.schema.dialect; + +import java.util.Arrays; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * OpenAPI 3.0. + */ +public class OpenApi30 { + private static final String ID = DialectId.OPENAPI_3_0; + private static final String ID_KEYWORD = "id"; + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_4) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(Arrays.asList( + new AnnotationKeyword("title"), + KeywordType.PATTERN, + KeywordType.REQUIRED, + KeywordType.ENUM, + KeywordType.MINIMUM, + KeywordType.MAXIMUM, + KeywordType.MULTIPLE_OF, + KeywordType.MIN_LENGTH, + KeywordType.MAX_LENGTH, + KeywordType.MIN_ITEMS, + KeywordType.MAX_ITEMS, + KeywordType.UNIQUE_ITEMS, + KeywordType.MIN_PROPERTIES, + KeywordType.MAX_PROPERTIES, + + KeywordType.TYPE, + KeywordType.FORMAT, + new AnnotationKeyword("description"), + KeywordType.ITEMS_LEGACY, + KeywordType.PROPERTIES, + KeywordType.ADDITIONAL_PROPERTIES, + new AnnotationKeyword("default"), + KeywordType.ALL_OF, + KeywordType.ONE_OF, + KeywordType.ANY_OF, + KeywordType.NOT, + + new AnnotationKeyword("deprecated"), + KeywordType.DISCRIMINATOR, + new AnnotationKeyword("example"), + new AnnotationKeyword("externalDocs"), + new NonValidationKeyword("nullable"), + KeywordType.READ_ONLY, + KeywordType.WRITE_ONLY, + new AnnotationKeyword("xml"), + + KeywordType.REF + )) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/dialect/OpenApi31.java b/src/main/java/com/networknt/schema/dialect/OpenApi31.java new file mode 100644 index 000000000..a51934cb6 --- /dev/null +++ b/src/main/java/com/networknt/schema/dialect/OpenApi31.java @@ -0,0 +1,53 @@ +package com.networknt.schema.dialect; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.format.Formats; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * OpenAPI 3.1. + */ +public class OpenApi31 { + private static final String ID = DialectId.OPENAPI_3_1; + private static final String ID_KEYWORD = "$id"; + private static final Map VOCABULARY; + + static { + Map vocabulary = new HashMap<>(); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/core", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/applicator", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/unevaluated", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/validation", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/meta-data", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/format-annotation", true); + vocabulary.put("https://json-schema.org/draft/2020-12/vocab/content", true); + vocabulary.put("https://spec.openapis.org/oas/3.1/vocab/base", false); + VOCABULARY = vocabulary; + } + + private static class Holder { + private static final Dialect INSTANCE; + static { + INSTANCE = Dialect.builder(ID) + .specificationVersion(SpecificationVersion.DRAFT_2020_12) + .idKeyword(ID_KEYWORD) + .formats(Formats.DEFAULT) + .keywords(KeywordType.getKeywords(SpecificationVersion.DRAFT_2020_12)) + // keywords that may validly exist, but have no validation aspect to them + .keywords(Collections.singletonList( + new NonValidationKeyword("definitions") + )) + .vocabularies(VOCABULARY) + .build(); + } + } + + public static Dialect getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java b/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java new file mode 100644 index 000000000..a97a053ab --- /dev/null +++ b/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java @@ -0,0 +1,42 @@ +package com.networknt.schema.format; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Pattern; + +import com.networknt.schema.ExecutionContext; + +/** + * {@link Format} for RFC 3986 Uniform Resource Identifier (URI): Generic Syntax. + */ +public abstract class AbstractRFC3986Format implements Format { + private static final Pattern VALID = Pattern.compile("([A-Za-z0-9+-\\.]*:)?//|[A-Za-z0-9+-\\.]+:"); + + @Override + public final boolean matches(ExecutionContext executionContext, String value) { + try { + URI uri = new URI(value); + return validate(uri); + } catch (URISyntaxException e) { + return handleException(e); + } + } + + /** + * Determines if the uri matches the format. + * + * @param uri the uri to match + * @return true if matches + */ + protected abstract boolean validate(URI uri); + + /** + * Determines if the uri matches the format. + * + * @param e the URISyntaxException + * @return false if it does not match + */ + protected boolean handleException(URISyntaxException e) { + return VALID.matcher(e.getInput()).matches(); + } +} diff --git a/src/main/java/com/networknt/schema/format/BaseFormatValidator.java b/src/main/java/com/networknt/schema/format/BaseFormatValidator.java new file mode 100644 index 000000000..b22e17b2b --- /dev/null +++ b/src/main/java/com/networknt/schema/format/BaseFormatValidator.java @@ -0,0 +1,55 @@ +package com.networknt.schema.format; + +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.keyword.BaseKeywordValidator; +import com.networknt.schema.keyword.Keyword; + +public abstract class BaseFormatValidator extends BaseKeywordValidator { + protected final boolean assertionsEnabled; + + public BaseFormatValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, Keyword keyword, + SchemaContext schemaContext) { + super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext); + SpecificationVersion dialect = this.schemaContext.getDialect().getSpecificationVersion(); + if (dialect == null || dialect.getOrder() < SpecificationVersion.DRAFT_2019_09.getOrder()) { + assertionsEnabled = true; + } else { + // Check vocabulary + assertionsEnabled = isFormatAssertionVocabularyEnabled(dialect, + this.schemaContext.getDialect().getVocabularies()); + } + } + + protected boolean isFormatAssertionVocabularyEnabled() { + return isFormatAssertionVocabularyEnabled(this.schemaContext.getDialect().getSpecificationVersion(), + this.schemaContext.getDialect().getVocabularies()); + } + + protected boolean isFormatAssertionVocabularyEnabled(SpecificationVersion specification, Map vocabularies) { + if (SpecificationVersion.DRAFT_2020_12.equals(specification)) { + String vocabulary = "https://json-schema.org/draft/2020-12/vocab/format-assertion"; + return vocabularies.containsKey(vocabulary); // doesn't matter if it is true or false + } else if (SpecificationVersion.DRAFT_2019_09.equals(specification)) { + String vocabulary = "https://json-schema.org/draft/2019-09/vocab/format"; + return vocabularies.getOrDefault(vocabulary, false); + } + return false; + } + + protected boolean isAssertionsEnabled(ExecutionContext executionContext) { + if (Boolean.TRUE.equals(executionContext.getExecutionConfig().getFormatAssertionsEnabled())) { + return true; + } else if (Boolean.FALSE.equals(executionContext.getExecutionConfig().getFormatAssertionsEnabled())) { + return false; + } + return this.assertionsEnabled; + } +} diff --git a/src/main/java/com/networknt/schema/format/DateFormat.java b/src/main/java/com/networknt/schema/format/DateFormat.java new file mode 100644 index 000000000..3e30e499b --- /dev/null +++ b/src/main/java/com/networknt/schema/format/DateFormat.java @@ -0,0 +1,32 @@ +package com.networknt.schema.format; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +import com.networknt.schema.ExecutionContext; + +/** + * Format for date. + */ +public class DateFormat implements Format { + @Override + public boolean matches(ExecutionContext executionContext, String value) { + try { + LocalDate date = LocalDate.parse(value); + int year = date.getYear(); + return 0 <= year && year <= 9999; + } catch (DateTimeParseException e) { + return false; + } + } + + @Override + public String getName() { + return "date"; + } + + @Override + public String getMessageKey() { + return "format.date"; + } +} diff --git a/src/main/java/com/networknt/schema/format/DateTimeFormat.java b/src/main/java/com/networknt/schema/format/DateTimeFormat.java new file mode 100644 index 000000000..a12faa665 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/DateTimeFormat.java @@ -0,0 +1,81 @@ +package com.networknt.schema.format; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeParseException; +import java.util.function.Predicate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ethlo.time.ITU; +import com.ethlo.time.LeapSecondException; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.utils.Classes; + +/** + * Format for date-time. + */ +public class DateTimeFormat implements Format { + private static final Logger logger = LoggerFactory.getLogger(DateTimeFormat.class); + + private static final boolean ETHLO_PRESENT = Classes.isPresent("com.ethlo.time.ITU", DateTimeFormat.class.getClassLoader()); + + /** + * Uses etho. + *

+ * This needs to be in a holder class otherwise a ClassNotFoundException will be + * thrown when the DateTimeFormat is instantiated. + */ + public static class Ethlo { + public static boolean isValid(String value) { + try { + ITU.parseDateTime(value); + } catch (LeapSecondException ex) { + if (!ex.isVerifiedValidLeapYearMonth()) { + return false; + } + } + return true; + } + } + + /** + * Uses java time. + */ + public static class JavaTimeOffsetDateTime { + public static boolean isValid(String value) { + try { + OffsetDateTime.parse(value); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + } + + private static final Predicate VALIDATE = ETHLO_PRESENT ? Ethlo::isValid : JavaTimeOffsetDateTime::isValid; + + @Override + public String getName() { + return "date-time"; + } + + @Override + public String getMessageKey() { + return "format.date-time"; + } + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return isValid(value); + } + + private static boolean isValid(String value) { + try { + return VALIDATE.test(value); + } catch (Exception ex) { + logger.debug("Invalid {}: {}", "date-time", ex.getMessage()); + return false; + } + } +} diff --git a/src/main/java/com/networknt/schema/format/DomainValidator.java b/src/main/java/com/networknt/schema/format/DomainValidator.java deleted file mode 100644 index 2a25f8ceb..000000000 --- a/src/main/java/com/networknt/schema/format/DomainValidator.java +++ /dev/null @@ -1,2123 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema.format; - -import java.io.Serializable; -import java.net.IDN; -import java.util.Arrays; -import java.util.Locale; - -/** - *

Domain name validation routines.

- * - *

- * This validator provides methods for validating Internet domain names - * and top-level domains. - *

- * - *

Domain names are evaluated according - * to the standards RFC1034, - * section 3, and RFC1123, - * section 2.1. No accommodation is provided for the specialized needs of - * other applications; if the domain name has been URL-encoded, for example, - * validation will fail even though the equivalent plaintext version of the - * same name would have passed. - *

- * - *

- * Validation is also provided for top-level domains (TLDs) as defined and - * maintained by the Internet Assigned Numbers Authority (IANA): - *

- * - *
    - *
  • {@link #isValidInfrastructureTld} - validates infrastructure TLDs - * (.arpa, etc.)
  • - *
  • {@link #isValidGenericTld} - validates generic TLDs - * (.com, .org, etc.)
  • - *
  • {@link #isValidCountryCodeTld} - validates country code TLDs - * (.us, .uk, .cn, etc.)
  • - *
- * - *

- * (NOTE: This class does not provide IP address lookup for domain names or - * methods to ensure that a given domain name matches a specific IP; see - * {@link java.net.InetAddress} for that functionality.) - *

- * - * @version $Revision$ - * @since Validator 1.4 - */ -public class DomainValidator implements Serializable { - - private static final int MAX_DOMAIN_LENGTH = 253; - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private static final long serialVersionUID = -4407125112880174009L; - - // Regular expression strings for hostnames (derived from RFC2396 and RFC 1123) - - // RFC2396: domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum - // Max 63 characters - private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; - - // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum - // Max 63 characters - private static final String TOP_LABEL_REGEX = "\\p{Alpha}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; - - // RFC2396 hostname = *( domainlabel "." ) toplabel [ "." ] - // Note that the regex currently requires both a domain label and a top level label, whereas - // the RFC does not. This is because the regex is used to detect if a TLD is present. - // If the match fails, input is checked against DOMAIN_LABEL_REGEX (hostnameRegex) - // RFC1123 sec 2.1 allows hostnames to start with a digit - private static final String DOMAIN_NAME_REGEX = - "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$"; - - private final boolean allowLocal; - - /** - * Singleton instance of this validator, which - * doesn't consider local addresses as valid. - */ - private static final DomainValidator DOMAIN_VALIDATOR = new DomainValidator(false); - - /** - * Singleton instance of this validator, which does - * consider local addresses valid. - */ - private static final DomainValidator DOMAIN_VALIDATOR_WITH_LOCAL = new DomainValidator(true); - - /** - * RegexValidator for matching domains. - */ - private final RegexValidator domainRegex = - new RegexValidator(DOMAIN_NAME_REGEX); - /** - * RegexValidator for matching a local hostname - */ - // RFC1123 sec 2.1 allows hostnames to start with a digit - private final RegexValidator hostnameRegex = - new RegexValidator(DOMAIN_LABEL_REGEX); - - /** - * Returns the singleton instance of this validator. It - * will not consider local addresses as valid. - * - * @return the singleton instance of this validator - */ - public static synchronized DomainValidator getInstance() { - inUse = true; - return DOMAIN_VALIDATOR; - } - - /** - * Returns the singleton instance of this validator, - * with local validation as required. - * - * @param allowLocal Should local addresses be considered valid? - * @return the singleton instance of this validator - */ - public static synchronized DomainValidator getInstance(boolean allowLocal) { - inUse = true; - if (allowLocal) { - return DOMAIN_VALIDATOR_WITH_LOCAL; - } - return DOMAIN_VALIDATOR; - } - - /** - * Private constructor. - */ - private DomainValidator(boolean allowLocal) { - this.allowLocal = allowLocal; - } - - /** - * Returns true if the specified String parses - * as a valid domain name with a recognized top-level domain. - * The parsing is case-insensitive. - * - * @param domain the parameter to check for domain name syntax - * @return true if the parameter is a valid domain name - */ - public boolean isValid(String domain) { - if (domain == null) { - return false; - } - domain = unicodeToASCII(domain); - // hosts must be equally reachable via punycode and Unicode; - // Unicode is never shorter than punycode, so check punycode - // if domain did not convert, then it will be caught by ASCII - // checks in the regexes below - if (domain.length() > MAX_DOMAIN_LENGTH) { - return false; - } - String[] groups = domainRegex.match(domain); - if (groups != null && groups.length > 0) { - return isValidTld(groups[0]); - } - return allowLocal && hostnameRegex.isValid(domain); - } - - // package protected for unit test access - // must agree with isValid() above - final boolean isValidDomainSyntax(String domain) { - if (domain == null) { - return false; - } - domain = unicodeToASCII(domain); - // hosts must be equally reachable via punycode and Unicode; - // Unicode is never shorter than punycode, so check punycode - // if domain did not convert, then it will be caught by ASCII - // checks in the regexes below - if (domain.length() > MAX_DOMAIN_LENGTH) { - return false; - } - String[] groups = domainRegex.match(domain); - return (groups != null && groups.length > 0) - || hostnameRegex.isValid(domain); - } - - /** - * Returns true if the specified String matches any - * IANA-defined top-level domain. Leading dots are ignored if present. - * The search is case-insensitive. - * - * @param tld the parameter to check for TLD status, not null - * @return true if the parameter is a TLD - */ - public boolean isValidTld(String tld) { - tld = unicodeToASCII(tld); - if (allowLocal && isValidLocalTld(tld)) { - return true; - } - return isValidInfrastructureTld(tld) - || isValidGenericTld(tld) - || isValidCountryCodeTld(tld); - } - - /** - * Returns true if the specified String matches any - * IANA-defined infrastructure top-level domain. Leading dots are - * ignored if present. The search is case-insensitive. - * - * @param iTld the parameter to check for infrastructure TLD status, not null - * @return true if the parameter is an infrastructure TLD - */ - public boolean isValidInfrastructureTld(String iTld) { - final String key = chompLeadingDot(unicodeToASCII(iTld).toLowerCase(Locale.ENGLISH)); - return arrayContains(INFRASTRUCTURE_TLDS, key); - } - - /** - * Returns true if the specified String matches any - * IANA-defined generic top-level domain. Leading dots are ignored - * if present. The search is case-insensitive. - * - * @param gTld the parameter to check for generic TLD status, not null - * @return true if the parameter is a generic TLD - */ - public boolean isValidGenericTld(String gTld) { - final String key = chompLeadingDot(unicodeToASCII(gTld).toLowerCase(Locale.ENGLISH)); - return (arrayContains(GENERIC_TLDS, key) || arrayContains(genericTLDsPlus, key)) - && !arrayContains(genericTLDsMinus, key); - } - - /** - * Returns true if the specified String matches any - * IANA-defined country code top-level domain. Leading dots are - * ignored if present. The search is case-insensitive. - * - * @param ccTld the parameter to check for country code TLD status, not null - * @return true if the parameter is a country code TLD - */ - public boolean isValidCountryCodeTld(String ccTld) { - final String key = chompLeadingDot(unicodeToASCII(ccTld).toLowerCase(Locale.ENGLISH)); - return (arrayContains(COUNTRY_CODE_TLDS, key) || arrayContains(countryCodeTLDsPlus, key)) - && !arrayContains(countryCodeTLDsMinus, key); - } - - /** - * Returns true if the specified String matches any - * widely used "local" domains (localhost or localdomain). Leading dots are - * ignored if present. The search is case-insensitive. - * - * @param lTld the parameter to check for local TLD status, not null - * @return true if the parameter is an local TLD - */ - public boolean isValidLocalTld(String lTld) { - final String key = chompLeadingDot(unicodeToASCII(lTld).toLowerCase(Locale.ENGLISH)); - return arrayContains(LOCAL_TLDS, key); - } - - private String chompLeadingDot(String str) { - if (str.startsWith(".")) { - return str.substring(1); - } - return str; - } - - // --------------------------------------------- - // ----- TLDs defined by IANA - // ----- Authoritative and comprehensive list at: - // ----- http://data.iana.org/TLD/tlds-alpha-by-domain.txt - - // Note that the above list is in UPPER case. - // The code currently converts strings to lower case (as per the tables below) - - // IANA also provide an HTML list at http://www.iana.org/domains/root/db - // Note that this contains several country code entries which are NOT in - // the text file. These all have the "Not assigned" in the "Sponsoring Organisation" column - // For example (as of 2015-01-02): - // .bl country-code Not assigned - // .um country-code Not assigned - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] INFRASTRUCTURE_TLDS = new String[]{ - "arpa", // internet infrastructure - }; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] GENERIC_TLDS = new String[]{ - // Taken from Version 2018092800, Last Updated Fri Sep 28 07:07:02 2018 UTC - "aaa", // aaa American Automobile Association, Inc. - "aarp", // aarp AARP - "abarth", // abarth Fiat Chrysler Automobiles N.V. - "abb", // abb ABB Ltd - "abbott", // abbott Abbott Laboratories, Inc. - "abbvie", // abbvie AbbVie Inc. - "abc", // abc Disney Enterprises, Inc. - "able", // able Able Inc. - "abogado", // abogado Top Level Domain Holdings Limited - "abudhabi", // abudhabi Abu Dhabi Systems and Information Centre - "academy", // academy Half Oaks, LLC - "accenture", // accenture Accenture plc - "accountant", // accountant dot Accountant Limited - "accountants", // accountants Knob Town, LLC - "aco", // aco ACO Severin Ahlmann GmbH & Co. KG - "active", // active The Active Network, Inc - "actor", // actor United TLD Holdco Ltd. - "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC) - "ads", // ads Charleston Road Registry Inc. - "adult", // adult ICM Registry AD LLC - "aeg", // aeg Aktiebolaget Electrolux - "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC USA) - "aetna", // aetna Aetna Life Insurance Company - "afamilycompany", // afamilycompany Johnson Shareholdings, Inc. - "afl", // afl Australian Football League - "africa", // africa ZA Central Registry NPC trading as Registry.Africa - "agakhan", // agakhan Fondation Aga Khan (Aga Khan Foundation) - "agency", // agency Steel Falls, LLC - "aig", // aig American International Group, Inc. - "aigo", // aigo aigo Digital Technology Co,Ltd. - "airbus", // airbus Airbus S.A.S. - "airforce", // airforce United TLD Holdco Ltd. - "airtel", // airtel Bharti Airtel Limited - "akdn", // akdn Fondation Aga Khan (Aga Khan Foundation) - "alfaromeo", // alfaromeo Fiat Chrysler Automobiles N.V. - "alibaba", // alibaba Alibaba Group Holding Limited - "alipay", // alipay Alibaba Group Holding Limited - "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft - "allstate", // allstate Allstate Fire and Casualty Insurance Company - "ally", // ally Ally Financial Inc. - "alsace", // alsace REGION D ALSACE - "alstom", // alstom ALSTOM - "americanexpress", // americanexpress American Express Travel Related Services Company, Inc. - "americanfamily", // americanfamily AmFam, Inc. - "amex", // amex American Express Travel Related Services Company, Inc. - "amfam", // amfam AmFam, Inc. - "amica", // amica Amica Mutual Insurance Company - "amsterdam", // amsterdam Gemeente Amsterdam - "analytics", // analytics Campus IP LLC - "android", // android Charleston Road Registry Inc. - "anquan", // anquan QIHOO 360 TECHNOLOGY CO. LTD. - "anz", // anz Australia and New Zealand Banking Group Limited - "aol", // aol AOL Inc. - "apartments", // apartments June Maple, LLC - "app", // app Charleston Road Registry Inc. - "apple", // apple Apple Inc. - "aquarelle", // aquarelle Aquarelle.com - "arab", // arab League of Arab States - "aramco", // aramco Aramco Services Company - "archi", // archi STARTING DOT LIMITED - "army", // army United TLD Holdco Ltd. - "art", // art UK Creative Ideas Limited - "arte", // arte Association Relative à la Télévision Européenne G.E.I.E. - "asda", // asda Wal-Mart Stores, Inc. - "asia", // asia DotAsia Organisation Ltd. - "associates", // associates Baxter Hill, LLC - "athleta", // athleta The Gap, Inc. - "attorney", // attorney United TLD Holdco, Ltd - "auction", // auction United TLD HoldCo, Ltd. - "audi", // audi AUDI Aktiengesellschaft - "audible", // audible Amazon Registry Services, Inc. - "audio", // audio Uniregistry, Corp. - "auspost", // auspost Australian Postal Corporation - "author", // author Amazon Registry Services, Inc. - "auto", // auto Uniregistry, Corp. - "autos", // autos DERAutos, LLC - "avianca", // avianca Aerovias del Continente Americano S.A. Avianca - "aws", // aws Amazon Registry Services, Inc. - "axa", // axa AXA SA - "azure", // azure Microsoft Corporation - "baby", // baby Johnson & Johnson Services, Inc. - "baidu", // baidu Baidu, Inc. - "banamex", // banamex Citigroup Inc. - "bananarepublic", // bananarepublic The Gap, Inc. - "band", // band United TLD Holdco, Ltd - "bank", // bank fTLD Registry Services, LLC - "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable - "barcelona", // barcelona Municipi de Barcelona - "barclaycard", // barclaycard Barclays Bank PLC - "barclays", // barclays Barclays Bank PLC - "barefoot", // barefoot Gallo Vineyards, Inc. - "bargains", // bargains Half Hallow, LLC - "baseball", // baseball MLB Advanced Media DH, LLC - "basketball", // basketball Fédération Internationale de Basketball (FIBA) - "bauhaus", // bauhaus Werkhaus GmbH - "bayern", // bayern Bayern Connect GmbH - "bbc", // bbc British Broadcasting Corporation - "bbt", // bbt BB&T Corporation - "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A. - "bcg", // bcg The Boston Consulting Group, Inc. - "bcn", // bcn Municipi de Barcelona - "beats", // beats Beats Electronics, LLC - "beauty", // beauty L'Oréal - "beer", // beer Top Level Domain Holdings Limited - "bentley", // bentley Bentley Motors Limited - "berlin", // berlin dotBERLIN GmbH & Co. KG - "best", // best BestTLD Pty Ltd - "bestbuy", // bestbuy BBY Solutions, Inc. - "bet", // bet Afilias plc - "bharti", // bharti Bharti Enterprises (Holding) Private Limited - "bible", // bible American Bible Society - "bid", // bid dot Bid Limited - "bike", // bike Grand Hollow, LLC - "bing", // bing Microsoft Corporation - "bingo", // bingo Sand Cedar, LLC - "bio", // bio STARTING DOT LIMITED - "biz", // biz Neustar, Inc. - "black", // black Afilias Limited - "blackfriday", // blackfriday Uniregistry, Corp. - "blanco", // blanco BLANCO GmbH + Co KG - "blockbuster", // blockbuster Dish DBS Corporation - "blog", // blog Knock Knock WHOIS There, LLC - "bloomberg", // bloomberg Bloomberg IP Holdings LLC - "blue", // blue Afilias Limited - "bms", // bms Bristol-Myers Squibb Company - "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft - "bnl", // bnl Banca Nazionale del Lavoro - "bnpparibas", // bnpparibas BNP Paribas - "boats", // boats DERBoats, LLC - "boehringer", // boehringer Boehringer Ingelheim International GmbH - "bofa", // bofa NMS Services, Inc. - "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br - "bond", // bond Bond University Limited - "boo", // boo Charleston Road Registry Inc. - "book", // book Amazon Registry Services, Inc. - "booking", // booking Booking.com B.V. -// "boots", // boots THE BOOTS COMPANY PLC - "bosch", // bosch Robert Bosch GMBH - "bostik", // bostik Bostik SA - "boston", // boston Boston TLD Management, LLC - "bot", // bot Amazon Registry Services, Inc. - "boutique", // boutique Over Galley, LLC - "box", // box NS1 Limited - "bradesco", // bradesco Banco Bradesco S.A. - "bridgestone", // bridgestone Bridgestone Corporation - "broadway", // broadway Celebrate Broadway, Inc. - "broker", // broker DOTBROKER REGISTRY LTD - "brother", // brother Brother Industries, Ltd. - "brussels", // brussels DNS.be vzw - "budapest", // budapest Top Level Domain Holdings Limited - "bugatti", // bugatti Bugatti International SA - "build", // build Plan Bee LLC - "builders", // builders Atomic Madison, LLC - "business", // business Spring Cross, LLC - "buy", // buy Amazon Registry Services, INC - "buzz", // buzz DOTSTRATEGY CO. - "bzh", // bzh Association www.bzh - "cab", // cab Half Sunset, LLC - "cafe", // cafe Pioneer Canyon, LLC - "cal", // cal Charleston Road Registry Inc. - "call", // call Amazon Registry Services, Inc. - "calvinklein", // calvinklein PVH gTLD Holdings LLC - "cam", // cam AC Webconnecting Holding B.V. - "camera", // camera Atomic Maple, LLC - "camp", // camp Delta Dynamite, LLC - "cancerresearch", // cancerresearch Australian Cancer Research Foundation - "canon", // canon Canon Inc. - "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry - "capital", // capital Delta Mill, LLC - "capitalone", // capitalone Capital One Financial Corporation - "car", // car Cars Registry Limited - "caravan", // caravan Caravan International, Inc. - "cards", // cards Foggy Hollow, LLC - "care", // care Goose Cross, LLC - "career", // career dotCareer LLC - "careers", // careers Wild Corner, LLC - "cars", // cars Uniregistry, Corp. - "cartier", // cartier Richemont DNS Inc. - "casa", // casa Top Level Domain Holdings Limited - "case", // case CNH Industrial N.V. - "caseih", // caseih CNH Industrial N.V. - "cash", // cash Delta Lake, LLC - "casino", // casino Binky Sky, LLC - "cat", // cat Fundacio puntCAT - "catering", // catering New Falls. LLC - "catholic", // catholic Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) - "cba", // cba COMMONWEALTH BANK OF AUSTRALIA - "cbn", // cbn The Christian Broadcasting Network, Inc. - "cbre", // cbre CBRE, Inc. - "cbs", // cbs CBS Domains Inc. - "ceb", // ceb The Corporate Executive Board Company - "center", // center Tin Mill, LLC - "ceo", // ceo CEOTLD Pty Ltd - "cern", // cern European Organization for Nuclear Research ("CERN") - "cfa", // cfa CFA Institute - "cfd", // cfd DOTCFD REGISTRY LTD - "chanel", // chanel Chanel International B.V. - "channel", // channel Charleston Road Registry Inc. - "charity", // charity Corn Lake, LLC - "chase", // chase JPMorgan Chase & Co. - "chat", // chat Sand Fields, LLC - "cheap", // cheap Sand Cover, LLC - "chintai", // chintai CHINTAI Corporation -// "chloe", // chloe Richemont DNS Inc. (Not assigned) - "christmas", // christmas Uniregistry, Corp. - "chrome", // chrome Charleston Road Registry Inc. - "chrysler", // chrysler FCA US LLC. - "church", // church Holly Fileds, LLC - "cipriani", // cipriani Hotel Cipriani Srl - "circle", // circle Amazon Registry Services, Inc. - "cisco", // cisco Cisco Technology, Inc. - "citadel", // citadel Citadel Domain LLC - "citi", // citi Citigroup Inc. - "citic", // citic CITIC Group Corporation - "city", // city Snow Sky, LLC - "cityeats", // cityeats Lifestyle Domain Holdings, Inc. - "claims", // claims Black Corner, LLC - "cleaning", // cleaning Fox Shadow, LLC - "click", // click Uniregistry, Corp. - "clinic", // clinic Goose Park, LLC - "clinique", // clinique The Estée Lauder Companies Inc. - "clothing", // clothing Steel Lake, LLC - "cloud", // cloud ARUBA S.p.A. - "club", // club .CLUB DOMAINS, LLC - "clubmed", // clubmed Club Méditerranée S.A. - "coach", // coach Koko Island, LLC - "codes", // codes Puff Willow, LLC - "coffee", // coffee Trixy Cover, LLC - "college", // college XYZ.COM LLC - "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH - "com", // com VeriSign Global Registry Services - "comcast", // comcast Comcast IP Holdings I, LLC - "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA - "community", // community Fox Orchard, LLC - "company", // company Silver Avenue, LLC - "compare", // compare iSelect Ltd - "computer", // computer Pine Mill, LLC - "comsec", // comsec VeriSign, Inc. - "condos", // condos Pine House, LLC - "construction", // construction Fox Dynamite, LLC - "consulting", // consulting United TLD Holdco, LTD. - "contact", // contact Top Level Spectrum, Inc. - "contractors", // contractors Magic Woods, LLC - "cooking", // cooking Top Level Domain Holdings Limited - "cookingchannel", // cookingchannel Lifestyle Domain Holdings, Inc. - "cool", // cool Koko Lake, LLC - "coop", // coop DotCooperation LLC - "corsica", // corsica Collectivité Territoriale de Corse - "country", // country Top Level Domain Holdings Limited - "coupon", // coupon Amazon Registry Services, Inc. - "coupons", // coupons Black Island, LLC - "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD - "credit", // credit Snow Shadow, LLC - "creditcard", // creditcard Binky Frostbite, LLC - "creditunion", // creditunion CUNA Performance Resources, LLC - "cricket", // cricket dot Cricket Limited - "crown", // crown Crown Equipment Corporation - "crs", // crs Federated Co-operatives Limited - "cruise", // cruise Viking River Cruises (Bermuda) Ltd. - "cruises", // cruises Spring Way, LLC - "csc", // csc Alliance-One Services, Inc. - "cuisinella", // cuisinella SALM S.A.S. - "cymru", // cymru Nominet UK - "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd. - "dabur", // dabur Dabur India Limited - "dad", // dad Charleston Road Registry Inc. - "dance", // dance United TLD Holdco Ltd. - "data", // data Dish DBS Corporation - "date", // date dot Date Limited - "dating", // dating Pine Fest, LLC - "datsun", // datsun NISSAN MOTOR CO., LTD. - "day", // day Charleston Road Registry Inc. - "dclk", // dclk Charleston Road Registry Inc. - "dds", // dds Minds + Machines Group Limited - "deal", // deal Amazon Registry Services, Inc. - "dealer", // dealer Dealer Dot Com, Inc. - "deals", // deals Sand Sunset, LLC - "degree", // degree United TLD Holdco, Ltd - "delivery", // delivery Steel Station, LLC - "dell", // dell Dell Inc. - "deloitte", // deloitte Deloitte Touche Tohmatsu - "delta", // delta Delta Air Lines, Inc. - "democrat", // democrat United TLD Holdco Ltd. - "dental", // dental Tin Birch, LLC - "dentist", // dentist United TLD Holdco, Ltd - "desi", // desi Desi Networks LLC - "design", // design Top Level Design, LLC - "dev", // dev Charleston Road Registry Inc. - "dhl", // dhl Deutsche Post AG - "diamonds", // diamonds John Edge, LLC - "diet", // diet Uniregistry, Corp. - "digital", // digital Dash Park, LLC - "direct", // direct Half Trail, LLC - "directory", // directory Extra Madison, LLC - "discount", // discount Holly Hill, LLC - "discover", // discover Discover Financial Services - "dish", // dish Dish DBS Corporation - "diy", // diy Lifestyle Domain Holdings, Inc. - "dnp", // dnp Dai Nippon Printing Co., Ltd. - "docs", // docs Charleston Road Registry Inc. - "doctor", // doctor Brice Trail, LLC - "dodge", // dodge FCA US LLC. - "dog", // dog Koko Mill, LLC - "doha", // doha Communications Regulatory Authority (CRA) - "domains", // domains Sugar Cross, LLC -// "doosan", // doosan Doosan Corporation (retired) - "dot", // dot Dish DBS Corporation - "download", // download dot Support Limited - "drive", // drive Charleston Road Registry Inc. - "dtv", // dtv Dish DBS Corporation - "dubai", // dubai Dubai Smart Government Department - "duck", // duck Johnson Shareholdings, Inc. - "dunlop", // dunlop The Goodyear Tire & Rubber Company - "duns", // duns The Dun & Bradstreet Corporation - "dupont", // dupont E. I. du Pont de Nemours and Company - "durban", // durban ZA Central Registry NPC trading as ZA Central Registry - "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG - "dvr", // dvr Hughes Satellite Systems Corporation - "earth", // earth Interlink Co., Ltd. - "eat", // eat Charleston Road Registry Inc. - "eco", // eco Big Room Inc. - "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V. - "edu", // edu EDUCAUSE - "education", // education Brice Way, LLC - "email", // email Spring Madison, LLC - "emerck", // emerck Merck KGaA - "energy", // energy Binky Birch, LLC - "engineer", // engineer United TLD Holdco Ltd. - "engineering", // engineering Romeo Canyon - "enterprises", // enterprises Snow Oaks, LLC - "epost", // epost Deutsche Post AG - "epson", // epson Seiko Epson Corporation - "equipment", // equipment Corn Station, LLC - "ericsson", // ericsson Telefonaktiebolaget L M Ericsson - "erni", // erni ERNI Group Holding AG - "esq", // esq Charleston Road Registry Inc. - "estate", // estate Trixy Park, LLC - "esurance", // esurance Esurance Insurance Company - "etisalat", // etisalat Emirates Telecommunic - "eurovision", // eurovision European Broadcasting Union (EBU) - "eus", // eus Puntueus Fundazioa - "events", // events Pioneer Maple, LLC - "everbank", // everbank EverBank - "exchange", // exchange Spring Falls, LLC - "expert", // expert Magic Pass, LLC - "exposed", // exposed Victor Beach, LLC - "express", // express Sea Sunset, LLC - "extraspace", // extraspace Extra Space Storage LLC - "fage", // fage Fage International S.A. - "fail", // fail Atomic Pipe, LLC - "fairwinds", // fairwinds FairWinds Partners, LLC - "faith", // faith dot Faith Limited - "family", // family United TLD Holdco Ltd. - "fan", // fan Asiamix Digital Ltd - "fans", // fans Asiamix Digital Limited - "farm", // farm Just Maple, LLC - "farmers", // farmers Farmers Insurance Exchange - "fashion", // fashion Top Level Domain Holdings Limited - "fast", // fast Amazon Registry Services, Inc. - "fedex", // fedex Federal Express Corporation - "feedback", // feedback Top Level Spectrum, Inc. - "ferrari", // ferrari Fiat Chrysler Automobiles N.V. - "ferrero", // ferrero Ferrero Trading Lux S.A. - "fiat", // fiat Fiat Chrysler Automobiles N.V. - "fidelity", // fidelity Fidelity Brokerage Services LLC - "fido", // fido Rogers Communications Canada Inc. - "film", // film Motion Picture Domain Registry Pty Ltd - "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br - "finance", // finance Cotton Cypress, LLC - "financial", // financial Just Cover, LLC - "fire", // fire Amazon Registry Services, Inc. - "firestone", // firestone Bridgestone Corporation - "firmdale", // firmdale Firmdale Holdings Limited - "fish", // fish Fox Woods, LLC - "fishing", // fishing Top Level Domain Holdings Limited - "fit", // fit Minds + Machines Group Limited - "fitness", // fitness Brice Orchard, LLC - "flickr", // flickr Yahoo! Domain Services Inc. - "flights", // flights Fox Station, LLC - "flir", // flir FLIR Systems, Inc. - "florist", // florist Half Cypress, LLC - "flowers", // flowers Uniregistry, Corp. -// "flsmidth", // flsmidth FLSmidth A/S retired 2016-07-22 - "fly", // fly Charleston Road Registry Inc. - "foo", // foo Charleston Road Registry Inc. - "food", // food Lifestyle Domain Holdings, Inc. - "foodnetwork", // foodnetwork Lifestyle Domain Holdings, Inc. - "football", // football Foggy Farms, LLC - "ford", // ford Ford Motor Company - "forex", // forex DOTFOREX REGISTRY LTD - "forsale", // forsale United TLD Holdco, LLC - "forum", // forum Fegistry, LLC - "foundation", // foundation John Dale, LLC - "fox", // fox FOX Registry, LLC - "free", // free Amazon Registry Services, Inc. - "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH - "frl", // frl FRLregistry B.V. - "frogans", // frogans OP3FT - "frontdoor", // frontdoor Lifestyle Domain Holdings, Inc. - "frontier", // frontier Frontier Communications Corporation - "ftr", // ftr Frontier Communications Corporation - "fujitsu", // fujitsu Fujitsu Limited - "fujixerox", // fujixerox Xerox DNHC LLC - "fun", // fun DotSpace, Inc. - "fund", // fund John Castle, LLC - "furniture", // furniture Lone Fields, LLC - "futbol", // futbol United TLD Holdco, Ltd. - "fyi", // fyi Silver Tigers, LLC - "gal", // gal Asociación puntoGAL - "gallery", // gallery Sugar House, LLC - "gallo", // gallo Gallo Vineyards, Inc. - "gallup", // gallup Gallup, Inc. - "game", // game Uniregistry, Corp. - "games", // games United TLD Holdco Ltd. - "gap", // gap The Gap, Inc. - "garden", // garden Top Level Domain Holdings Limited - "gbiz", // gbiz Charleston Road Registry Inc. - "gdn", // gdn Joint Stock Company "Navigation-information systems" - "gea", // gea GEA Group Aktiengesellschaft - "gent", // gent COMBELL GROUP NV/SA - "genting", // genting Resorts World Inc. Pte. Ltd. - "george", // george Wal-Mart Stores, Inc. - "ggee", // ggee GMO Internet, Inc. - "gift", // gift Uniregistry, Corp. - "gifts", // gifts Goose Sky, LLC - "gives", // gives United TLD Holdco Ltd. - "giving", // giving Giving Limited - "glade", // glade Johnson Shareholdings, Inc. - "glass", // glass Black Cover, LLC - "gle", // gle Charleston Road Registry Inc. - "global", // global Dot Global Domain Registry Limited - "globo", // globo Globo Comunicação e Participações S.A - "gmail", // gmail Charleston Road Registry Inc. - "gmbh", // gmbh Extra Dynamite, LLC - "gmo", // gmo GMO Internet, Inc. - "gmx", // gmx 1&1 Mail & Media GmbH - "godaddy", // godaddy Go Daddy East, LLC - "gold", // gold June Edge, LLC - "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD. - "golf", // golf Lone Falls, LLC - "goo", // goo NTT Resonant Inc. -// "goodhands", // goodhands Allstate Fire and Casualty Insurance Company - "goodyear", // goodyear The Goodyear Tire & Rubber Company - "goog", // goog Charleston Road Registry Inc. - "google", // google Charleston Road Registry Inc. - "gop", // gop Republican State Leadership Committee, Inc. - "got", // got Amazon Registry Services, Inc. - "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain Registration) - "grainger", // grainger Grainger Registry Services, LLC - "graphics", // graphics Over Madison, LLC - "gratis", // gratis Pioneer Tigers, LLC - "green", // green Afilias Limited - "gripe", // gripe Corn Sunset, LLC - "grocery", // grocery Wal-Mart Stores, Inc. - "group", // group Romeo Town, LLC - "guardian", // guardian The Guardian Life Insurance Company of America - "gucci", // gucci Guccio Gucci S.p.a. - "guge", // guge Charleston Road Registry Inc. - "guide", // guide Snow Moon, LLC - "guitars", // guitars Uniregistry, Corp. - "guru", // guru Pioneer Cypress, LLC - "hair", // hair L'Oreal - "hamburg", // hamburg Hamburg Top-Level-Domain GmbH - "hangout", // hangout Charleston Road Registry Inc. - "haus", // haus United TLD Holdco, LTD. - "hbo", // hbo HBO Registry Services, Inc. - "hdfc", // hdfc HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED - "hdfcbank", // hdfcbank HDFC Bank Limited - "health", // health DotHealth, LLC - "healthcare", // healthcare Silver Glen, LLC - "help", // help Uniregistry, Corp. - "helsinki", // helsinki City of Helsinki - "here", // here Charleston Road Registry Inc. - "hermes", // hermes Hermes International - "hgtv", // hgtv Lifestyle Domain Holdings, Inc. - "hiphop", // hiphop Uniregistry, Corp. - "hisamitsu", // hisamitsu Hisamitsu Pharmaceutical Co.,Inc. - "hitachi", // hitachi Hitachi, Ltd. - "hiv", // hiv dotHIV gemeinnuetziger e.V. - "hkt", // hkt PCCW-HKT DataCom Services Limited - "hockey", // hockey Half Willow, LLC - "holdings", // holdings John Madison, LLC - "holiday", // holiday Goose Woods, LLC - "homedepot", // homedepot Homer TLC, Inc. - "homegoods", // homegoods The TJX Companies, Inc. - "homes", // homes DERHomes, LLC - "homesense", // homesense The TJX Companies, Inc. - "honda", // honda Honda Motor Co., Ltd. - "honeywell", // honeywell Honeywell GTLD LLC - "horse", // horse Top Level Domain Holdings Limited - "hospital", // hospital Ruby Pike, LLC - "host", // host DotHost Inc. - "hosting", // hosting Uniregistry, Corp. - "hot", // hot Amazon Registry Services, Inc. - "hoteles", // hoteles Travel Reservations SRL - "hotels", // hotels Booking.com B.V. - "hotmail", // hotmail Microsoft Corporation - "house", // house Sugar Park, LLC - "how", // how Charleston Road Registry Inc. - "hsbc", // hsbc HSBC Holdings PLC -// "htc", // htc HTC corporation (Not assigned) - "hughes", // hughes Hughes Satellite Systems Corporation - "hyatt", // hyatt Hyatt GTLD, L.L.C. - "hyundai", // hyundai Hyundai Motor Company - "ibm", // ibm International Business Machines Corporation - "icbc", // icbc Industrial and Commercial Bank of China Limited - "ice", // ice IntercontinentalExchange, Inc. - "icu", // icu One.com A/S - "ieee", // ieee IEEE Global LLC - "ifm", // ifm ifm electronic gmbh -// "iinet", // iinet Connect West Pty. Ltd. (Retired) - "ikano", // ikano Ikano S.A. - "imamat", // imamat Fondation Aga Khan (Aga Khan Foundation) - "imdb", // imdb Amazon Registry Services, Inc. - "immo", // immo Auburn Bloom, LLC - "immobilien", // immobilien United TLD Holdco Ltd. - "inc", // inc Intercap Holdings Inc. - "industries", // industries Outer House, LLC - "infiniti", // infiniti NISSAN MOTOR CO., LTD. - "info", // info Afilias Limited - "ing", // ing Charleston Road Registry Inc. - "ink", // ink Top Level Design, LLC - "institute", // institute Outer Maple, LLC - "insurance", // insurance fTLD Registry Services LLC - "insure", // insure Pioneer Willow, LLC - "int", // int Internet Assigned Numbers Authority - "intel", // intel Intel Corporation - "international", // international Wild Way, LLC - "intuit", // intuit Intuit Administrative Services, Inc. - "investments", // investments Holly Glen, LLC - "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A. - "irish", // irish Dot-Irish LLC - "iselect", // iselect iSelect Ltd - "ismaili", // ismaili Fondation Aga Khan (Aga Khan Foundation) - "ist", // ist Istanbul Metropolitan Municipality - "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S. - "itau", // itau Itau Unibanco Holding S.A. - "itv", // itv ITV Services Limited - "iveco", // iveco CNH Industrial N.V. -// "iwc", // iwc Richemont DNS Inc. - "jaguar", // jaguar Jaguar Land Rover Ltd - "java", // java Oracle Corporation - "jcb", // jcb JCB Co., Ltd. - "jcp", // jcp JCP Media, Inc. - "jeep", // jeep FCA US LLC. - "jetzt", // jetzt New TLD Company AB - "jewelry", // jewelry Wild Bloom, LLC - "jio", // jio Affinity Names, Inc. -// "jlc", // jlc Richemont DNS Inc. - "jll", // jll Jones Lang LaSalle Incorporated - "jmp", // jmp Matrix IP LLC - "jnj", // jnj Johnson & Johnson Services, Inc. - "jobs", // jobs Employ Media LLC - "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry - "jot", // jot Amazon Registry Services, Inc. - "joy", // joy Amazon Registry Services, Inc. - "jpmorgan", // jpmorgan JPMorgan Chase & Co. - "jprs", // jprs Japan Registry Services Co., Ltd. - "juegos", // juegos Uniregistry, Corp. - "juniper", // juniper JUNIPER NETWORKS, INC. - "kaufen", // kaufen United TLD Holdco Ltd. - "kddi", // kddi KDDI CORPORATION - "kerryhotels", // kerryhotels Kerry Trading Co. Limited - "kerrylogistics", // kerrylogistics Kerry Trading Co. Limited - "kerryproperties", // kerryproperties Kerry Trading Co. Limited - "kfh", // kfh Kuwait Finance House - "kia", // kia KIA MOTORS CORPORATION - "kim", // kim Afilias Limited - "kinder", // kinder Ferrero Trading Lux S.A. - "kindle", // kindle Amazon Registry Services, Inc. - "kitchen", // kitchen Just Goodbye, LLC - "kiwi", // kiwi DOT KIWI LIMITED - "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH - "komatsu", // komatsu Komatsu Ltd. - "kosher", // kosher Kosher Marketing Assets LLC - "kpmg", // kpmg KPMG International Cooperative (KPMG International Genossenschaft) - "kpn", // kpn Koninklijke KPN N.V. - "krd", // krd KRG Department of Information Technology - "kred", // kred KredTLD Pty Ltd - "kuokgroup", // kuokgroup Kerry Trading Co. Limited - "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen - "lacaixa", // lacaixa CAIXA D'ESTALVIS I PENSIONS DE BARCELONA - "ladbrokes", // ladbrokes LADBROKES INTERNATIONAL PLC - "lamborghini", // lamborghini Automobili Lamborghini S.p.A. - "lamer", // lamer The Estée Lauder Companies Inc. - "lancaster", // lancaster LANCASTER - "lancia", // lancia Fiat Chrysler Automobiles N.V. - "lancome", // lancome L'Oréal - "land", // land Pine Moon, LLC - "landrover", // landrover Jaguar Land Rover Ltd - "lanxess", // lanxess LANXESS Corporation - "lasalle", // lasalle Jones Lang LaSalle Incorporated - "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el Comercio Electrónico - "latino", // latino Dish DBS Corporation - "latrobe", // latrobe La Trobe University - "law", // law Minds + Machines Group Limited - "lawyer", // lawyer United TLD Holdco, Ltd - "lds", // lds IRI Domain Management, LLC - "lease", // lease Victor Trail, LLC - "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc - "lefrak", // lefrak LeFrak Organization, Inc. - "legal", // legal Blue Falls, LLC - "lego", // lego LEGO Juris A/S - "lexus", // lexus TOYOTA MOTOR CORPORATION - "lgbt", // lgbt Afilias Limited - "liaison", // liaison Liaison Technologies, Incorporated - "lidl", // lidl Schwarz Domains und Services GmbH & Co. KG - "life", // life Trixy Oaks, LLC - "lifeinsurance", // lifeinsurance American Council of Life Insurers - "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc. - "lighting", // lighting John McCook, LLC - "like", // like Amazon Registry Services, Inc. - "lilly", // lilly Eli Lilly and Company - "limited", // limited Big Fest, LLC - "limo", // limo Hidden Frostbite, LLC - "lincoln", // lincoln Ford Motor Company - "linde", // linde Linde Aktiengesellschaft - "link", // link Uniregistry, Corp. - "lipsy", // lipsy Lipsy Ltd - "live", // live United TLD Holdco Ltd. - "living", // living Lifestyle Domain Holdings, Inc. - "lixil", // lixil LIXIL Group Corporation - "llc", // llc Afilias plc - "loan", // loan dot Loan Limited - "loans", // loans June Woods, LLC - "locker", // locker Dish DBS Corporation - "locus", // locus Locus Analytics LLC - "loft", // loft Annco, Inc. - "lol", // lol Uniregistry, Corp. - "london", // london Dot London Domains Limited - "lotte", // lotte Lotte Holdings Co., Ltd. - "lotto", // lotto Afilias Limited - "love", // love Merchant Law Group LLP - "lpl", // lpl LPL Holdings, Inc. - "lplfinancial", // lplfinancial LPL Holdings, Inc. - "ltd", // ltd Over Corner, LLC - "ltda", // ltda InterNetX Corp. - "lundbeck", // lundbeck H. Lundbeck A/S - "lupin", // lupin LUPIN LIMITED - "luxe", // luxe Top Level Domain Holdings Limited - "luxury", // luxury Luxury Partners LLC - "macys", // macys Macys, Inc. - "madrid", // madrid Comunidad de Madrid - "maif", // maif Mutuelle Assurance Instituteur France (MAIF) - "maison", // maison Victor Frostbite, LLC - "makeup", // makeup L'Oréal - "man", // man MAN SE - "management", // management John Goodbye, LLC - "mango", // mango PUNTO FA S.L. - "map", // map Charleston Road Registry Inc. - "market", // market Unitied TLD Holdco, Ltd - "marketing", // marketing Fern Pass, LLC - "markets", // markets DOTMARKETS REGISTRY LTD - "marriott", // marriott Marriott Worldwide Corporation - "marshalls", // marshalls The TJX Companies, Inc. - "maserati", // maserati Fiat Chrysler Automobiles N.V. - "mattel", // mattel Mattel Sites, Inc. - "mba", // mba Lone Hollow, LLC -// "mcd", // mcd McDonald’s Corporation (Not assigned) -// "mcdonalds", // mcdonalds McDonald’s Corporation (Not assigned) - "mckinsey", // mckinsey McKinsey Holdings, Inc. - "med", // med Medistry LLC - "media", // media Grand Glen, LLC - "meet", // meet Afilias Limited - "melbourne", // melbourne The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation - "meme", // meme Charleston Road Registry Inc. - "memorial", // memorial Dog Beach, LLC - "men", // men Exclusive Registry Limited - "menu", // menu Wedding TLD2, LLC -// "meo", // meo PT Comunicacoes S.A. - "merckmsd", // merckmsd MSD Registry Holdings, Inc. - "metlife", // metlife MetLife Services and Solutions, LLC - "miami", // miami Top Level Domain Holdings Limited - "microsoft", // microsoft Microsoft Corporation - "mil", // mil DoD Network Information Center - "mini", // mini Bayerische Motoren Werke Aktiengesellschaft - "mint", // mint Intuit Administrative Services, Inc. - "mit", // mit Massachusetts Institute of Technology - "mitsubishi", // mitsubishi Mitsubishi Corporation - "mlb", // mlb MLB Advanced Media DH, LLC - "mls", // mls The Canadian Real Estate Association - "mma", // mma MMA IARD - "mobi", // mobi Afilias Technologies Limited dba dotMobi - "mobile", // mobile Dish DBS Corporation - "mobily", // mobily GreenTech Consultancy Company W.L.L. - "moda", // moda United TLD Holdco Ltd. - "moe", // moe Interlink Co., Ltd. - "moi", // moi Amazon Registry Services, Inc. - "mom", // mom Uniregistry, Corp. - "monash", // monash Monash University - "money", // money Outer McCook, LLC - "monster", // monster Monster Worldwide, Inc. -// "montblanc", // montblanc Richemont DNS Inc. (Not assigned) - "mopar", // mopar FCA US LLC. - "mormon", // mormon IRI Domain Management, LLC ("Applicant") - "mortgage", // mortgage United TLD Holdco, Ltd - "moscow", // moscow Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) - "moto", // moto Motorola Trademark Holdings, LLC - "motorcycles", // motorcycles DERMotorcycles, LLC - "mov", // mov Charleston Road Registry Inc. - "movie", // movie New Frostbite, LLC - "movistar", // movistar Telefónica S.A. - "msd", // msd MSD Registry Holdings, Inc. - "mtn", // mtn MTN Dubai Limited -// "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation (Retired) - "mtr", // mtr MTR Corporation Limited - "museum", // museum Museum Domain Management Association - "mutual", // mutual Northwestern Mutual MU TLD Registry, LLC -// "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française (Retired) - "nab", // nab National Australia Bank Limited - "nadex", // nadex Nadex Domains, Inc - "nagoya", // nagoya GMO Registry, Inc. - "name", // name VeriSign Information Services, Inc. - "nationwide", // nationwide Nationwide Mutual Insurance Company - "natura", // natura NATURA COSMÉTICOS S.A. - "navy", // navy United TLD Holdco Ltd. - "nba", // nba NBA REGISTRY, LLC - "nec", // nec NEC Corporation - "net", // net VeriSign Global Registry Services - "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA - "netflix", // netflix Netflix, Inc. - "network", // network Trixy Manor, LLC - "neustar", // neustar NeuStar, Inc. - "new", // new Charleston Road Registry Inc. - "newholland", // newholland CNH Industrial N.V. - "news", // news United TLD Holdco Ltd. - "next", // next Next plc - "nextdirect", // nextdirect Next plc - "nexus", // nexus Charleston Road Registry Inc. - "nfl", // nfl NFL Reg Ops LLC - "ngo", // ngo Public Interest Registry - "nhk", // nhk Japan Broadcasting Corporation (NHK) - "nico", // nico DWANGO Co., Ltd. - "nike", // nike NIKE, Inc. - "nikon", // nikon NIKON CORPORATION - "ninja", // ninja United TLD Holdco Ltd. - "nissan", // nissan NISSAN MOTOR CO., LTD. - "nissay", // nissay Nippon Life Insurance Company - "nokia", // nokia Nokia Corporation - "northwesternmutual", // northwesternmutual Northwestern Mutual Registry, LLC - "norton", // norton Symantec Corporation - "now", // now Amazon Registry Services, Inc. - "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "nowtv", // nowtv Starbucks (HK) Limited - "nra", // nra NRA Holdings Company, INC. - "nrw", // nrw Minds + Machines GmbH - "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION - "nyc", // nyc The City of New York by and through the New York City Department of Information Technology & Telecommunications - "obi", // obi OBI Group Holding SE & Co. KGaA - "observer", // observer Top Level Spectrum, Inc. - "off", // off Johnson Shareholdings, Inc. - "office", // office Microsoft Corporation - "okinawa", // okinawa BusinessRalliart inc. - "olayan", // olayan Crescent Holding GmbH - "olayangroup", // olayangroup Crescent Holding GmbH - "oldnavy", // oldnavy The Gap, Inc. - "ollo", // ollo Dish DBS Corporation - "omega", // omega The Swatch Group Ltd - "one", // one One.com A/S - "ong", // ong Public Interest Registry - "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland - "online", // online DotOnline Inc. - "onyourside", // onyourside Nationwide Mutual Insurance Company - "ooo", // ooo INFIBEAM INCORPORATION LIMITED - "open", // open American Express Travel Related Services Company, Inc. - "oracle", // oracle Oracle Corporation - "orange", // orange Orange Brand Services Limited - "org", // org Public Interest Registry (PIR) - "organic", // organic Afilias Limited -// "orientexpress", // orientexpress Orient Express (retired 2017-04-11) - "origins", // origins The Estée Lauder Companies Inc. - "osaka", // osaka Interlink Co., Ltd. - "otsuka", // otsuka Otsuka Holdings Co., Ltd. - "ott", // ott Dish DBS Corporation - "ovh", // ovh OVH SAS - "page", // page Charleston Road Registry Inc. -// "pamperedchef", // pamperedchef The Pampered Chef, Ltd. (Not assigned) - "panasonic", // panasonic Panasonic Corporation -// "panerai", // panerai Richemont DNS Inc. - "paris", // paris City of Paris - "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "partners", // partners Magic Glen, LLC - "parts", // parts Sea Goodbye, LLC - "party", // party Blue Sky Registry Limited - "passagens", // passagens Travel Reservations SRL - "pay", // pay Amazon Registry Services, Inc. - "pccw", // pccw PCCW Enterprises Limited - "pet", // pet Afilias plc - "pfizer", // pfizer Pfizer Inc. - "pharmacy", // pharmacy National Association of Boards of Pharmacy - "phd", // phd Charleston Road Registry Inc. - "philips", // philips Koninklijke Philips N.V. - "phone", // phone Dish DBS Corporation - "photo", // photo Uniregistry, Corp. - "photography", // photography Sugar Glen, LLC - "photos", // photos Sea Corner, LLC - "physio", // physio PhysBiz Pty Ltd - "piaget", // piaget Richemont DNS Inc. - "pics", // pics Uniregistry, Corp. - "pictet", // pictet Pictet Europe S.A. - "pictures", // pictures Foggy Sky, LLC - "pid", // pid Top Level Spectrum, Inc. - "pin", // pin Amazon Registry Services, Inc. - "ping", // ping Ping Registry Provider, Inc. - "pink", // pink Afilias Limited - "pioneer", // pioneer Pioneer Corporation - "pizza", // pizza Foggy Moon, LLC - "place", // place Snow Galley, LLC - "play", // play Charleston Road Registry Inc. - "playstation", // playstation Sony Computer Entertainment Inc. - "plumbing", // plumbing Spring Tigers, LLC - "plus", // plus Sugar Mill, LLC - "pnc", // pnc PNC Domain Co., LLC - "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG - "poker", // poker Afilias Domains No. 5 Limited - "politie", // politie Politie Nederland - "porn", // porn ICM Registry PN LLC - "post", // post Universal Postal Union - "pramerica", // pramerica Prudential Financial, Inc. - "praxi", // praxi Praxi S.p.A. - "press", // press DotPress Inc. - "prime", // prime Amazon Registry Services, Inc. - "pro", // pro Registry Services Corporation dba RegistryPro - "prod", // prod Charleston Road Registry Inc. - "productions", // productions Magic Birch, LLC - "prof", // prof Charleston Road Registry Inc. - "progressive", // progressive Progressive Casualty Insurance Company - "promo", // promo Afilias plc - "properties", // properties Big Pass, LLC - "property", // property Uniregistry, Corp. - "protection", // protection XYZ.COM LLC - "pru", // pru Prudential Financial, Inc. - "prudential", // prudential Prudential Financial, Inc. - "pub", // pub United TLD Holdco Ltd. - "pwc", // pwc PricewaterhouseCoopers LLP - "qpon", // qpon dotCOOL, Inc. - "quebec", // quebec PointQuébec Inc - "quest", // quest Quest ION Limited - "qvc", // qvc QVC, Inc. - "racing", // racing Premier Registry Limited - "radio", // radio European Broadcasting Union (EBU) - "raid", // raid Johnson Shareholdings, Inc. - "read", // read Amazon Registry Services, Inc. - "realestate", // realestate dotRealEstate LLC - "realtor", // realtor Real Estate Domains LLC - "realty", // realty Fegistry, LLC - "recipes", // recipes Grand Island, LLC - "red", // red Afilias Limited - "redstone", // redstone Redstone Haute Couture Co., Ltd. - "redumbrella", // redumbrella Travelers TLD, LLC - "rehab", // rehab United TLD Holdco Ltd. - "reise", // reise Foggy Way, LLC - "reisen", // reisen New Cypress, LLC - "reit", // reit National Association of Real Estate Investment Trusts, Inc. - "reliance", // reliance Reliance Industries Limited - "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd. - "rent", // rent XYZ.COM LLC - "rentals", // rentals Big Hollow,LLC - "repair", // repair Lone Sunset, LLC - "report", // report Binky Glen, LLC - "republican", // republican United TLD Holdco Ltd. - "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable - "restaurant", // restaurant Snow Avenue, LLC - "review", // review dot Review Limited - "reviews", // reviews United TLD Holdco, Ltd. - "rexroth", // rexroth Robert Bosch GMBH - "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland - "richardli", // richardli Pacific Century Asset Management (HK) Limited - "ricoh", // ricoh Ricoh Company, Ltd. - "rightathome", // rightathome Johnson Shareholdings, Inc. - "ril", // ril Reliance Industries Limited - "rio", // rio Empresa Municipal de Informática SA - IPLANRIO - "rip", // rip United TLD Holdco Ltd. - "rmit", // rmit Royal Melbourne Institute of Technology - "rocher", // rocher Ferrero Trading Lux S.A. - "rocks", // rocks United TLD Holdco, LTD. - "rodeo", // rodeo Top Level Domain Holdings Limited - "rogers", // rogers Rogers Communications Canada Inc. - "room", // room Amazon Registry Services, Inc. - "rsvp", // rsvp Charleston Road Registry Inc. - "rugby", // rugby World Rugby Strategic Developments Limited - "ruhr", // ruhr regiodot GmbH & Co. KG - "run", // run Snow Park, LLC - "rwe", // rwe RWE AG - "ryukyu", // ryukyu BusinessRalliart inc. - "saarland", // saarland dotSaarland GmbH - "safe", // safe Amazon Registry Services, Inc. - "safety", // safety Safety Registry Services, LLC. - "sakura", // sakura SAKURA Internet Inc. - "sale", // sale United TLD Holdco, Ltd - "salon", // salon Outer Orchard, LLC - "samsclub", // samsclub Wal-Mart Stores, Inc. - "samsung", // samsung SAMSUNG SDS CO., LTD - "sandvik", // sandvik Sandvik AB - "sandvikcoromant", // sandvikcoromant Sandvik AB - "sanofi", // sanofi Sanofi - "sap", // sap SAP AG -// "sapo", // sapo PT Comunicacoes S.A. - "sarl", // sarl Delta Orchard, LLC - "sas", // sas Research IP LLC - "save", // save Amazon Registry Services, Inc. - "saxo", // saxo Saxo Bank A/S - "sbi", // sbi STATE BANK OF INDIA - "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION - "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) - "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") - "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG - "schmidt", // schmidt SALM S.A.S. - "scholarships", // scholarships Scholarships.com, LLC - "school", // school Little Galley, LLC - "schule", // schule Outer Moon, LLC - "schwarz", // schwarz Schwarz Domains und Services GmbH & Co. KG - "science", // science dot Science Limited - "scjohnson", // scjohnson Johnson Shareholdings, Inc. - "scor", // scor SCOR SE - "scot", // scot Dot Scot Registry Limited - "search", // search Charleston Road Registry Inc. - "seat", // seat SEAT, S.A. (Sociedad Unipersonal) - "secure", // secure Amazon Registry Services, Inc. - "security", // security XYZ.COM LLC - "seek", // seek Seek Limited - "select", // select iSelect Ltd - "sener", // sener Sener Ingeniería y Sistemas, S.A. - "services", // services Fox Castle, LLC - "ses", // ses SES - "seven", // seven Seven West Media Ltd - "sew", // sew SEW-EURODRIVE GmbH & Co KG - "sex", // sex ICM Registry SX LLC - "sexy", // sexy Uniregistry, Corp. - "sfr", // sfr Societe Francaise du Radiotelephone - SFR - "shangrila", // shangrila Shangri‐La International Hotel Management Limited - "sharp", // sharp Sharp Corporation - "shaw", // shaw Shaw Cablesystems G.P. - "shell", // shell Shell Information Technology International Inc - "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "shiksha", // shiksha Afilias Limited - "shoes", // shoes Binky Galley, LLC - "shop", // shop GMO Registry, Inc. - "shopping", // shopping Over Keep, LLC - "shouji", // shouji QIHOO 360 TECHNOLOGY CO. LTD. - "show", // show Snow Beach, LLC - "showtime", // showtime CBS Domains Inc. - "shriram", // shriram Shriram Capital Ltd. - "silk", // silk Amazon Registry Services, Inc. - "sina", // sina Sina Corporation - "singles", // singles Fern Madison, LLC - "site", // site DotSite Inc. - "ski", // ski STARTING DOT LIMITED - "skin", // skin L'Oréal - "sky", // sky Sky International AG - "skype", // skype Microsoft Corporation - "sling", // sling Hughes Satellite Systems Corporation - "smart", // smart Smart Communications, Inc. (SMART) - "smile", // smile Amazon Registry Services, Inc. - "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais) - "soccer", // soccer Foggy Shadow, LLC - "social", // social United TLD Holdco Ltd. - "softbank", // softbank SoftBank Group Corp. - "software", // software United TLD Holdco, Ltd - "sohu", // sohu Sohu.com Limited - "solar", // solar Ruby Town, LLC - "solutions", // solutions Silver Cover, LLC - "song", // song Amazon Registry Services, Inc. - "sony", // sony Sony Corporation - "soy", // soy Charleston Road Registry Inc. - "space", // space DotSpace Inc. - "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG - "sport", // sport Global Association of International Sports Federations (GAISF) - "spot", // spot Amazon Registry Services, Inc. - "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD - "srl", // srl InterNetX Corp. - "srt", // srt FCA US LLC. - "stada", // stada STADA Arzneimittel AG - "staples", // staples Staples, Inc. - "star", // star Star India Private Limited - "starhub", // starhub StarHub Limited - "statebank", // statebank STATE BANK OF INDIA - "statefarm", // statefarm State Farm Mutual Automobile Insurance Company - "statoil", // statoil Statoil ASA - "stc", // stc Saudi Telecom Company - "stcgroup", // stcgroup Saudi Telecom Company - "stockholm", // stockholm Stockholms kommun - "storage", // storage Self Storage Company LLC - "store", // store DotStore Inc. - "stream", // stream dot Stream Limited - "studio", // studio United TLD Holdco Ltd. - "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD - "style", // style Binky Moon, LLC - "sucks", // sucks Vox Populi Registry Ltd. - "supplies", // supplies Atomic Fields, LLC - "supply", // supply Half Falls, LLC - "support", // support Grand Orchard, LLC - "surf", // surf Top Level Domain Holdings Limited - "surgery", // surgery Tin Avenue, LLC - "suzuki", // suzuki SUZUKI MOTOR CORPORATION - "swatch", // swatch The Swatch Group Ltd - "swiftcover", // swiftcover Swiftcover Insurance Services Limited - "swiss", // swiss Swiss Confederation - "sydney", // sydney State of New South Wales, Department of Premier and Cabinet - "symantec", // symantec Symantec Corporation - "systems", // systems Dash Cypress, LLC - "tab", // tab Tabcorp Holdings Limited - "taipei", // taipei Taipei City Government - "talk", // talk Amazon Registry Services, Inc. - "taobao", // taobao Alibaba Group Holding Limited - "target", // target Target Domain Holdings, LLC - "tatamotors", // tatamotors Tata Motors Ltd - "tatar", // tatar Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" - "tattoo", // tattoo Uniregistry, Corp. - "tax", // tax Storm Orchard, LLC - "taxi", // taxi Pine Falls, LLC - "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "tdk", // tdk TDK Corporation - "team", // team Atomic Lake, LLC - "tech", // tech Dot Tech LLC - "technology", // technology Auburn Falls, LLC - "tel", // tel Telnic Ltd. -// "telecity", // telecity TelecityGroup International Limited - "telefonica", // telefonica Telefónica S.A. - "temasek", // temasek Temasek Holdings (Private) Limited - "tennis", // tennis Cotton Bloom, LLC - "teva", // teva Teva Pharmaceutical Industries Limited - "thd", // thd Homer TLC, Inc. - "theater", // theater Blue Tigers, LLC - "theatre", // theatre XYZ.COM LLC - "tiaa", // tiaa Teachers Insurance and Annuity Association of America - "tickets", // tickets Accent Media Limited - "tienda", // tienda Victor Manor, LLC - "tiffany", // tiffany Tiffany and Company - "tips", // tips Corn Willow, LLC - "tires", // tires Dog Edge, LLC - "tirol", // tirol punkt Tirol GmbH - "tjmaxx", // tjmaxx The TJX Companies, Inc. - "tjx", // tjx The TJX Companies, Inc. - "tkmaxx", // tkmaxx The TJX Companies, Inc. - "tmall", // tmall Alibaba Group Holding Limited - "today", // today Pearl Woods, LLC - "tokyo", // tokyo GMO Registry, Inc. - "tools", // tools Pioneer North, LLC - "top", // top Jiangsu Bangning Science & Technology Co.,Ltd. - "toray", // toray Toray Industries, Inc. - "toshiba", // toshiba TOSHIBA Corporation - "total", // total Total SA - "tours", // tours Sugar Station, LLC - "town", // town Koko Moon, LLC - "toyota", // toyota TOYOTA MOTOR CORPORATION - "toys", // toys Pioneer Orchard, LLC - "trade", // trade Elite Registry Limited - "trading", // trading DOTTRADING REGISTRY LTD - "training", // training Wild Willow, LLC - "travel", // travel Tralliance Registry Management Company, LLC. - "travelchannel", // travelchannel Lifestyle Domain Holdings, Inc. - "travelers", // travelers Travelers TLD, LLC - "travelersinsurance", // travelersinsurance Travelers TLD, LLC - "trust", // trust Artemis Internet Inc - "trv", // trv Travelers TLD, LLC - "tube", // tube Latin American Telecom LLC - "tui", // tui TUI AG - "tunes", // tunes Amazon Registry Services, Inc. - "tushu", // tushu Amazon Registry Services, Inc. - "tvs", // tvs T V SUNDRAM IYENGAR & SONS PRIVATE LIMITED - "ubank", // ubank National Australia Bank Limited - "ubs", // ubs UBS AG - "uconnect", // uconnect FCA US LLC. - "unicom", // unicom China United Network Communications Corporation Limited - "university", // university Little Station, LLC - "uno", // uno Dot Latin LLC - "uol", // uol UBN INTERNET LTDA. - "ups", // ups UPS Market Driver, Inc. - "vacations", // vacations Atomic Tigers, LLC - "vana", // vana Lifestyle Domain Holdings, Inc. - "vanguard", // vanguard The Vanguard Group, Inc. - "vegas", // vegas Dot Vegas, Inc. - "ventures", // ventures Binky Lake, LLC - "verisign", // verisign VeriSign, Inc. - "versicherung", // versicherung dotversicherung-registry GmbH - "vet", // vet United TLD Holdco, Ltd - "viajes", // viajes Black Madison, LLC - "video", // video United TLD Holdco, Ltd - "vig", // vig VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe - "viking", // viking Viking River Cruises (Bermuda) Ltd. - "villas", // villas New Sky, LLC - "vin", // vin Holly Shadow, LLC - "vip", // vip Minds + Machines Group Limited - "virgin", // virgin Virgin Enterprises Limited - "visa", // visa Visa Worldwide Pte. Limited - "vision", // vision Koko Station, LLC -// "vista", // vista Vistaprint Limited - "vistaprint", // vistaprint Vistaprint Limited - "viva", // viva Saudi Telecom Company - "vivo", // vivo Telefonica Brasil S.A. - "vlaanderen", // vlaanderen DNS.be vzw - "vodka", // vodka Top Level Domain Holdings Limited - "volkswagen", // volkswagen Volkswagen Group of America Inc. - "volvo", // volvo Volvo Holding Sverige Aktiebolag - "vote", // vote Monolith Registry LLC - "voting", // voting Valuetainment Corp. - "voto", // voto Monolith Registry LLC - "voyage", // voyage Ruby House, LLC - "vuelos", // vuelos Travel Reservations SRL - "wales", // wales Nominet UK - "walmart", // walmart Wal-Mart Stores, Inc. - "walter", // walter Sandvik AB - "wang", // wang Zodiac Registry Limited - "wanggou", // wanggou Amazon Registry Services, Inc. - "warman", // warman Weir Group IP Limited - "watch", // watch Sand Shadow, LLC - "watches", // watches Richemont DNS Inc. - "weather", // weather The Weather Channel, LLC - "weatherchannel", // weatherchannel The Weather Channel, LLC - "webcam", // webcam dot Webcam Limited - "weber", // weber Saint-Gobain Weber SA - "website", // website DotWebsite Inc. - "wed", // wed Atgron, Inc. - "wedding", // wedding Top Level Domain Holdings Limited - "weibo", // weibo Sina Corporation - "weir", // weir Weir Group IP Limited - "whoswho", // whoswho Who's Who Registry - "wien", // wien punkt.wien GmbH - "wiki", // wiki Top Level Design, LLC - "williamhill", // williamhill William Hill Organization Limited - "win", // win First Registry Limited - "windows", // windows Microsoft Corporation - "wine", // wine June Station, LLC - "winners", // winners The TJX Companies, Inc. - "wme", // wme William Morris Endeavor Entertainment, LLC - "wolterskluwer", // wolterskluwer Wolters Kluwer N.V. - "woodside", // woodside Woodside Petroleum Limited - "work", // work Top Level Domain Holdings Limited - "works", // works Little Dynamite, LLC - "world", // world Bitter Fields, LLC - "wow", // wow Amazon Registry Services, Inc. - "wtc", // wtc World Trade Centers Association, Inc. - "wtf", // wtf Hidden Way, LLC - "xbox", // xbox Microsoft Corporation - "xerox", // xerox Xerox DNHC LLC - "xfinity", // xfinity Comcast IP Holdings I, LLC - "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. - "xin", // xin Elegant Leader Limited - "xn--11b4c3d", // कॉम VeriSign Sarl - "xn--1ck2e1b", // セール Amazon Registry Services, Inc. - "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd. - "xn--30rr7y", // 慈善 Excellent First Limited - "xn--3bst00m", // 集团 Eagle Horizon Limited - "xn--3ds443g", // 在线 TLD REGISTRY LIMITED - "xn--3oq18vl8pn36a", // 大众汽车 Volkswagen (China) Investment Co., Ltd. - "xn--3pxu8k", // 点看 VeriSign Sarl - "xn--42c2d9a", // คอม VeriSign Sarl - "xn--45q11c", // 八卦 Zodiac Scorpio Limited - "xn--4gbrim", // موقع Suhub Electronic Establishment - "xn--55qw42g", // 公益 China Organizational Name Administration Center - "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) - "xn--5su34j936bgsg", // 香格里拉 Shangri‐La International Hotel Management Limited - "xn--5tzm5g", // 网站 Global Website TLD Asia Limited - "xn--6frz82g", // 移动 Afilias Limited - "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited - "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) - "xn--80aqecdr1a", // католик Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) - "xn--80asehdb", // онлайн CORE Association - "xn--80aswg", // сайт CORE Association - "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited - "xn--90ae", // бг Imena.BG Plc (NAMES.BG Plc) - "xn--9dbq2a", // קום VeriSign Sarl - "xn--9et52u", // 时尚 RISE VICTORY LIMITED - "xn--9krt00a", // 微博 Sina Corporation - "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited - "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc. - "xn--c1avg", // орг Public Interest Registry - "xn--c2br7g", // नेट VeriSign Sarl - "xn--cck2b3b", // ストア Amazon Registry Services, Inc. - "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD - "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG LIMITED - "xn--czrs0t", // 商店 Wild Island, LLC - "xn--czru2d", // 商城 Zodiac Aquarius Limited - "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet” - "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc. - "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社 - "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited - "xn--fct429k", // 家電 Amazon Registry Services, Inc. - "xn--fhbei", // كوم VeriSign Sarl - "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED - "xn--fiq64b", // 中信 CITIC Group Corporation - "xn--fjq720a", // 娱乐 Will Bloom, LLC - "xn--flw351e", // 谷歌 Charleston Road Registry Inc. - "xn--fzys8d69uvgm", // 電訊盈科 PCCW Enterprises Limited - "xn--g2xx48c", // 购物 Minds + Machines Group Limited - "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc. - "xn--gk3at1e", // 通販 Amazon Registry Services, Inc. - "xn--hxt814e", // 网店 Zodiac Libra Limited - "xn--i1b6b1a6a2e", // संगठन Public Interest Registry - "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED - "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) - "xn--j1aef", // ком VeriSign Sarl - "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation - "xn--jvr189m", // 食品 Amazon Registry Services, Inc. - "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V. - "xn--kpu716f", // 手表 Richemont DNS Inc. - "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd - "xn--mgba3a3ejt", // ارامكو Aramco Services Company - "xn--mgba7c0bbn0a", // العليان Crescent Holding GmbH - "xn--mgbaakc7dvf", // اتصالات Emirates Telecommunications Corporation (trading as Etisalat) - "xn--mgbab2bd", // بازار CORE Association - "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L. - "xn--mgbca7dzdo", // ابوظبي Abu Dhabi Systems and Information Centre - "xn--mgbi4ecexp", // كاثوليك Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) - "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "xn--mk1bu44c", // 닷컴 VeriSign Sarl - "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd. - "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd. - "xn--ngbe9e0a", // بيتك Kuwait Finance House - "xn--ngbrx", // عرب League of Arab States - "xn--nqv7f", // 机构 Public Interest Registry - "xn--nqv7fs00ema", // 组织机构 Public Interest Registry - "xn--nyqy26a", // 健康 Stable Tone Limited - "xn--otu796d", // 招聘 Dot Trademark TLD Holding Company Limited - "xn--p1acf", // рус Rusnames Limited - "xn--pbt977c", // 珠宝 Richemont DNS Inc. - "xn--pssy2u", // 大拿 VeriSign Sarl - "xn--q9jyb4c", // みんな Charleston Road Registry Inc. - "xn--qcka1pmc", // グーグル Charleston Road Registry Inc. - "xn--rhqv96g", // 世界 Stable Tone Limited - "xn--rovu88b", // 書籍 Amazon EU S.à r.l. - "xn--ses554g", // 网址 KNET Co., Ltd - "xn--t60b56a", // 닷넷 VeriSign Sarl - "xn--tckwe", // コム VeriSign Sarl - "xn--tiq49xqyj", // 天主教 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) - "xn--unup4y", // 游戏 Spring Fields, LLC - "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung Aktiengesellschaft DVAG - "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung Aktiengesellschaft DVAG - "xn--vhquv", // 企业 Dash McCook, LLC - "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd. - "xn--w4r85el8fhu5dnra", // 嘉里大酒店 Kerry Trading Co. Limited - "xn--w4rs40l", // 嘉里 Kerry Trading Co. Limited - "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd. - "xn--zfr164b", // 政务 China Organizational Name Administration Center -// "xperia", // xperia Sony Mobile Communications AB - "xxx", // xxx ICM Registry LLC - "xyz", // xyz XYZ.COM LLC - "yachts", // yachts DERYachts, LLC - "yahoo", // yahoo Yahoo! Domain Services Inc. - "yamaxun", // yamaxun Amazon Registry Services, Inc. - "yandex", // yandex YANDEX, LLC - "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD. - "yoga", // yoga Top Level Domain Holdings Limited - "yokohama", // yokohama GMO Registry, Inc. - "you", // you Amazon Registry Services, Inc. - "youtube", // youtube Charleston Road Registry Inc. - "yun", // yun QIHOO 360 TECHNOLOGY CO. LTD. - "zappos", // zappos Amazon Registry Services, Inc. - "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.) - "zero", // zero Amazon Registry Services, Inc. - "zip", // zip Charleston Road Registry Inc. - "zippo", // zippo Zadco Company - "zone", // zone Outer Falls, LLC - "zuerich", // zuerich Kanton Zürich (Canton of Zurich) - }; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] COUNTRY_CODE_TLDS = new String[]{ - // Taken from Version 2018031400, Last Updated Wed Mar 14 07:07:01 2018 UTC - "ac", // Ascension Island - "ad", // Andorra - "ae", // United Arab Emirates - "af", // Afghanistan - "ag", // Antigua and Barbuda - "ai", // Anguilla - "al", // Albania - "am", // Armenia -// "an", // Netherlands Antilles (retired) - "ao", // Angola - "aq", // Antarctica - "ar", // Argentina - "as", // American Samoa - "at", // Austria - "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) - "aw", // Aruba - "ax", // Åland - "az", // Azerbaijan - "ba", // Bosnia and Herzegovina - "bb", // Barbados - "bd", // Bangladesh - "be", // Belgium - "bf", // Burkina Faso - "bg", // Bulgaria - "bh", // Bahrain - "bi", // Burundi - "bj", // Benin - "bm", // Bermuda - "bn", // Brunei Darussalam - "bo", // Bolivia - "br", // Brazil - "bs", // Bahamas - "bt", // Bhutan - "bv", // Bouvet Island - "bw", // Botswana - "by", // Belarus - "bz", // Belize - "ca", // Canada - "cc", // Cocos (Keeling) Islands - "cd", // Democratic Republic of the Congo (formerly Zaire) - "cf", // Central African Republic - "cg", // Republic of the Congo - "ch", // Switzerland - "ci", // Côte d'Ivoire - "ck", // Cook Islands - "cl", // Chile - "cm", // Cameroon - "cn", // China, mainland - "co", // Colombia - "cr", // Costa Rica - "cu", // Cuba - "cv", // Cape Verde - "cw", // Curaçao - "cx", // Christmas Island - "cy", // Cyprus - "cz", // Czech Republic - "de", // Germany - "dj", // Djibouti - "dk", // Denmark - "dm", // Dominica - "do", // Dominican Republic - "dz", // Algeria - "ec", // Ecuador - "ee", // Estonia - "eg", // Egypt - "er", // Eritrea - "es", // Spain - "et", // Ethiopia - "eu", // European Union - "fi", // Finland - "fj", // Fiji - "fk", // Falkland Islands - "fm", // Federated States of Micronesia - "fo", // Faroe Islands - "fr", // France - "ga", // Gabon - "gb", // Great Britain (United Kingdom) - "gd", // Grenada - "ge", // Georgia - "gf", // French Guiana - "gg", // Guernsey - "gh", // Ghana - "gi", // Gibraltar - "gl", // Greenland - "gm", // The Gambia - "gn", // Guinea - "gp", // Guadeloupe - "gq", // Equatorial Guinea - "gr", // Greece - "gs", // South Georgia and the South Sandwich Islands - "gt", // Guatemala - "gu", // Guam - "gw", // Guinea-Bissau - "gy", // Guyana - "hk", // Hong Kong - "hm", // Heard Island and McDonald Islands - "hn", // Honduras - "hr", // Croatia (Hrvatska) - "ht", // Haiti - "hu", // Hungary - "id", // Indonesia - "ie", // Ireland (Éire) - "il", // Israel - "im", // Isle of Man - "in", // India - "io", // British Indian Ocean Territory - "iq", // Iraq - "ir", // Iran - "is", // Iceland - "it", // Italy - "je", // Jersey - "jm", // Jamaica - "jo", // Jordan - "jp", // Japan - "ke", // Kenya - "kg", // Kyrgyzstan - "kh", // Cambodia (Khmer) - "ki", // Kiribati - "km", // Comoros - "kn", // Saint Kitts and Nevis - "kp", // North Korea - "kr", // South Korea - "kw", // Kuwait - "ky", // Cayman Islands - "kz", // Kazakhstan - "la", // Laos (currently being marketed as the official domain for Los Angeles) - "lb", // Lebanon - "lc", // Saint Lucia - "li", // Liechtenstein - "lk", // Sri Lanka - "lr", // Liberia - "ls", // Lesotho - "lt", // Lithuania - "lu", // Luxembourg - "lv", // Latvia - "ly", // Libya - "ma", // Morocco - "mc", // Monaco - "md", // Moldova - "me", // Montenegro - "mg", // Madagascar - "mh", // Marshall Islands - "mk", // Republic of Macedonia - "ml", // Mali - "mm", // Myanmar - "mn", // Mongolia - "mo", // Macau - "mp", // Northern Mariana Islands - "mq", // Martinique - "mr", // Mauritania - "ms", // Montserrat - "mt", // Malta - "mu", // Mauritius - "mv", // Maldives - "mw", // Malawi - "mx", // Mexico - "my", // Malaysia - "mz", // Mozambique - "na", // Namibia - "nc", // New Caledonia - "ne", // Niger - "nf", // Norfolk Island - "ng", // Nigeria - "ni", // Nicaragua - "nl", // Netherlands - "no", // Norway - "np", // Nepal - "nr", // Nauru - "nu", // Niue - "nz", // New Zealand - "om", // Oman - "pa", // Panama - "pe", // Peru - "pf", // French Polynesia With Clipperton Island - "pg", // Papua New Guinea - "ph", // Philippines - "pk", // Pakistan - "pl", // Poland - "pm", // Saint-Pierre and Miquelon - "pn", // Pitcairn Islands - "pr", // Puerto Rico - "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) - "pt", // Portugal - "pw", // Palau - "py", // Paraguay - "qa", // Qatar - "re", // Réunion - "ro", // Romania - "rs", // Serbia - "ru", // Russia - "rw", // Rwanda - "sa", // Saudi Arabia - "sb", // Solomon Islands - "sc", // Seychelles - "sd", // Sudan - "se", // Sweden - "sg", // Singapore - "sh", // Saint Helena - "si", // Slovenia - "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) - "sk", // Slovakia - "sl", // Sierra Leone - "sm", // San Marino - "sn", // Senegal - "so", // Somalia - "sr", // Suriname - "st", // São Tomé and Príncipe - "su", // Soviet Union (deprecated) - "sv", // El Salvador - "sx", // Sint Maarten - "sy", // Syria - "sz", // Swaziland - "tc", // Turks and Caicos Islands - "td", // Chad - "tf", // French Southern and Antarctic Lands - "tg", // Togo - "th", // Thailand - "tj", // Tajikistan - "tk", // Tokelau - "tl", // East Timor (deprecated old code) - "tm", // Turkmenistan - "tn", // Tunisia - "to", // Tonga -// "tp", // East Timor (Retired) - "tr", // Turkey - "tt", // Trinidad and Tobago - "tv", // Tuvalu - "tw", // Taiwan, Republic of China - "tz", // Tanzania - "ua", // Ukraine - "ug", // Uganda - "uk", // United Kingdom - "us", // United States of America - "uy", // Uruguay - "uz", // Uzbekistan - "va", // Vatican City State - "vc", // Saint Vincent and the Grenadines - "ve", // Venezuela - "vg", // British Virgin Islands - "vi", // U.S. Virgin Islands - "vn", // Vietnam - "vu", // Vanuatu - "wf", // Wallis and Futuna - "ws", // Samoa (formerly Western Samoa) - "xn--2scrj9c", // ಭಾರತ National Internet eXchange of India - "xn--3e0b707e", // 한국 KISA (Korea Internet & Security Agency) - "xn--3hcrj9c", // ଭାରତ National Internet eXchange of India - "xn--45br5cyl", // ভাৰত National Internet eXchange of India - "xn--45brj9c", // ভারত National Internet Exchange of India - "xn--54b7fta0cc", // বাংলা Posts and Telecommunications Division - "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan - "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS) - "xn--90ais", // ??? Reliable Software Inc. - "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre (SGNIC) Pte Ltd - "xn--d1alf", // мкд Macedonian Academic Research Network Skopje - "xn--e1a4c", // ею EURid vzw/asbl - "xn--fiqs8s", // 中国 China Internet Network Information Center - "xn--fiqz9s", // 中國 China Internet Network Information Center - "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India - "xn--fzc2c9e2c", // ලංකා LK Domain Registry - "xn--gecrj9c", // ભારત National Internet Exchange of India - "xn--h2breg3eve", // भारतम् National Internet eXchange of India - "xn--h2brj9c", // भारत National Internet Exchange of India - "xn--h2brj9c8c", // भारोत National Internet eXchange of India - "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc. - "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd. - "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC) - "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC) - "xn--l1acc", // мон Datacom Co.,Ltd - "xn--lgbbat1ad8j", // الجزائر CERIST - "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA) - "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM) - "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA) - "xn--mgbai9azgqp6j", // پاکستان National Telecommunication Corporation - "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC) - "xn--mgbbh1a", // بارت National Internet eXchange of India - "xn--mgbbh1a71e", // بھارت National Internet Exchange of India - "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des Télécommunications (ANRT) - "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology Commission - "xn--mgbgu82a", // ڀارت National Internet eXchange of India - "xn--mgbpl2fh", // ????? Sudan Internet Society - "xn--mgbtx2b", // عراق Communications and Media Commission (CMC) - "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad - "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT) - "xn--node", // გე Information Technologies Development Center (ITDC) - "xn--o3cw4h", // ไทย Thai Network Information Center Foundation - "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS) - "xn--p1ai", // рф Coordination Center for TLD RU - "xn--pgbs0dh", // تونس Agence Tunisienne d'Internet - "xn--qxam", // ελ ICS-FORTH GR - "xn--rvc1e0am3e", // ഭാരതം National Internet eXchange of India - "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India - "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA - "xn--wgbl6a", // قطر Communications Regulatory Authority - "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry - "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India - "xn--y9a3aq", // ??? Internet Society - "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd - "xn--ygbi2ammx", // فلسطين Ministry of Telecom & Information Technology (MTIT) - "ye", // Yemen - "yt", // Mayotte - "za", // South Africa - "zm", // Zambia - "zw", // Zimbabwe - }; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] LOCAL_TLDS = new String[]{ - "localdomain", // Also widely used as localhost.localdomain - "localhost", // RFC2606 defined - }; - - // Additional arrays to supplement or override the built in ones. - // The PLUS arrays are valid keys, the MINUS arrays are invalid keys - - /* - * This field is used to detect whether the getInstance has been called. - * After this, the method updateTLDOverride is not allowed to be called. - * This field does not need to be volatile since it is only accessed from - * synchronized methods. - */ - private static boolean inUse = false; - - /* - * These arrays are mutable, but they don't need to be volatile. - * They can only be updated by the updateTLDOverride method, and any readers must get an instance - * using the getInstance methods which are all (now) synchronised. - */ - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static volatile String[] countryCodeTLDsPlus = EMPTY_STRING_ARRAY; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static volatile String[] genericTLDsPlus = EMPTY_STRING_ARRAY; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static volatile String[] countryCodeTLDsMinus = EMPTY_STRING_ARRAY; - - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static volatile String[] genericTLDsMinus = EMPTY_STRING_ARRAY; - - /** - * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])} - * to determine which override array to update / fetch - * - * @since 1.5.0 - * @since 1.5.1 made public and added read-only array references - */ - public enum ArrayType { - /** - * Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs - */ - GENERIC_PLUS, - /** - * Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs - */ - GENERIC_MINUS, - /** - * Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country code TLDs - */ - COUNTRY_CODE_PLUS, - /** - * Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country code TLDs - */ - COUNTRY_CODE_MINUS, - /** - * Get a copy of the generic TLDS table - */ - GENERIC_RO, - /** - * Get a copy of the country code table - */ - COUNTRY_CODE_RO, - /** - * Get a copy of the infrastructure table - */ - INFRASTRUCTURE_RO, - /** - * Get a copy of the local table - */ - LOCAL_RO; - } - - ; - - // For use by unit test code only - static synchronized void clearTLDOverrides() { - inUse = false; - countryCodeTLDsPlus = EMPTY_STRING_ARRAY; - countryCodeTLDsMinus = EMPTY_STRING_ARRAY; - genericTLDsPlus = EMPTY_STRING_ARRAY; - genericTLDsMinus = EMPTY_STRING_ARRAY; - } - - /** - * Update one of the TLD override arrays. - * This must only be done at program startup, before any instances are accessed using getInstance. - *

- * For example: - *

- * {@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"apache"})} - *

- * To clear an override array, provide an empty array. - * - * @param table the table to update, see {@link DomainValidator.ArrayType} - * Must be one of the following - *

    - *
  • COUNTRY_CODE_MINUS
  • - *
  • COUNTRY_CODE_PLUS
  • - *
  • GENERIC_MINUS
  • - *
  • GENERIC_PLUS
  • - *
- * @param tlds the array of TLDs, must not be null - * @throws IllegalStateException if the method is called after getInstance - * @throws IllegalArgumentException if one of the read-only tables is requested - * @since 1.5.0 - */ - public static synchronized void updateTLDOverride(ArrayType table, String[] tlds) { - if (inUse) { - throw new IllegalStateException("Can only invoke this method before calling getInstance"); - } - String[] copy = new String[tlds.length]; - // Comparisons are always done with lower-case entries - for (int i = 0; i < tlds.length; i++) { - copy[i] = tlds[i].toLowerCase(Locale.ENGLISH); - } - Arrays.sort(copy); - switch (table) { - case COUNTRY_CODE_MINUS: - countryCodeTLDsMinus = copy; - break; - case COUNTRY_CODE_PLUS: - countryCodeTLDsPlus = copy; - break; - case GENERIC_MINUS: - genericTLDsMinus = copy; - break; - case GENERIC_PLUS: - genericTLDsPlus = copy; - break; - case COUNTRY_CODE_RO: - case GENERIC_RO: - case INFRASTRUCTURE_RO: - case LOCAL_RO: - throw new IllegalArgumentException("Cannot update the table: " + table); - default: - throw new IllegalArgumentException("Unexpected enum value: " + table); - } - } - - /** - * Get a copy of the internal array. - * - * @param table the array type (any of the enum values) - * @return a copy of the array - * @throws IllegalArgumentException if the table type is unexpected (should not happen) - * @since 1.5.1 - */ - public static String[] getTLDEntries(ArrayType table) { - final String array[]; - switch (table) { - case COUNTRY_CODE_MINUS: - array = countryCodeTLDsMinus; - break; - case COUNTRY_CODE_PLUS: - array = countryCodeTLDsPlus; - break; - case GENERIC_MINUS: - array = genericTLDsMinus; - break; - case GENERIC_PLUS: - array = genericTLDsPlus; - break; - case GENERIC_RO: - array = GENERIC_TLDS; - break; - case COUNTRY_CODE_RO: - array = COUNTRY_CODE_TLDS; - break; - case INFRASTRUCTURE_RO: - array = INFRASTRUCTURE_TLDS; - break; - case LOCAL_RO: - array = LOCAL_TLDS; - break; - default: - throw new IllegalArgumentException("Unexpected enum value: " + table); - } - return Arrays.copyOf(array, array.length); // clone the array - } - - /** - * Converts potentially Unicode input to punycode. - * If conversion fails, returns the original input. - * - * @param input the string to convert, not null - * @return converted input, or original input if conversion fails - */ - // Needed by UrlValidator - static String unicodeToASCII(String input) { - if (isOnlyASCII(input)) { // skip possibly expensive processing - return input; - } - try { - final String ascii = IDN.toASCII(input); - if (IDNBUGHOLDER.IDN_TOASCII_PRESERVES_TRAILING_DOTS) { - return ascii; - } - final int length = input.length(); - if (length == 0) {// check there is a last character - return input; - } - // RFC3490 3.1. 1) - // Whenever dots are used as label separators, the following - // characters MUST be recognized as dots: U+002E (full stop), U+3002 - // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 - // (halfwidth ideographic full stop). - char lastChar = input.charAt(length - 1);// fetch original last char - switch (lastChar) { - case '\u002E': // "." full stop - case '\u3002': // ideographic full stop - case '\uFF0E': // fullwidth full stop - case '\uFF61': // halfwidth ideographic full stop - return ascii + "."; // restore the missing stop - default: - return ascii; - } - } catch (IllegalArgumentException e) { // input is not valid - return input; - } - } - - private static class IDNBUGHOLDER { - private static boolean keepsTrailingDot() { - final String input = "a."; // must be a valid name - return input.equals(IDN.toASCII(input)); - } - - private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot(); - } - - /* - * Check if input contains only ASCII - * Treats null as all ASCII - */ - private static boolean isOnlyASCII(String input) { - if (input == null) { - return true; - } - for (int i = 0; i < input.length(); i++) { - if (input.charAt(i) > 0x7F) { // CHECKSTYLE IGNORE MagicNumber - return false; - } - } - return true; - } - - /** - * Check if a sorted array contains the specified key - * - * @param sortedArray the array to search - * @param key the key to find - * @return {@code true} if the array contains the key - */ - private static boolean arrayContains(String[] sortedArray, String key) { - return Arrays.binarySearch(sortedArray, key) >= 0; - } -} diff --git a/src/main/java/com/networknt/schema/format/DurationFormat.java b/src/main/java/com/networknt/schema/format/DurationFormat.java new file mode 100644 index 000000000..0d1f04411 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/DurationFormat.java @@ -0,0 +1,46 @@ +package com.networknt.schema.format; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.SchemaContext; + +/** + * Format for duration. + */ +public class DurationFormat implements Format { + private static final String DURATION = "duration"; + + private static final Pattern STRICT = Pattern.compile("^(?:P\\d+W)|(?:P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?)$", Pattern.CASE_INSENSITIVE); + private static final Pattern LAX = Pattern.compile("^(?:[-+]?)P(?:[-+]?[0-9]+Y)?(?:[-+]?[0-9]+M)?(?:[-+]?[0-9]+W)?(?:[-+]?[0-9]+D)?(?:T(?:[-+]?[0-9]+H)?(?:[-+]?[0-9]+M)?(?:[-+]?[0-9]+(?:[.,][0-9]{0,9})?S)?)?$", Pattern.CASE_INSENSITIVE); + + @Override + public boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, String duration) { + if (null == duration) { + return true; + } + + if (duration.endsWith("P") || duration.endsWith("T")) { + return false; + } + + Pattern pattern = isStrictValidation(schemaContext) ? STRICT : LAX; + Matcher matcher = pattern.matcher(duration); + return matcher.matches(); + } + + protected boolean isStrictValidation(SchemaContext schemaContext) { + return schemaContext.getSchemaRegistryConfig().isStrict(DURATION); + } + + @Override + public String getName() { + return "duration"; + } + + @Override + public String getMessageKey() { + return "format.duration"; + } +} diff --git a/src/main/java/com/networknt/schema/format/EmailFormat.java b/src/main/java/com/networknt/schema/format/EmailFormat.java new file mode 100644 index 000000000..86cbb5106 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/EmailFormat.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.format; + +import com.networknt.org.apache.commons.validator.routines.EmailValidator; +import com.networknt.schema.ExecutionContext; + +/** + * Format for email. + */ +public class EmailFormat implements Format { + private final EmailValidator emailValidator; + + public EmailFormat() { + this(new IPv6AwareEmailValidator(true, true)); + } + + public EmailFormat(EmailValidator emailValidator) { + this.emailValidator = emailValidator; + } + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return this.emailValidator.isValid(value); + } + + @Override + public String getName() { + return "email"; + } + + @Override + public String getMessageKey() { + return "format.email"; + } +} diff --git a/src/main/java/com/networknt/schema/format/EmailValidator.java b/src/main/java/com/networknt/schema/format/EmailValidator.java deleted file mode 100644 index e69f18301..000000000 --- a/src/main/java/com/networknt/schema/format/EmailValidator.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema.format; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - *

Perform email validations.

- *

- * Based on a script by Sandeep V. Tamhankar - * http://javascript.internet.com - *

- *

- * This implementation is not guaranteed to catch all possible errors in an email address. - *

. - * - * @version $Revision$ - * @since Validator 1.4 - */ -public class EmailValidator extends BaseJsonValidator implements JsonValidator { - private static final Logger logger = LoggerFactory.getLogger(EmailValidator.class); - - private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; - private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]"; - private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")"; - private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; - - private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$"; - private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; - private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$"; - - private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); - private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); - private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); - - private static final int MAX_USERNAME_LEN = 64; - - private final boolean allowLocal = false; - private final boolean allowTld = false; - private final String formatName; - - private final ValidationContext validationContext; - - /** - *

Checks if a field has a valid e-mail address.

- * - * @param email The value validation is being performed on. A null - * value is considered invalid. - * @return true if the email address is valid. - */ - public boolean isValid(String email) { - if (email == null) { - return false; - } - - if (email.endsWith(".")) { // check this first - it's cheap! - return false; - } - - // Check the whole email address structure - Matcher emailMatcher = EMAIL_PATTERN.matcher(email); - if (!emailMatcher.matches()) { - return false; - } - - if (!isValidUser(emailMatcher.group(1))) { - return false; - } - - if (!isValidDomain(emailMatcher.group(2))) { - return false; - } - - return true; - } - - /** - * Returns true if the domain component of an email address is valid. - * - * @param domain being validated, may be in IDN format - * @return true if the email address's domain is valid. - */ - protected boolean isValidDomain(String domain) { - // see if domain is an IP address in brackets - Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); - - if (ipDomainMatcher.matches()) { - InetAddressValidator inetAddressValidator = - InetAddressValidator.getInstance(); - return inetAddressValidator.isValid(ipDomainMatcher.group(1)); - } - // Domain is symbolic name - DomainValidator domainValidator = - DomainValidator.getInstance(allowLocal); - if (allowTld) { - return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain)); - } else { - return domainValidator.isValid(domain); - } - } - - /** - * Returns true if the user component of an email address is valid. - * - * @param user being validated - * @return true if the user name is valid. - */ - protected boolean isValidUser(String user) { - - if (user == null || user.length() > MAX_USERNAME_LEN) { - return false; - } - - return USER_PATTERN.matcher(user).matches(); - } - - public EmailValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { - super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT, validationContext); - this.formatName = formatName; - this.validationContext = validationContext; - parseErrorCode(getValidatorType().getErrorCodeKey()); - } - - @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { - debug(logger, node, rootNode, at); - - Set errors = new LinkedHashSet(); - - JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); - if (nodeType != JsonType.STRING) { - return errors; - } - if (!isValid(node.textValue())) { - errors.add(buildValidationMessage(at, node.textValue(), formatName)); - } - return Collections.unmodifiableSet(errors); - - } -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/format/Format.java b/src/main/java/com/networknt/schema/format/Format.java new file mode 100644 index 000000000..cf8064c61 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/Format.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.format; + +import java.util.function.Supplier; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.MessageSourceError; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.keyword.FormatValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * Used to implement the various formats for the format keyword. + *

+ * Simple implementations need only override {@link #matches(ExecutionContext, String)}. + */ +public interface Format { + /** + * Gets the format name. + * + * @return the format name as referred to in a json schema format node. + */ + String getName(); + + /** + * Gets the message key to use for the message. + *

+ * See jsv-messages.properties. + *

+ * The following are the arguments.
+ * {0} The format name
+ * {1} The input value + *

+ * Note that the default localized messages do not use the input value. + * + * @return the message key + */ + default String getMessageKey() { + return "format"; + } + + /** + * Determines if the value matches the format. + *

+ * This should be implemented for string node types. + * + * @param executionContext the execution context + * @param value to match + * @return true if matches + */ + default boolean matches(ExecutionContext executionContext, String value) { + return true; + } + + /** + * Determines if the value matches the format. + * + * @param executionContext the execution context + * @param schemaContext the schema context + * @param value to match + * @return true if matches + */ + default boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, String value) { + return matches(executionContext, value); + } + + /** + * Determines if the value matches the format. + * + * @param executionContext the execution context + * @param schemaContext the schema context + * @param value to match + * @return true if matches + */ + default boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, JsonNode value) { + JsonType nodeType = TypeFactory.getValueNodeType(value, schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + return true; + } + return matches(executionContext, schemaContext, value.textValue()); + } + + /** + * Determines if the value matches the format. + *

+ * This can be implemented for non-string node types. + * + * @param executionContext the execution context + * @param schemaContext the schema context + * @param node the node + * @param rootNode the root node + * @param instanceLocation the instance location + * @param assertionsEnabled if assertions are enabled + * @param formatValidator the format validator + * @return true if matches + */ + default boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, JsonNode node, + JsonNode rootNode, NodePath instanceLocation, boolean assertionsEnabled, FormatValidator formatValidator) { + return matches(executionContext, schemaContext, node); + } + + /** + * Validates the format. + *

+ * This is the most flexible method to implement. + * + * @param executionContext the execution context + * @param schemaContext the schema context + * @param node the node + * @param rootNode the root node + * @param instanceLocation the instance locaiton + * @param assertionsEnabled if assertions are enabled + * @param message the message builder + * @param formatValidator the format validator + */ + default void validate(ExecutionContext executionContext, SchemaContext schemaContext, + JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean assertionsEnabled, + Supplier message, + FormatValidator formatValidator) { + if (assertionsEnabled) { + if (!matches(executionContext, schemaContext, node, rootNode, instanceLocation, assertionsEnabled, + formatValidator)) { + executionContext.addError(message.get() + .arguments(this.getName(), node.asText()).build()); + } + } + } +} diff --git a/src/main/java/com/networknt/schema/format/Formats.java b/src/main/java/com/networknt/schema/format/Formats.java new file mode 100644 index 000000000..7ef566e17 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/Formats.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Formats. + */ +public class Formats { + private Formats() { + } + + static PatternFormat pattern(String name, String regex, String messageKey) { + return PatternFormat.of(name, regex, messageKey); + } + + static PatternFormat pattern(String name, String regex) { + return pattern(name, regex, null); + } + + public static final List DEFAULT; + + static { + List formats = new ArrayList<>(); + + formats.add(pattern("hostname", "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$", "format.hostname")); + formats.add(pattern("ipv4", "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$", "format.ipv4")); + formats.add(new IPv6Format()); + formats.add(pattern("json-pointer", "^(/([^/#~]|[~](?=[01]))*)*$", "format.json-pointer")); + formats.add(pattern("relative-json-pointer", "^(0|([1-9]\\d*))(#|(/([^/#~]|[~](?=[01]))*)*)$", "format.relative-json-pointer")); + formats.add(pattern("uri-template", "^([^\\p{Cntrl}\"'%<>\\^`\\{|\\}]|%\\p{XDigit}{2}|\\{[+#./;?&=,!@|]?((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?)(,((\\w|%\\p{XDigit}{2})(\\.?(\\w|%\\p{XDigit}{2}))*(:[1-9]\\d{0,3}|\\*)?))*\\})*$", "format.uri-template")); + formats.add(pattern("uuid", "^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$", "format.uuid")); + formats.add(new DateFormat()); + formats.add(new DateTimeFormat()); + formats.add(new EmailFormat()); + formats.add(new IdnEmailFormat()); + formats.add(new IdnHostnameFormat()); + formats.add(new IriFormat()); + formats.add(new IriReferenceFormat()); + formats.add(new RegexFormat()); + formats.add(new TimeFormat()); + formats.add(new UriFormat()); + formats.add(new UriReferenceFormat()); + formats.add(new DurationFormat()); + + // The following formats do not appear in any draft + formats.add(pattern("alpha", "^[a-zA-Z]+$")); + formats.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$")); + formats.add(pattern("color", "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))")); + formats.add(pattern("ip-address", "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")); + formats.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$")); + formats.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?")); + formats.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$")); + + DEFAULT = Collections.unmodifiableList(formats); + } + +} diff --git a/src/main/java/com/networknt/schema/format/IPv6AwareEmailValidator.java b/src/main/java/com/networknt/schema/format/IPv6AwareEmailValidator.java new file mode 100644 index 000000000..c9e7fc401 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IPv6AwareEmailValidator.java @@ -0,0 +1,33 @@ +package com.networknt.schema.format; + +import com.networknt.org.apache.commons.validator.routines.DomainValidator; +import com.networknt.org.apache.commons.validator.routines.EmailValidator; + +/** + * This is an extension of the Apache Commons Validator that correctly + * handles email addresses containing an IPv6 literal as the domain. + *

+ * Apache's {@link EmailValidator} delegates validation of the domain to + * its {@link DomainValidator}, which is not aware that it is validating + * an email address, which has a peculiar way of representing an IPv6 + * literal. + */ +class IPv6AwareEmailValidator extends EmailValidator { + private static final long serialVersionUID = 1L; + + /** + * Creates a new IPv6AwareEmailValidator. + * + * @param allowLocal Should local addresses be considered valid? + * @param allowTld Should TLDs be allowed? + */ + public IPv6AwareEmailValidator(final boolean allowLocal, final boolean allowTld) { + super(allowLocal, allowTld); + } + + @Override + protected boolean isValidDomain(String domain) { + return super.isValidDomain(domain.startsWith("[IPv6:") ? domain.replace("IPv6:", "") : domain); + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/format/IPv6Format.java b/src/main/java/com/networknt/schema/format/IPv6Format.java new file mode 100644 index 000000000..15298cb33 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IPv6Format.java @@ -0,0 +1,36 @@ +package com.networknt.schema.format; + +import java.util.regex.Pattern; + +import com.networknt.schema.ExecutionContext; + +/** + * Format for ipv6. + */ +public class IPv6Format implements Format { + public static final String PATTERN_VALUE = "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$"; + + public static final Pattern PATTERN = Pattern.compile(PATTERN_VALUE); + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + if (!value.trim().equals(value)) { + // leading and trailing spaces + return false; + } else if (value.contains("%")) { + // zone id is not part of the ipv6 + return false; + } + return PATTERN.matcher(value).matches(); + } + + @Override + public String getName() { + return "ipv6"; + } + + @Override + public String getMessageKey() { + return "format.ipv6"; + } +} diff --git a/src/main/java/com/networknt/schema/format/IdnEmailFormat.java b/src/main/java/com/networknt/schema/format/IdnEmailFormat.java new file mode 100644 index 000000000..880071082 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IdnEmailFormat.java @@ -0,0 +1,34 @@ +package com.networknt.schema.format; + +import com.networknt.org.apache.commons.validator.routines.EmailValidator; +import com.networknt.schema.ExecutionContext; + +/** + * Format for idn-email. + */ +public class IdnEmailFormat implements Format { + private final EmailValidator emailValidator; + + public IdnEmailFormat() { + this(new IPv6AwareEmailValidator(true, true)); + } + + public IdnEmailFormat(EmailValidator emailValidator) { + this.emailValidator = emailValidator; + } + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return this.emailValidator.isValid(value); + } + + @Override + public String getName() { + return "idn-email"; + } + + @Override + public String getMessageKey() { + return "format.idn-email"; + } +} diff --git a/src/main/java/com/networknt/schema/format/IdnHostnameFormat.java b/src/main/java/com/networknt/schema/format/IdnHostnameFormat.java new file mode 100644 index 000000000..e5f46808f --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IdnHostnameFormat.java @@ -0,0 +1,25 @@ +package com.networknt.schema.format; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.utils.RFC5892; + +/** + * Format for idn-hostname. + */ +public class IdnHostnameFormat implements Format { + @Override + public boolean matches(ExecutionContext executionContext, String value) { + if (null == value) return true; + return RFC5892.isValid(value); + } + + @Override + public String getName() { + return "idn-hostname"; + } + + @Override + public String getMessageKey() { + return "format.idn-hostname"; + } +} diff --git a/src/main/java/com/networknt/schema/format/InetAddressValidator.java b/src/main/java/com/networknt/schema/format/InetAddressValidator.java deleted file mode 100644 index a26c55264..000000000 --- a/src/main/java/com/networknt/schema/format/InetAddressValidator.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.format; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - *

InetAddress validation and conversion routines (java.net.InetAddress).

- * - *

This class provides methods to validate a candidate IP address. - * - *

- * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method. - *

- * - * @version $Revision$ - * @since Validator 1.4 - */ -public class InetAddressValidator implements Serializable { - - private static final int IPV4_MAX_OCTET_VALUE = 255; - - private static final int MAX_UNSIGNED_SHORT = 0xffff; - - private static final int BASE_16 = 16; - - private static final long serialVersionUID = -919201640201914789L; - - private static final String IPV4_REGEX = - "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; - - // Max number of hex groups (separated by :) in an IPV6 address - private static final int IPV6_MAX_HEX_GROUPS = 8; - - // Max hex digits in each IPv6 group - private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; - - /** - * Singleton instance of this class. - */ - private static final InetAddressValidator VALIDATOR = new InetAddressValidator(); - - /** - * IPv4 RegexValidator - */ - private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); - - /** - * Returns the singleton instance of this validator. - * - * @return the singleton instance of this validator - */ - public static InetAddressValidator getInstance() { - return VALIDATOR; - } - - /** - * Checks if the specified string is a valid IP address. - * - * @param inetAddress the string to validate - * @return true if the string validates as an IP address - */ - public boolean isValid(String inetAddress) { - return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress); - } - - /** - * Validates an IPv4 address. Returns true if valid. - * - * @param inet4Address the IPv4 address to validate - * @return true if the argument contains a valid IPv4 address - */ - public boolean isValidInet4Address(String inet4Address) { - // verify that address conforms to generic IPv4 format - String[] groups = ipv4Validator.match(inet4Address); - - if (groups == null) { - return false; - } - - // verify that address subgroups are legal - for (String ipSegment : groups) { - if (ipSegment == null || ipSegment.length() == 0) { - return false; - } - - int iIpSegment = 0; - - try { - iIpSegment = Integer.parseInt(ipSegment); - } catch (NumberFormatException e) { - return false; - } - - if (iIpSegment > IPV4_MAX_OCTET_VALUE) { - return false; - } - - if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { - return false; - } - - } - - return true; - } - - /** - * Validates an IPv6 address. Returns true if valid. - * - * @param inet6Address the IPv6 address to validate - * @return true if the argument contains a valid IPv6 address - * @since 1.4.1 - */ - public boolean isValidInet6Address(String inet6Address) { - boolean containsCompressedZeroes = inet6Address.contains("::"); - if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) { - return false; - } - if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::")) - || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) { - return false; - } - String[] octets = inet6Address.split(":"); - if (containsCompressedZeroes) { - List octetList = new ArrayList(Arrays.asList(octets)); - if (inet6Address.endsWith("::")) { - // String.split() drops ending empty segments - octetList.add(""); - } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { - octetList.remove(0); - } - octets = octetList.toArray(new String[octetList.size()]); - } - if (octets.length > IPV6_MAX_HEX_GROUPS) { - return false; - } - int validOctets = 0; - int emptyOctets = 0; // consecutive empty chunks - for (int index = 0; index < octets.length; index++) { - String octet = octets[index]; - if (octet.length() == 0) { - emptyOctets++; - if (emptyOctets > 1) { - return false; - } - } else { - emptyOctets = 0; - // Is last chunk an IPv4 address? - if (index == octets.length - 1 && octet.contains(".")) { - if (!isValidInet4Address(octet)) { - return false; - } - validOctets += 2; - continue; - } - if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { - return false; - } - int octetInt = 0; - try { - octetInt = Integer.parseInt(octet, BASE_16); - } catch (NumberFormatException e) { - return false; - } - if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { - return false; - } - } - validOctets++; - } - if (validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/format/IriFormat.java b/src/main/java/com/networknt/schema/format/IriFormat.java new file mode 100644 index 000000000..7d74429fe --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IriFormat.java @@ -0,0 +1,40 @@ +package com.networknt.schema.format; + +import java.net.URI; + +/** + * Format for iri. + */ +public class IriFormat extends AbstractRFC3986Format { + @Override + protected boolean validate(URI uri) { + boolean result = uri.isAbsolute(); + if (result) { + String authority = uri.getAuthority(); + if (authority != null) { + if (IPv6Format.PATTERN.matcher(authority).matches() ) { + return false; + } + } + + String query = uri.getRawQuery(); + if (query != null) { + // [ and ] must be percent encoded + if (query.indexOf('[') != -1 || query.indexOf(']') != -1) { + return false; + } + } + } + return result; + } + + @Override + public String getName() { + return "iri"; + } + + @Override + public String getMessageKey() { + return "format.iri"; + } +} diff --git a/src/main/java/com/networknt/schema/format/IriReferenceFormat.java b/src/main/java/com/networknt/schema/format/IriReferenceFormat.java new file mode 100644 index 000000000..b0be2bb0a --- /dev/null +++ b/src/main/java/com/networknt/schema/format/IriReferenceFormat.java @@ -0,0 +1,35 @@ +package com.networknt.schema.format; + +import java.net.URI; + +/** + * Format for iri-reference. + */ +public class IriReferenceFormat extends AbstractRFC3986Format { + @Override + protected boolean validate(URI uri) { + String authority = uri.getAuthority(); + if (authority != null) { + if (IPv6Format.PATTERN.matcher(authority).matches() ) { + return false; + } + } + String query = uri.getRawQuery(); + if (query != null) { + // [ and ] must be percent encoded + return query.indexOf('[') == -1 && query.indexOf(']') == -1; + } + return true; + } + + @Override + public String getName() { + return "iri-reference"; + } + + @Override + public String getMessageKey() { + return "format.iri-reference"; + } + +} diff --git a/src/main/java/com/networknt/schema/format/PatternFormat.java b/src/main/java/com/networknt/schema/format/PatternFormat.java new file mode 100644 index 000000000..c980da7ab --- /dev/null +++ b/src/main/java/com/networknt/schema/format/PatternFormat.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.format; + +import java.util.regex.Pattern; + +import com.networknt.schema.ExecutionContext; + +/** + * Format using a regex pattern. + */ +public class PatternFormat implements Format { + private final String name; + private final Pattern pattern; + private final String messageKey; + + /** + * Constructor. + *

+ * Use {@link #of(String, String, String)} instead. + * + * @param name the name + * @param regex the regex + */ + private PatternFormat(String name, String regex, String messageKey) { + this.name = name; + this.messageKey = messageKey; + this.pattern = Pattern.compile(regex); + } + + /** + * Creates a pattern format. + * + * @param name the name + * @param regex the regex pattern + * @param messageKey the message key + * @return the pattern format + */ + public static PatternFormat of(String name, String regex, String messageKey) { + return new PatternFormat(name, regex, messageKey != null ? messageKey : "format"); + } + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return this.pattern.matcher(value).matches(); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getMessageKey() { + return this.messageKey; + } +} diff --git a/src/main/java/com/networknt/schema/format/RegexFormat.java b/src/main/java/com/networknt/schema/format/RegexFormat.java new file mode 100644 index 000000000..dbb920cf8 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/RegexFormat.java @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.regex.RegularExpression; + +/** + * Format for regex. + */ +public class RegexFormat implements Format { + @Override + public boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, String value) { + if (null == value) return true; + try { + RegularExpression.compile(value, schemaContext); + return true; + } catch (RuntimeException e) { + return false; + } + } + + @Override + public String getName() { + return "regex"; + } + + @Override + public String getMessageKey() { + return "format.regex"; + } +} diff --git a/src/main/java/com/networknt/schema/format/RegexValidator.java b/src/main/java/com/networknt/schema/format/RegexValidator.java deleted file mode 100644 index 5b9fbda8b..000000000 --- a/src/main/java/com/networknt/schema/format/RegexValidator.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema.format; - -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Regular Expression validation (using JDK 1.4+ regex support). - *

- * Construct the validator either for a single regular expression or a set (array) of - * regular expressions. By default validation is case sensitive but constructors - * are provided to allow case in-sensitive validation. For example to create - * a validator which does case in-sensitive validation for a set of regular - * expressions: - *

- *
- * 
- * String[] regexs = new String[] {...};
- * RegexValidator validator = new RegexValidator(regexs, false);
- * 
- * 
- * - *
    - *
  • Validate true or false:
  • - *
  • - *
      - *
    • boolean valid = validator.isValid(value);
    • - *
    - *
  • - *
  • Validate returning an aggregated String of the matched groups:
  • - *
  • - *
      - *
    • String result = validator.validate(value);
    • - *
    - *
  • - *
  • Validate returning the matched groups:
  • - *
  • - *
      - *
    • String[] result = validator.match(value);
    • - *
    - *
  • - *
- * - * Note that patterns are matched against the entire input. - * - *

- * Cached instances pre-compile and re-use {@link Pattern}(s) - which according - * to the {@link Pattern} API are safe to use in a multi-threaded environment. - *

- * - * @version $Revision$ - * @since Validator 1.4 - */ -public class RegexValidator implements Serializable { - - private static final long serialVersionUID = -8832409930574867162L; - - private final Pattern[] patterns; - - /** - * Construct a case sensitive validator for a single - * regular expression. - * - * @param regex The regular expression this validator will - * validate against - */ - public RegexValidator(String regex) { - this(regex, true); - } - - /** - * Construct a validator for a single regular expression - * with the specified case sensitivity. - * - * @param regex The regular expression this validator will - * validate against - * @param caseSensitive when true matching is case - * sensitive, otherwise matching is case in-sensitive - */ - public RegexValidator(String regex, boolean caseSensitive) { - this(new String[]{regex}, caseSensitive); - } - - /** - * Construct a case sensitive validator that matches any one - * of the set of regular expressions. - * - * @param regexs The set of regular expressions this validator will - * validate against - */ - public RegexValidator(String[] regexs) { - this(regexs, true); - } - - /** - * Construct a validator that matches any one of the set of regular - * expressions with the specified case sensitivity. - * - * @param regexs The set of regular expressions this validator will - * validate against - * @param caseSensitive when true matching is case - * sensitive, otherwise matching is case in-sensitive - */ - public RegexValidator(String[] regexs, boolean caseSensitive) { - if (regexs == null || regexs.length == 0) { - throw new IllegalArgumentException("Regular expressions are missing"); - } - patterns = new Pattern[regexs.length]; - int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); - for (int i = 0; i < regexs.length; i++) { - if (regexs[i] == null || regexs[i].length() == 0) { - throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); - } - patterns[i] = Pattern.compile(regexs[i], flags); - } - } - - /** - * Validate a value against the set of regular expressions. - * - * @param value The value to validate. - * @return true if the value is valid - * otherwise false. - */ - public boolean isValid(String value) { - if (value == null) { - return false; - } - for (int i = 0; i < patterns.length; i++) { - if (patterns[i].matcher(value).matches()) { - return true; - } - } - return false; - } - - /** - * Validate a value against the set of regular expressions - * returning the array of matched groups. - * - * @param value The value to validate. - * @return String array of the groups matched if - * valid or null if invalid - */ - public String[] match(String value) { - if (value == null) { - return null; - } - for (int i = 0; i < patterns.length; i++) { - Matcher matcher = patterns[i].matcher(value); - if (matcher.matches()) { - int count = matcher.groupCount(); - String[] groups = new String[count]; - for (int j = 0; j < count; j++) { - groups[j] = matcher.group(j + 1); - } - return groups; - } - } - return null; - } - - - /** - * Validate a value against the set of regular expressions - * returning a String value of the aggregated groups. - * - * @param value The value to validate. - * @return Aggregated String value comprised of the - * groups matched if valid or null if invalid - */ - public String validate(String value) { - if (value == null) { - return null; - } - for (int i = 0; i < patterns.length; i++) { - Matcher matcher = patterns[i].matcher(value); - if (matcher.matches()) { - int count = matcher.groupCount(); - if (count == 1) { - return matcher.group(1); - } - StringBuilder buffer = new StringBuilder(); - for (int j = 0; j < count; j++) { - String component = matcher.group(j + 1); - if (component != null) { - buffer.append(component); - } - } - return buffer.toString(); - } - } - return null; - } - - /** - * Provide a String representation of this validator. - * - * @return A String representation of this validator - */ - @Override - public String toString() { - StringBuilder buffer = new StringBuilder(); - buffer.append("RegexValidator{"); - for (int i = 0; i < patterns.length; i++) { - if (i > 0) { - buffer.append(","); - } - buffer.append(patterns[i].pattern()); - } - buffer.append("}"); - return buffer.toString(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/format/TimeFormat.java b/src/main/java/com/networknt/schema/format/TimeFormat.java new file mode 100644 index 000000000..7b539b9ea --- /dev/null +++ b/src/main/java/com/networknt/schema/format/TimeFormat.java @@ -0,0 +1,108 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import java.text.ParsePosition; +import java.time.DateTimeException; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.TemporalAccessor; + +import com.networknt.schema.ExecutionContext; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME; +import static java.time.temporal.ChronoField.*; + +/** + * Format for time. + *

+ * Validates that a value conforms to the time specification in RFC 3339. + */ +public class TimeFormat implements Format { + // In 2023, time-zone offsets around the world extend from -12:00 to +14:00. + // However, RFC 3339 accepts -23:59 to +23:59. + private static final long MAX_OFFSET_MIN = 24 * 60 - 1; + private static final long MIN_OFFSET_MIN = -MAX_OFFSET_MIN; + + private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_TIME) + .appendOffset("+HH:MM", "Z") + .parseLenient() + .toFormatter(); + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + try { + if (null == value) return true; + + int pos = value.indexOf('Z'); + if (-1 != pos && pos != value.length() - 1) return false; + + TemporalAccessor accessor = formatter.parseUnresolved(value, new ParsePosition(0)); + if (null == accessor) return false; + + long offset = accessor.getLong(OFFSET_SECONDS) / 60; + if (MAX_OFFSET_MIN < offset || MIN_OFFSET_MIN > offset) return false; + + long hr = accessor.getLong(HOUR_OF_DAY); + long min = accessor.getLong(MINUTE_OF_HOUR); + long sec = accessor.getLong(SECOND_OF_MINUTE); + + boolean isStandardTimeRange = (sec <= 59 && min <= 59 && hr <= 23); + if (isStandardTimeRange) { + return true; + } + // Leap second check normalize to UTC to check if 23:59:60Z + hr = hr - offset / 60; + min = min - offset % 60; + + if (min < 0) { + --hr; + min += 60; + } + if (hr < 0) { + hr += 24; + } + return isSpecialCaseLeapSecond(sec, min, hr); + + } catch (DateTimeException e) { + return false; + } + } + + /** + * Determines if it is a valid leap second. + * + * See https://datatracker.ietf.org/doc/html/rfc3339#appendix-D + * + * @param sec second + * @param min minute + * @param hr hour + * @return true if it is a valid leap second + */ + private boolean isSpecialCaseLeapSecond(long sec, long min, long hr) { + return (sec == 60 && min == 59 && hr == 23); + } + + @Override + public String getName() { + return "time"; + } + + @Override + public String getMessageKey() { + return "format.time"; + } +} diff --git a/src/main/java/com/networknt/schema/format/UriFormat.java b/src/main/java/com/networknt/schema/format/UriFormat.java new file mode 100644 index 000000000..1b98c02d7 --- /dev/null +++ b/src/main/java/com/networknt/schema/format/UriFormat.java @@ -0,0 +1,37 @@ +package com.networknt.schema.format; + +import java.net.URI; + +/** + * Format for uri. + */ +public class UriFormat extends AbstractRFC3986Format { + @Override + protected boolean validate(URI uri) { + boolean result = uri.isAbsolute(); + if (result) { + // Java URI accepts non ASCII characters and this is not a valid in RFC3986 + result = uri.toString().codePoints().allMatch(ch -> ch < 0x7F); + if (result) { + String query = uri.getRawQuery(); + if (query != null) { + // [ and ] must be percent encoded + if (query.indexOf('[') != -1 || query.indexOf(']') != -1) { + return false; + } + } + } + } + return result; + } + + @Override + public String getName() { + return "uri"; + } + + @Override + public String getMessageKey() { + return "format.uri"; + } +} diff --git a/src/main/java/com/networknt/schema/format/UriReferenceFormat.java b/src/main/java/com/networknt/schema/format/UriReferenceFormat.java new file mode 100644 index 000000000..4cb5a203a --- /dev/null +++ b/src/main/java/com/networknt/schema/format/UriReferenceFormat.java @@ -0,0 +1,34 @@ +package com.networknt.schema.format; + +import java.net.URI; + +/** + * Format for uri-reference. + */ +public class UriReferenceFormat extends AbstractRFC3986Format { + @Override + protected boolean validate(URI uri) { + // Java URI accepts non ASCII characters and this is not a valid in RFC3986 + boolean result = uri.toString().codePoints().allMatch(ch -> ch < 0x7F); + if (result) { + String query = uri.getRawQuery(); + if (query != null) { + // [ and ] must be percent encoded + if (query.indexOf('[') != -1 || query.indexOf(']') != -1) { + return false; + } + } + } + return result; + } + + @Override + public String getName() { + return "uri-reference"; + } + + @Override + public String getMessageKey() { + return "format.uri-reference"; + } +} diff --git a/src/main/java/com/networknt/schema/i18n/DefaultMessageSource.java b/src/main/java/com/networknt/schema/i18n/DefaultMessageSource.java new file mode 100644 index 000000000..e62a73c5a --- /dev/null +++ b/src/main/java/com/networknt/schema/i18n/DefaultMessageSource.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +/** + * The default {@link MessageSource} singleton. + */ +public class DefaultMessageSource { + /** + * The bundle base name. + */ + public static final String BUNDLE_BASE_NAME = "jsv-messages"; + + /** + * The holder. + */ + public static class Holder { + private static final MessageSource INSTANCE = new ResourceBundleMessageSource(BUNDLE_BASE_NAME); + } + + /** + * Gets the default {@link MessageSource} using the jsv-messages bundle. + * + * @return the message source of the resource bundle + */ + public static MessageSource getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/i18n/Locales.java b/src/main/java/com/networknt/schema/i18n/Locales.java new file mode 100644 index 000000000..43f8fa8c7 --- /dev/null +++ b/src/main/java/com/networknt/schema/i18n/Locales.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Locale.FilteringMode; +import java.util.Locale.LanguageRange; +import java.util.stream.Collectors; + +/** + * Functions for working with Locales. + */ +public class Locales { + /** + * The list of locale resource bundles. + */ + public static final String[] SUPPORTED_LANGUAGE_TAGS = new String[] { "ar", "cs", "da", "de", "es", "fa", "fi", "fr", + "iw", "he", "hr", "hu", "it", "ja", "ko", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sv", "th", "tr", "uk", + "vi", "zh-CN", "zh-TW" }; + + /** + * The supported locales. + */ + public static final List SUPPORTED_LOCALES = of(SUPPORTED_LANGUAGE_TAGS); + + /** + * Gets the supported locales. + * + * @return the supported locales + */ + public static List getSupportedLocales() { + return SUPPORTED_LOCALES; + } + + /** + * Gets a list of {@link Locale} by language tags. + * + * @param languageTags for the locales + * @return the locales + */ + public static List of(String... languageTags) { + return Arrays.asList(languageTags).stream().map(Locale::forLanguageTag).collect(Collectors.toList()); + } + + /** + * Determine the best matching {@link Locale} with respect to the priority list. + * + * @param priorityList the language tag priority list + * @return the best matching locale + */ + public static Locale findSupported(String priorityList) { + return findSupported(priorityList, getSupportedLocales()); + } + + /** + * Determine the best matching {@link Locale} with respect to the priority list. + * + * @param priorityList the language tag priority list + * @param locales the supported locales + * @return the best matching locale + */ + public static Locale findSupported(String priorityList, Collection locales) { + return findSupported(LanguageRange.parse(priorityList), locales, FilteringMode.AUTOSELECT_FILTERING); + } + + /** + * Determine the best matching {@link Locale} with respect to the priority list. + * + * @param priorityList the language tag priority list + * @param locales the supported locales + * @param filteringMode the filtering mode + * @return the best matching locale + */ + public static Locale findSupported(List priorityList, Collection locales, + FilteringMode filteringMode) { + Locale result = Locale.lookup(priorityList, locales); + if (result != null) { + return result; + } + List matching = Locale.filter(priorityList, locales, filteringMode); + if (!matching.isEmpty()) { + return matching.get(0); + } + return Locale.ROOT; + } +} diff --git a/src/main/java/com/networknt/schema/i18n/MessageFormatter.java b/src/main/java/com/networknt/schema/i18n/MessageFormatter.java new file mode 100644 index 000000000..03e4a7905 --- /dev/null +++ b/src/main/java/com/networknt/schema/i18n/MessageFormatter.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +/** + * Formats messages with arguments. + */ +@FunctionalInterface +public interface MessageFormatter { + /** + * Formats a message with arguments. + * + * @param args the arguments + * @return the message + */ + String format(Object... args); +} diff --git a/src/main/java/com/networknt/schema/i18n/MessageSource.java b/src/main/java/com/networknt/schema/i18n/MessageSource.java new file mode 100644 index 000000000..aaeb8aaa0 --- /dev/null +++ b/src/main/java/com/networknt/schema/i18n/MessageSource.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +import java.util.Locale; +import java.util.function.Supplier; + +/** + * Resolves locale specific messages. + */ +@FunctionalInterface +public interface MessageSource { + /** + * Gets the message. + * + * @param key to look up the message + * @param defaultMessageSupplier the default message + * @param locale the locale to use + * @param args the message arguments + * @return the message + */ + String getMessage(String key, Supplier defaultMessageSupplier, Locale locale, Object... args); + + /** + * Gets the message. + * + * @param key to look up the message + * @param defaultMessage the default message + * @param locale the locale to use + * @param args the message arguments + * @return the message + */ + default String getMessage(String key, String defaultMessage, Locale locale, Object... args) { + return getMessage(key, defaultMessage::toString, locale, args); + } + + /** + * Gets the message. + * + * @param key to look up the message + * @param locale the locale to use + * @param args the message arguments + * @return the message + */ + default String getMessage(String key, Locale locale, Object... args) { + return getMessage(key, (Supplier) null, locale, args); + } +} diff --git a/src/main/java/com/networknt/schema/i18n/ResourceBundleMessageSource.java b/src/main/java/com/networknt/schema/i18n/ResourceBundleMessageSource.java new file mode 100644 index 000000000..df2879395 --- /dev/null +++ b/src/main/java/com/networknt/schema/i18n/ResourceBundleMessageSource.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Objects; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +/** + * {@link MessageSource} that retrieves messages from a {@link ResourceBundle}. + */ +public class ResourceBundleMessageSource implements MessageSource { + /** + * Resource Bundle Cache. baseName -> locale -> resourceBundle. + */ + private final Map> resourceBundleMap = new ConcurrentHashMap<>(); + + /** + * Message Cache. locale -> key -> message. + */ + private final Map> messageMap = new ConcurrentHashMap<>(); + + /** + * Message Format Cache. locale -> message -> messageFormat. + *

+ * Note that Message Format is not threadsafe. + */ + private final Map> messageFormatMap = new ConcurrentHashMap<>(); + + private final List baseNames; + + public ResourceBundleMessageSource(String... baseName) { + this.baseNames = Arrays.asList(baseName); + } + + @Override + public String getMessage(String key, Supplier defaultMessage, Locale locale, Object... arguments) { + String message = getMessageFromCache(locale, key); + if (message.isEmpty() && defaultMessage != null) { + message = defaultMessage.get(); + } + if (message.isEmpty()) { + // Fallback on message key + message = key; + } + if (arguments == null || arguments.length == 0) { + // When no arguments just return the message without formatting + return message; + } + MessageFormat messageFormat = getMessageFormat(locale, message); + synchronized (messageFormat) { + // Synchronized block on messageFormat as it is not threadsafe + return messageFormat.format(arguments, new StringBuffer(), null).toString(); + } + } + + protected MessageFormat getMessageFormat(Locale locale, String message) { + Map map = messageFormatMap.computeIfAbsent(locale, l -> new ConcurrentHashMap<>()); + return map.computeIfAbsent(message, m -> new MessageFormat(m, locale)); + } + + /** + * Gets the message from cache or the resource bundles. Returns an empty string + * if not found. + * + * @param locale the locale + * @param key the message key + * @return the message + */ + protected String getMessageFromCache(Locale locale, String key) { + Map map = messageMap.computeIfAbsent(locale, l -> new ConcurrentHashMap<>()); + return map.computeIfAbsent(key, k -> resolveMessage(locale, k)); + } + + /** + * Gets the message from the resource bundles. Returns an empty string if not + * found. + * + * @param locale the locale + * @param key the message key + * @return the message + */ + protected String resolveMessage(Locale locale, String key) { + Optional optionalPattern = this.baseNames.stream().map(baseName -> getResourceBundle(baseName, locale)) + .filter(Objects::nonNull).map(resourceBundle -> { + try { + return resourceBundle.getString(key); + } catch (MissingResourceException e) { + return null; + } + }).filter(Objects::nonNull).findFirst(); + return optionalPattern.orElse(""); + } + + protected Map getResourceBundle(String baseName) { + return resourceBundleMap.computeIfAbsent(baseName, key -> new ConcurrentHashMap<>()); + } + + protected ResourceBundle getResourceBundle(String baseName, Locale locale) { + return getResourceBundle(baseName).computeIfAbsent(locale, key -> { + try { + return ResourceBundle.getBundle(baseName, key); + } catch (MissingResourceException e) { + return null; + } + }); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/AbstractKeyword.java b/src/main/java/com/networknt/schema/keyword/AbstractKeyword.java new file mode 100644 index 000000000..3683b2123 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AbstractKeyword.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.Objects; + +/** + * Abstract keyword. + */ +public abstract class AbstractKeyword implements Keyword { + private final String value; + + /** + * Create abstract keyword. + * + * @param value the keyword + */ + public AbstractKeyword(String value) { + this.value = value; + } + + /** + * Gets the keyword. + * + * @return the keyword + */ + public String getValue() { + return this.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractKeyword other = (AbstractKeyword) obj; + return Objects.equals(value, other.value); + } + + @Override + public String toString() { + return getValue(); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java b/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java new file mode 100644 index 000000000..70d323bf3 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.Iterator; +import java.util.function.Consumer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.annotation.Annotation; + +/** + * Abstract {@link KeywordValidator}. + */ +public abstract class AbstractKeywordValidator implements KeywordValidator { + private final String keyword; + protected final JsonNode schemaNode; + protected final SchemaLocation schemaLocation; + + /** + * Constructor. + * @param keyword the keyword + * @param schemaNode the schema node + * @param schemaLocation the schema location + */ + public AbstractKeywordValidator(String keyword, JsonNode schemaNode, SchemaLocation schemaLocation) { + this.keyword = keyword; + this.schemaNode = schemaNode; + this.schemaLocation = schemaLocation; + } + + /** + * Constructor. + * @param keyword the keyword + * @param schemaNode the schema node + * @param schemaLocation the schema location + */ + public AbstractKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation schemaLocation) { + this(keyword.getValue(), schemaNode, schemaLocation); + } + + @Override + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + @Override + public String getKeyword() { + return keyword; + } + + /** + * The schema node used to create the validator. + * + * @return the schema node + */ + public JsonNode getSchemaNode() { + return this.schemaNode; + } + + @Override + public String toString() { + return getKeyword(); + } + + /** + * Determine if annotations should be reported. + * + * @param executionContext the execution context + * @return true if annotations should be reported + */ + protected boolean collectAnnotations(ExecutionContext executionContext) { + return collectAnnotations(executionContext, getKeyword()); + } + + /** + * Determine if annotations should be reported. + * + * @param executionContext the execution context + * @param keyword the keyword + * @return true if annotations should be reported + */ + protected boolean collectAnnotations(ExecutionContext executionContext, String keyword) { + return executionContext.getExecutionConfig().isAnnotationCollectionEnabled() + && executionContext.getExecutionConfig().getAnnotationCollectionFilter().test(keyword); + } + + /** + * Puts an annotation. + * + * @param executionContext the execution context + * @param customizer to customize the annotation + */ + protected void putAnnotation(ExecutionContext executionContext, Consumer customizer) { + Annotation.Builder builder = Annotation.builder().evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.schemaLocation).keyword(getKeyword()); + customizer.accept(builder); + executionContext.getAnnotations().put(builder.build()); + } + + + + /** + * Determines if the keyword exists adjacent in the evaluation path. + *

+ * This does not check if the keyword exists in the current meta schema as this + * can be a cross-draft case where the properties keyword is in a Draft 7 schema + * and the unevaluatedProperties keyword is in an outer Draft 2020-12 schema. + *

+ * The fact that the validator exists in the evaluation path implies that the + * keyword was valid in whatever meta schema for that schema it was created for. + * + * @param keyword the keyword to check + * @return true if found + */ + protected boolean hasAdjacentKeywordInEvaluationPath(ExecutionContext executionContext, String keyword) { + Iterator evaluationSchemaPathIterator = executionContext.getEvaluationSchemaPath().descendingIterator(); + Iterator evaluationSchemaIterator = executionContext.getEvaluationSchema().descendingIterator(); + boolean stop = false; + + // Skip the first as this is the path pointing to the current keyword eg. properties eg /$ref/properties + // What is needed is the evaluationPath pointing to the current evaluationSchema eg /$ref + if (evaluationSchemaPathIterator.hasNext()) { + evaluationSchemaPathIterator.next(); + } + + while (evaluationSchemaIterator.hasNext()) { + Schema schema = evaluationSchemaIterator.next(); + boolean hasKeyword = schema.getSchemaNode().has(keyword); + if (hasKeyword) { + return true; + } + if (stop) { + return false; + } + if (evaluationSchemaPathIterator.hasNext()) { + Object evaluationPath = evaluationSchemaPathIterator.next(); + if ("properties".equals(evaluationPath) || "items".equals(evaluationPath)) { + // If there is a change in instance location then after the next schema + // stop + stop = true; + } + } + } + return false; + } + + protected boolean hasUnevaluatedItemsInEvaluationPath(ExecutionContext executionContext) { + if (executionContext.isUnevaluatedItemsPresent() + && hasAdjacentKeywordInEvaluationPath(executionContext, "unevaluatedItems")) { + return true; + } + return false; + } + + protected boolean hasUnevaluatedPropertiesInEvaluationPath(ExecutionContext executionContext) { + if (executionContext.isUnevaluatedPropertiesPresent() + && hasAdjacentKeywordInEvaluationPath(executionContext, "unevaluatedProperties")) { + return true; + } + return false; + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java new file mode 100644 index 000000000..37a53d749 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.regex.RegularExpression; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +/** + * {@link KeywordValidator} for additionalProperties. + */ +public class AdditionalPropertiesValidator extends BaseKeywordValidator { + private final boolean allowAdditionalProperties; + private final Schema additionalPropertiesSchema; + private final Set allowedProperties; + private final List patternProperties; + + public AdditionalPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, + SchemaContext schemaContext) { + super(KeywordType.ADDITIONAL_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.isBoolean()) { + allowAdditionalProperties = schemaNode.booleanValue(); + additionalPropertiesSchema = null; + } else if (schemaNode.isObject()) { + allowAdditionalProperties = true; + additionalPropertiesSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } else { + allowAdditionalProperties = false; + additionalPropertiesSchema = null; + } + + JsonNode propertiesNode = parentSchema.getSchemaNode().get(PropertiesValidator.PROPERTY); + if (propertiesNode != null) { + allowedProperties = new HashSet<>(); + for (Iterator it = propertiesNode.fieldNames(); it.hasNext(); ) { + allowedProperties.add(it.next()); + } + } else { + allowedProperties = Collections.emptySet(); + } + + JsonNode patternPropertiesNode = parentSchema.getSchemaNode().get(PatternPropertiesValidator.PROPERTY); + if (patternPropertiesNode != null) { + this.patternProperties = new ArrayList<>(patternPropertiesNode.size()); + for (Iterator it = patternPropertiesNode.fieldNames(); it.hasNext(); ) { + patternProperties.add(RegularExpression.compile(it.next(), schemaContext)); + } + } else { + this.patternProperties = Collections.emptyList(); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + if (!node.isObject()) { + // ignore no object + return; + } + + Set matchedInstancePropertyNames = null; + + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) + || collectAnnotations(executionContext); + // if allowAdditionalProperties is true, add all the properties as evaluated. + if (allowAdditionalProperties && collectAnnotations) { + for (Iterator it = node.fieldNames(); it.hasNext();) { + if (matchedInstancePropertyNames == null) { + matchedInstancePropertyNames = new LinkedHashSet<>(); + } + String fieldName = it.next(); + matchedInstancePropertyNames.add(fieldName); + } + } + + for (Iterator> it = node.fields(); it.hasNext(); ) { + Entry entry = it.next(); + String pname = entry.getKey(); + // skip the context items + if (pname.startsWith("#")) { + continue; + } + if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) { + if (!allowAdditionalProperties) { + executionContext.addError(error().instanceNode(node).property(pname) + .instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .locale(executionContext.getExecutionConfig().getLocale()) + .arguments(pname).build()); + } else { + if (additionalPropertiesSchema != null) { + if (!walk) { + additionalPropertiesSchema.validate(executionContext, entry.getValue(), rootNode, + instanceLocation.append(pname)); + } else { + additionalPropertiesSchema.walk(executionContext, entry.getValue(), rootNode, + instanceLocation.append(pname), true); + } + } + } + } + } + if (collectAnnotations) { + executionContext.getAnnotations().put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation).keyword(getKeyword()) + .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames : Collections.emptySet()) + .build()); + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + if (shouldValidateSchema && node != null) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + + if (node == null || !node.isObject()) { + // ignore no object + return; + } + + // Else continue walking. + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + // skip the context items + if (pname.startsWith("#")) { + continue; + } + if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) { + if (allowAdditionalProperties) { + if (additionalPropertiesSchema != null) { + additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode, + instanceLocation.append(pname), shouldValidateSchema); + } + } + } + } + } + + private boolean handledByPatternProperties(String pname) { + for (RegularExpression pattern : this.patternProperties) { + if (pattern.matches(pname)) { + return true; + } + } + return false; + } + + @Override + public void preloadSchema() { + if(additionalPropertiesSchema != null) { + additionalPropertiesSchema.initializeValidators(); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/AllOfValidator.java b/src/main/java/com/networknt/schema/keyword/AllOfValidator.java new file mode 100644 index 000000000..c0d871d2d --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AllOfValidator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * {@link KeywordValidator} for allOf. + */ +public class AllOfValidator extends BaseKeywordValidator { + private final List schemas; + + public AllOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ALL_OF, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isArray()) { + JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); + throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) + .messageKey("type").arguments(nodeType.toString(), "array").build()); + } + int size = schemaNode.size(); + this.schemas = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), + schemaNode.get(i), parentSchema)); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + int schemaIndex = 0; + for (Schema schema : this.schemas) { + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, + boolean shouldValidateSchema) { + if (shouldValidateSchema && node != null) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + int schemaIndex = 0; + for (Schema schema : this.schemas) { + // Walk through the schema + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + } + } + + @Override + public void preloadSchema() { + preloadSchemas(this.schemas); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java b/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java new file mode 100644 index 000000000..6c961b973 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * Used for Keywords that have no validation aspect, but are part of the metaschema, where annotations may need to be collected. + */ +public class AnnotationKeyword extends AbstractKeyword { + + private static final class Validator extends AbstractKeywordValidator { + public Validator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext, Keyword keyword) { + super(keyword, schemaNode, schemaLocation); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (collectAnnotations(executionContext)) { + Object value = getAnnotationValue(getSchemaNode()); + if (value != null) { + putAnnotation(executionContext, + annotation -> annotation.instanceLocation(instanceLocation).value(value)); + } + } + } + + private Object getAnnotationValue(JsonNode schemaNode) { + if (schemaNode.isTextual()) { + return schemaNode.textValue(); + } else if (schemaNode.isNumber()) { + return schemaNode.numberValue(); + } else if (schemaNode.isObject()) { + return schemaNode; + } + return null; + } + } + + public AnnotationKeyword(String keyword) { + super(keyword); + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + return new Validator(schemaLocation, schemaNode, parentSchema, schemaContext, this); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java b/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java new file mode 100644 index 000000000..d2b722ffc --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * {@link KeywordValidator} for anyOf. + */ +public class AnyOfValidator extends BaseKeywordValidator { + private final List schemas; + + public AnyOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ANY_OF, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isArray()) { + JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); + throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) + .messageKey("type").arguments(nodeType.toString(), "array").build()); + } + int size = schemaNode.size(); + this.schemas = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), + schemaNode.get(i), parentSchema)); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + int numberOfValidSubSchemas = 0; + List existingErrors = executionContext.getErrors(); + List allErrors = null; // Keeps track of all the errors for reporting if in the end none of the schemas + // match + List discriminatorErrors = null; // The errors from the sub schema that match the discriminator + List subSchemaErrors = new ArrayList<>(); // Temporary errors from each sub schema execution + executionContext.setErrors(subSchemaErrors); + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + try { + int schemaIndex = 0; + executionContext.setFailFast(false); + for (Schema schema : this.schemas) { + subSchemaErrors.clear(); // Reuse and clear for each run + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + + // check if any validation errors have occurred + if (subSchemaErrors.isEmpty()) { + // we found a valid subschema, so increase counter + numberOfValidSubSchemas++; + } + + if (subSchemaErrors.isEmpty() && (!this.schemaContext.isDiscriminatorKeywordEnabled()) + && canShortCircuit(executionContext)) { + // Successful so return only the existing errors, ie. no new errors + executionContext.setErrors(existingErrors); + return; + } else if (this.schemaContext.isDiscriminatorKeywordEnabled()) { + boolean discriminatorMatchFound = false; + DiscriminatorState discriminator = executionContext.getDiscriminatorMapping().get(instanceLocation); + JsonNode refNode = schema.getSchemaNode().get("$ref"); + if (discriminator != null && refNode != null) { + discriminatorMatchFound = discriminator.matches(refNode.asText()); + } + if (discriminatorMatchFound) { + /* + * Note that discriminator cannot change the outcome of the evaluation but can + * be used to filter off any additional messages + */ + if (!subSchemaErrors.isEmpty()) { + /* + * This means that the discriminated value has errors and doesn't match so these + * errors are the only ones that will be reported *IF* there are no other + * schemas that successfully validate to meet the requirement of anyOf. + * + * If there are any successful schemas as per anyOf, all these errors will be + * discarded. + */ + discriminatorErrors = new ArrayList<>(subSchemaErrors); + allErrors = null; // This is no longer needed + } + } + } + /* + * This adds all the errors for this schema to the list that contains all the + * errors for later reporting. + * + * There's no need to add these if there was a discriminator match with errors + * as only the discriminator errors will be reported if all the schemas fail. + */ + if (!subSchemaErrors.isEmpty() && discriminatorErrors == null) { + if (allErrors == null) { + allErrors = new ArrayList<>(); + } + allErrors.addAll(subSchemaErrors); + } + } + } finally { + // Restore flag + executionContext.setFailFast(failFast); + } + + if (this.schemaContext.isDiscriminatorKeywordEnabled()) { + /* + * The only case where the discriminator can change the outcome of the result is + * if the discriminator value does not match an implicit or explicit mapping + */ + /* + * If the discriminator value does not match an implicit or explicit mapping, no + * schema can be determined and validation SHOULD fail. Mapping keys MUST be + * string values, but tooling MAY convert response values to strings for + * comparison. + * + * https://spec.openapis.org/oas/v3.1.2#examples-0 + */ + DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation); + if (state != null && !state.hasMatchedSchema() && state.hasDiscriminatingValue()) { + // The check for state.hasDiscriminatingValue is due to issue 988 + // Note that this is related to the DiscriminatorValidator by default not + // generating an assertion + // if the discriminatingValue is not set in the payload + existingErrors.add(error().keyword("discriminator").instanceNode(node) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .messageKey("discriminator.anyOf.no_match_found").arguments(state.getDiscriminatingValue()) + .build()); + } + } + + if (numberOfValidSubSchemas >= 1) { + // Successful so return only the existing errors, ie. no new errors + executionContext.setErrors(existingErrors); + } else { + if (discriminatorErrors != null) { + // If errors are present matching the discriminator, only these errors should be + // reported + existingErrors.addAll(discriminatorErrors); + } else if (allErrors != null) { + // As the anyOf has failed, report all the errors + existingErrors.addAll(allErrors); + } + executionContext.setErrors(existingErrors); + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, + boolean shouldValidateSchema) { + if (shouldValidateSchema && node != null) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + int schemaIndex = 0; + for (Schema schema : this.schemas) { + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + } + } + + /** + * If annotation collection is enabled cannot short circuit. + * + * @see anyOf + * @param executionContext the execution context + * @return true if can short circuit + */ + protected boolean canShortCircuit(ExecutionContext executionContext) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext)) { + return false; + } + if (hasUnevaluatedPropertiesInEvaluationPath(executionContext)) { + return false; + } + return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled(); + } + + @Override + public void preloadSchema() { + preloadSchemas(this.schemas); + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java b/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java new file mode 100644 index 000000000..ef13efb08 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ErrorMessages; +import com.networknt.schema.Schema; +import com.networknt.schema.MessageSourceError; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; + +import java.util.Collection; +import java.util.Map; + +/** + * Base {@link KeywordValidator}. + */ +public abstract class BaseKeywordValidator extends AbstractKeywordValidator { + protected final SchemaContext schemaContext; + + protected final Schema parentSchema; + protected final Map errorMessage; + + public BaseKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation schemaLocation, + Schema parentSchema, SchemaContext schemaContext) { + super(keyword, schemaNode, schemaLocation); + this.schemaContext = schemaContext; + + this.parentSchema = parentSchema; + if (keyword != null && parentSchema != null && schemaContext.getSchemaRegistryConfig().getErrorMessageKeyword() != null) { + this.errorMessage = ErrorMessages.getErrorMessage(parentSchema, + schemaContext.getSchemaRegistryConfig().getErrorMessageKeyword(), keyword.getValue()); + } else { + this.errorMessage = null; + } + } + + /** + * Constructor to create a copy using fields. + * @param keyword the keyword + * @param schemaNode the schema node + * @param schemaLocation the schema location + * @param schemaContext the schema context + * @param parentSchema the parent schema + * @param errorMessage the error message + */ + protected BaseKeywordValidator( + Keyword keyword, + JsonNode schemaNode, + SchemaLocation schemaLocation, + SchemaContext schemaContext, + Schema parentSchema, + Map errorMessage) { + super(keyword, schemaNode, schemaLocation); + this.schemaContext = schemaContext; + + this.parentSchema = parentSchema; + this.errorMessage = errorMessage; + } + + /** + * Gets the parent schema. + *

+ * This is the lexical parent schema. + * + * @return the parent schema + */ + public Schema getParentSchema() { + return this.parentSchema; + } + + protected String getNodeFieldType() { + JsonNode typeField = this.getParentSchema().getSchemaNode().get("type"); + if (typeField != null) { + return typeField.asText(); + } + return null; + } + + protected void preloadSchemas(final Collection schemas) { + for (final Schema schema : schemas) { + schema.initializeValidators(); + } + } + + protected MessageSourceError.Builder error() { + return MessageSourceError + .builder(this.schemaContext.getSchemaRegistryConfig().getMessageSource(), this.errorMessage) + .schemaNode(this.schemaNode).schemaLocation(this.schemaLocation) + .keyword(this.getKeyword()).messageKey(this.getKeyword()); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ConstValidator.java b/src/main/java/com/networknt/schema/keyword/ConstValidator.java new file mode 100644 index 000000000..e1c9f56bc --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ConstValidator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for const. + */ +public class ConstValidator extends BaseKeywordValidator implements KeywordValidator { + public ConstValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.CONST, schemaNode, schemaLocation, parentSchema, schemaContext); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (schemaNode.isNumber() && node.isNumber()) { + if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(schemaNode.asText(), node.asText()) + .build()); + } + } else if (!schemaNode.equals(node)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText(), node.asText()).build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ContainsValidator.java b/src/main/java/com/networknt/schema/keyword/ContainsValidator.java new file mode 100644 index 000000000..801f776d5 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ContainsValidator.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; + +import static com.networknt.schema.SpecificationVersionRange.MIN_DRAFT_2019_09; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +/** + * {@link KeywordValidator} for contains. + */ +public class ContainsValidator extends BaseKeywordValidator { + private static final String CONTAINS_MAX = "contains.max"; + private static final String CONTAINS_MIN = "contains.min"; + + private final Schema schema; + private final boolean isMinV201909; + + private final Integer min; + private final Integer max; + + public ContainsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext); + + // Draft 6 added the contains keyword but maxContains and minContains first + // appeared in Draft 2019-09 so the semantics of the validation changes + // slightly. + this.isMinV201909 = MIN_DRAFT_2019_09.getVersions().contains(this.schemaContext.getDialect().getSpecificationVersion()); + + Integer currentMax = null; + Integer currentMin = null; + if (schemaNode.isObject() || schemaNode.isBoolean()) { + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + JsonNode parentSchemaNode = parentSchema.getSchemaNode(); + Optional maxNode = Optional + .ofNullable(parentSchemaNode.get(KeywordType.MAX_CONTAINS.getValue())) + .filter(JsonNode::canConvertToExactIntegral); + if (maxNode.isPresent()) { + currentMax = maxNode.get().intValue(); + } + + Optional minNode = Optional + .ofNullable(parentSchemaNode.get(KeywordType.MIN_CONTAINS.getValue())) + .filter(JsonNode::canConvertToExactIntegral); + if (minNode.isPresent()) { + currentMin = minNode.get().intValue(); + } + } else { + this.schema = null; + } + this.max = currentMax; + this.min = currentMin; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + // ignores non-arrays + int actual = 0, i = 0; + List indexes = new ArrayList<>(); // for the annotation + if (null != this.schema && node.isArray()) { + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + List existingErrors = executionContext.getErrors(); + try { + executionContext.setFailFast(false); + List test = new ArrayList<>(); + executionContext.setErrors(test); + for (JsonNode n : node) { + NodePath path = instanceLocation.append(i); + this.schema.validate(executionContext, n, rootNode, path); + if (test.isEmpty()) { + ++actual; + indexes.add(i); + } else { + test.clear(); + } + ++i; + } + } finally { + // Restore flag + executionContext.setFailFast(failFast); + executionContext.setErrors(existingErrors); + } + int m = 1; // default to 1 if "min" not specified + if (this.min != null) { + m = this.min; + } + if (actual < m) { + boundsViolated(executionContext, isMinV201909 ? KeywordType.MIN_CONTAINS : KeywordType.CONTAINS, + executionContext.getExecutionConfig().getLocale(), + node, instanceLocation, m); + } + + if (this.max != null && actual > this.max) { + boundsViolated(executionContext, isMinV201909 ? KeywordType.MAX_CONTAINS : KeywordType.CONTAINS, + executionContext.getExecutionConfig().getLocale(), + node, instanceLocation, this.max); + } + } + + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); + if (this.schema != null) { + // This keyword produces an annotation value which is an array of the indexes to + // which this keyword validates successfully when applying its subschema, in + // ascending order. The value MAY be a boolean "true" if the subschema validates + // successfully when applied to every index of the instance. The annotation MUST + // be present if the instance array to which this keyword's schema applies is + // empty. + + if (collectAnnotations || collectAnnotations(executionContext, "contains")) { + if (actual == i) { + // evaluated all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword("contains").value(true).build()); + } else { + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword("contains").value(indexes).build()); + } + } + + // Add minContains and maxContains annotations + if (this.min != null) { + String minContainsKeyword = "minContains"; + if (collectAnnotations || collectAnnotations(executionContext, minContainsKeyword)) { + // Omitted keywords MUST NOT produce annotation results. However, as described + // in the section for contains, the absence of this keyword's annotation causes + // contains to assume a minimum value of 1. + executionContext.evaluationPathAddLast(minContainsKeyword); + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.schemaLocation.append(minContainsKeyword)) + .keyword(minContainsKeyword).value(this.min).build()); + executionContext.evaluationPathRemoveLast(); + } + } + + if (this.max != null) { + String maxContainsKeyword = "maxContains"; + if (collectAnnotations || collectAnnotations(executionContext, maxContainsKeyword)) { + executionContext.evaluationPathAddLast(maxContainsKeyword); + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.schemaLocation.append(maxContainsKeyword)) + .keyword(maxContainsKeyword).value(this.max).build()); + executionContext.evaluationPathRemoveLast(); + } + } + } + } + + @Override + public void preloadSchema() { + Optional.ofNullable(this.schema).ifPresent(Schema::initializeValidators); + } + + private void boundsViolated(ExecutionContext executionContext, KeywordType validatorTypeCode, Locale locale, + JsonNode instanceNode, NodePath instanceLocation, int bounds) { + String messageKey = "contains"; + if (KeywordType.MIN_CONTAINS.equals(validatorTypeCode)) { + messageKey = CONTAINS_MIN; + } else if (KeywordType.MAX_CONTAINS.equals(validatorTypeCode)) { + messageKey = CONTAINS_MAX; + } + executionContext.addError(error().instanceNode(instanceNode).instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()).messageKey(messageKey) + .locale(locale).arguments(String.valueOf(bounds), this.schema.getSchemaNode().toString()) + .keyword(validatorTypeCode.getValue()).build()); + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java b/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java new file mode 100644 index 000000000..758ffc420 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; +import com.networknt.schema.SchemaContext; + +import java.util.Base64; + +/** + * {@link KeywordValidator} for contentEncoding. + *

+ * Note that since 2019-09 this keyword only generates annotations and not + * errors. + */ +public class ContentEncodingValidator extends BaseKeywordValidator { + private final String contentEncoding; + + /** + * Constructor. + * + * @param schemaLocation the schema location + * @param schemaNode the schema node + * @param parentSchema the parent schema + * @param schemaContext the schema context + */ + public ContentEncodingValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.CONTENT_ENCODING, schemaNode, schemaLocation, parentSchema, schemaContext); + this.contentEncoding = schemaNode.textValue(); + } + + private boolean matches(String value) { + if ("base64".equals(this.contentEncoding)) { + try { + Base64.getDecoder().decode(value); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } else { + return true; + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + // Ignore non-strings + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + return; + } + + if (collectAnnotations(executionContext)) { + putAnnotation(executionContext, + annotation -> annotation.instanceLocation(instanceLocation).value(this.contentEncoding)); + } + + if (!matches(node.asText())) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.contentEncoding) + .build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java b/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java new file mode 100644 index 000000000..98e9aae71 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * {@link KeywordValidator} for contentMediaType. + *

+ * Note that since 2019-09 this keyword only generates annotations and not errors. + */ +public class ContentMediaTypeValidator extends BaseKeywordValidator { + private static final String PATTERN_STRING = "(application|audio|font|example|image|message|model|multipart|text|video|x-(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+))/([0-9A-Za-z!#$%&'*+.^_`|~-]+)((?:[ \t]*;[ \t]*[0-9A-Za-z!#$%&'*+.^_`|~-]+=(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+|\"(?:[^\"\\\\]|\\.)*\"))*)"; + private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING); + private final String contentMediaType; + + /** + * Constructor. + * + * @param schemaLocation the schema location + * @param schemaNode the schema node + * @param parentSchema the parent schema + * @param schemaContext the schema context + */ + public ContentMediaTypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.CONTENT_MEDIA_TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); + this.contentMediaType = schemaNode.textValue(); + } + + private boolean matches(String value) { + if ("application/json".equals(this.contentMediaType)) { + // Validate content + JsonNode node = this.parentSchema.getSchemaNode().get("contentEncoding"); + String encoding = null; + if (node != null && node.isTextual()) { + encoding = node.asText(); + } + String data = value; + if ("base64".equals(encoding)) { + try { + data = new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); + } catch(IllegalArgumentException e) { + return true; // The contentEncoding keyword will report the failure + } + } + // Validate the json + try { + JsonMapperFactory.getInstance().readTree(data); + } catch (JsonProcessingException e) { + return false; + } + return true; + } + else if (!PATTERN.matcher(this.contentMediaType).matches()) { + return false; + } else { + // validate data + } + return true; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + // Ignore non-strings + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + return; + } + + if (collectAnnotations(executionContext)) { + putAnnotation(executionContext, + annotation -> annotation.instanceLocation(instanceLocation).value(this.contentMediaType)); + } + + if (!matches(node.asText())) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.contentMediaType) + .build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java b/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java new file mode 100644 index 000000000..af18b6293 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for dependencies. + */ +public class DependenciesValidator extends BaseKeywordValidator implements KeywordValidator { + private final Map> propertyDeps = new HashMap<>(); + private final Map schemaDeps = new HashMap<>(); + + /** + * Constructor. + * + * @param schemaLocation the schema location + * @param schemaNode the schema node + * @param parentSchema the parent schema + * @param schemaContext the schema context + */ + public DependenciesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + + super(KeywordType.DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext); + + for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + JsonNode pvalue = schemaNode.get(pname); + if (pvalue.isArray()) { + List depsProps = propertyDeps.get(pname); + if (depsProps == null) { + depsProps = new ArrayList<>(); + propertyDeps.put(pname, depsProps); + } + for (int i = 0; i < pvalue.size(); i++) { + depsProps.add(pvalue.get(i).asText()); + } + } else if (pvalue.isObject() || pvalue.isBoolean()) { + schemaDeps.put(pname, schemaContext.newSchema(schemaLocation.append(pname), + pvalue, parentSchema)); + } + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + List deps = propertyDeps.get(pname); + if (deps != null && !deps.isEmpty()) { + for (String field : deps) { + if (node.get(field) == null) { + executionContext.addError(error().instanceNode(node).property(pname).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(propertyDeps.toString()).build()); + } + } + } + Schema schema = schemaDeps.get(pname); + if (schema != null) { + executionContext.evaluationPathAddLast(pname); + try { + schema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + } + + @Override + public void preloadSchema() { + preloadSchemas(schemaDeps.values()); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/DependentRequired.java b/src/main/java/com/networknt/schema/keyword/DependentRequired.java new file mode 100644 index 000000000..88384a368 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DependentRequired.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for dependentRequired. + */ +public class DependentRequired extends BaseKeywordValidator implements KeywordValidator { + private final Map> propertyDependencies = new HashMap<>(); + + public DependentRequired(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + + super(KeywordType.DEPENDENT_REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext); + + for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + JsonNode pvalue = schemaNode.get(pname); + if (pvalue.isArray()) { + List dependencies = propertyDependencies.computeIfAbsent(pname, k -> new ArrayList<>()); + + for (int i = 0; i < pvalue.size(); i++) { + dependencies.add(pvalue.get(i).asText()); + } + } + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + List dependencies = propertyDependencies.get(pname); + if (dependencies != null && !dependencies.isEmpty()) { + for (String field : dependencies) { + if (node.get(field) == null) { + executionContext.addError(error().instanceNode(node).property(pname).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(field, pname) + .build()); + } + } + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/DependentSchemas.java b/src/main/java/com/networknt/schema/keyword/DependentSchemas.java new file mode 100644 index 000000000..8f87bdc9f --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DependentSchemas.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for dependentSchemas. + */ +public class DependentSchemas extends BaseKeywordValidator { + private final Map schemaDependencies = new HashMap<>(); + + public DependentSchemas(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.DEPENDENT_SCHEMAS, schemaNode, schemaLocation, parentSchema, schemaContext); + for (Iterator it = schemaNode.fieldNames(); it.hasNext();) { + String pname = it.next(); + JsonNode pvalue = schemaNode.get(pname); + if (pvalue.isObject() || pvalue.isBoolean()) { + this.schemaDependencies.put(pname, schemaContext.newSchema(schemaLocation.append(pname), + pvalue, parentSchema)); + } + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + String pname = it.next(); + Schema schema = this.schemaDependencies.get(pname); + if (schema != null) { + executionContext.evaluationPathAddLast(pname); + try { + if(!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + } + + @Override + public void preloadSchema() { + preloadSchemas(this.schemaDependencies.values()); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + if (shouldValidateSchema) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + for (Schema schema : this.schemaDependencies.values()) { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/DisallowUnknownKeywordFactory.java b/src/main/java/com/networknt/schema/keyword/DisallowUnknownKeywordFactory.java new file mode 100644 index 000000000..30afa000b --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DisallowUnknownKeywordFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.networknt.schema.Error; +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.SchemaContext; + +/** + * Unknown keyword factory that rejects unknown keywords. + */ +public class DisallowUnknownKeywordFactory implements KeywordFactory { + private static final Logger logger = LoggerFactory.getLogger(DisallowUnknownKeywordFactory.class); + + @Override + public Keyword getKeyword(String value, SchemaContext schemaContext) { + logger.error("Keyword '{}' is unknown and must be configured on the meta-schema or vocabulary", value); + throw new InvalidSchemaException(Error.builder() + .message("Keyword ''{0}'' is unknown and must be configured on the meta-schema or vocabulary") + .arguments(value).build()); + } + + private static class Holder { + private static final DisallowUnknownKeywordFactory INSTANCE = new DisallowUnknownKeywordFactory(); + } + + /** + * Gets the instance of {@link DisallowUnknownKeywordFactory}. + * + * @return the keyword factory + */ + public static DisallowUnknownKeywordFactory getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/DiscriminatorState.java b/src/main/java/com/networknt/schema/keyword/DiscriminatorState.java new file mode 100644 index 000000000..a92095c3b --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DiscriminatorState.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +/** + * Discriminator state for an instance location. + */ +public class DiscriminatorState { + /** + * The propertyName field defined in the discriminator keyword schema. + */ + private String propertyName; + /** + * The discriminating value from the payload matching the property name. + */ + private String discriminatingValue; + /** + * The mapped schema from the mapping or the discriminating value if there is no + * mapping. + */ + private String mappedSchema = null; + + /** + * Whether the mapped schema is explicitly mapped using mapping or is the + * discriminating value. + */ + private boolean explicitMapping = false; + + /** + * The matched schema for the instance. + */ + private String matchedSchema; + + /** + * Determines if there is a match of the mapped schema to the ref and update the + * matched schema if there is a match. + * + * @param refSchema the $ref value + * @return true if there is a match + */ + public boolean matches(String refSchema) { + boolean discriminatorMatchFound = false; + String mappedSchema = getMappedSchema(); + if (mappedSchema != null) { + if (isExplicitMapping() && refSchema.equals(mappedSchema)) { + // Explicit matching + discriminatorMatchFound = true; + setMatchedSchema(refSchema); + } else if (!isExplicitMapping() && isImplicitMatch(refSchema, mappedSchema)) { + // Implicit matching + discriminatorMatchFound = true; + setMatchedSchema(refSchema); + } + } + return discriminatorMatchFound; + } + + /** + * Gets the property name defined in the discriminator keyword schema. + * + * @return + */ + public String getPropertyName() { + return propertyName; + } + + /** + * Sets the property name defined in the discriminator keyword schema. + * + * @param propertyName the property name + */ + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + /** + * Gets the discriminating value, which is the value in the payload + * corresponding with the property name. + * + * @return the discriminating value + */ + public String getDiscriminatingValue() { + return discriminatingValue; + } + + /** + * Sets the discriminating value, which is the value in the payload + * corresponding with the property name. + * + * @param discriminatingValue + */ + public void setDiscriminatingValue(String discriminatingValue) { + this.discriminatingValue = discriminatingValue; + } + + /** + * Returns true if the discriminating value is found in the payload + * corresponding to the property name. + * + * @return true if the discriminating value is found in the payload + * corresponding to the property name + */ + public boolean hasDiscriminatingValue() { + return this.discriminatingValue != null; + } + + /** + * Gets the mapped schema which is the discriminating value mapped to the schema + * name or uri. + * + * @return the mapped schema + */ + public String getMappedSchema() { + return mappedSchema; + } + + /** + * Sets the mapped schema which is the discriminating value mapped to the schema + * name or uri. + * + * @param mappedSchema the mapped schema + */ + public void setMappedSchema(String mappedSchema) { + this.mappedSchema = mappedSchema; + } + + /** + * Gets whether the mapping is explicit using the mappings on the discriminator + * keyword. + * + * @return true if the mapping is explicitly mapped using mappings + */ + public boolean isExplicitMapping() { + return explicitMapping; + } + + /** + * Sets whether the mapping is explicit using the mappings on the discriminator + * keyword. + * + * @param explicitMapping true if explicitly mapped using mappings + */ + public void setExplicitMapping(boolean explicitMapping) { + this.explicitMapping = explicitMapping; + } + + /** + * Sets the matched schema $ref. + * + * @param matchedSchema the matched schema $ref + */ + public void setMatchedSchema(String matchedSchema) { + this.matchedSchema = matchedSchema; + } + + /** + * Gets the matched schema $ref. + * + * @return the matched schema $ref + */ + public String getMatchedSchema() { + return this.matchedSchema; + } + + /** + * Returns true if there was a schema that matched the discriminating value. + *

+ * If the discriminating value does not match an implicit or explicit mapping, + * no schema can be determined and validation SHOULD fail. Therefore if this + * returns false validation should fail. + *

+ * 4.8.25.4 + * Examples + * + * @return true if there was a schema that matched the discriminating value. + */ + public boolean hasMatchedSchema() { + return this.matchedSchema != null; + } + + /** + * Determine if there is an implicit match of the mapped schema to the ref. + * + * @param refSchema the $ref value + * @param mappedSchema the mapped schema + * @return true if there is a match + */ + private static boolean isImplicitMatch(String refSchema, String mappedSchema) { + /* + * To ensure that an ambiguous value (e.g. "foo") is treated as a relative URI + * reference by all implementations, authors MUST prefix it with the "." path + * segment (e.g. "./foo"). + */ + if (mappedSchema.startsWith(".")) { + return refSchema.equals(mappedSchema); + } else { + int found = refSchema.lastIndexOf('/'); + if (found == -1) { + return refSchema.equals(mappedSchema); + } else { + return refSchema.substring(found + 1).equals(mappedSchema); + } + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java b/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java new file mode 100644 index 000000000..bc85d0011 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * {@link KeywordValidator} for discriminator. + *

+ * Note that discriminator MUST NOT change the validation outcome of the schema. + *

+ * Discriminator + * Object + */ +public class DiscriminatorValidator extends BaseKeywordValidator { + /** + * The name of the property in the payload that will hold the discriminating + * value. This property SHOULD be required in the payload schema, as the + * behavior when the property is absent is undefined. + */ + private final String propertyName; + /** + * An object to hold mappings between payload values and schema names or URI + * references. + */ + private final Map mapping; + + /** + * The schema name or URI reference to a schema that is expected to validate the + * structure of the model when the discriminating property is not present in the + * payload or contains a value for which there is no explicit or implicit + * mapping. + *

+ * Since OpenAPI 3.2.0 + */ + private final String defaultMapping; + + public DiscriminatorValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.DISCRIMINATOR, schemaNode, schemaLocation, parentSchema, schemaContext); + ObjectNode discriminator = schemaNode.isObject() ? (ObjectNode) schemaNode : null; + if (discriminator != null) { + JsonNode propertyName = discriminator.get("propertyName"); + /* + * There really should be a parse error if propertyName is not defined in the + * schema but there is non-specification compliant behavior if there are + * multiple discriminators on the same path if the propertyName is not defined. + */ + this.propertyName = propertyName != null ? propertyName.asText() : ""; + JsonNode mappingNode = discriminator.get("mapping"); + ObjectNode mapping = mappingNode != null && mappingNode.isObject() ? (ObjectNode) mappingNode : null; + if (mapping != null) { + this.mapping = new HashMap<>(); + for (Iterator> iter = mapping.fields(); iter.hasNext();) { + Entry entry = iter.next(); + this.mapping.put(entry.getKey(), entry.getValue().asText()); + } + } else { + this.mapping = Collections.emptyMap(); + } + + // Check if OpenAPI 3.2.0 + JsonNode defaultMapping = discriminator.get("defaultMapping"); + if (defaultMapping != null) { + this.defaultMapping = defaultMapping.asText(); + } else { + this.defaultMapping = null; + } + + } else { + this.propertyName = ""; + this.mapping = Collections.emptyMap(); + this.defaultMapping = null; + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + DiscriminatorState state = null; + DiscriminatorState existing = executionContext.getDiscriminatorMapping().get(instanceLocation); + if (existing != null) { + /* + * By default this does not throw an exception unless strict for discriminator + * is set to true. + * + * This default is in line with the fact that the discriminator keyword doesn't + * affect validation but just helps to filter the messages. + */ + if (this.schemaContext.getSchemaRegistryConfig().isStrict("discriminator", Boolean.FALSE)) { + throw new SchemaException("Schema at " + this.schemaLocation + + " has a discriminator keyword for which another discriminator keyword has already been set for at " + + instanceLocation); + } + /* + * This allows a new discriminator keyword if the propertyName is empty or if + * the propertyName value is the same as the existing one. + * + * In the specification the behavior of this is undefined. There shouldn't be + * multiple matching discriminator keywords for the same instance. + * + * Also propertyName for the discriminator keyword should not be empty according + * to the specification. + */ + if (!"".equals(this.propertyName) && !existing.getPropertyName().equals(this.propertyName)) { + throw new SchemaException("Schema at " + this.schemaLocation + + " is redefining the discriminator property that has already been set for at " + + instanceLocation); + } + state = existing; + } else { + state = new DiscriminatorState(); + state.setPropertyName(this.propertyName); + executionContext.getDiscriminatorMapping().put(instanceLocation, state); + } + JsonNode discriminatingValueNode = node.get(state.getPropertyName()); + if (discriminatingValueNode != null && discriminatingValueNode.isTextual()) { + String discriminatingValue = discriminatingValueNode.asText(); + state.setDiscriminatingValue(discriminatingValue); + // Check for explicit mapping + String mappedSchema = mapping.get(discriminatingValue); + if (existing != null && mappedSchema != null) { + /* + * If the existing already has an explicit mapping and this doesn't tally with + * this one this is an issue as well. + */ + if (existing.isExplicitMapping() && !existing.getMappedSchema().equals(mappedSchema)) { + throw new SchemaException( + "Schema at " + this.schemaLocation + " is mapping that has already been set for " + + instanceLocation + " from " + existing.getMappedSchema() + " to " + mappedSchema); + } + } + if (mappedSchema != null) { + // Explicit mapping found + state.setMappedSchema(mappedSchema); + state.setExplicitMapping(true); + } else { + if (!state.isExplicitMapping()) { // only sets implicit if an explicit mapping was not previously set + // If explicit mapping not found use implicit value + state.setMappedSchema(discriminatingValue); + state.setExplicitMapping(false); + } + } + } else { + /* + * Since OpenAPI 3.2.0 if defaultMapping is set, then the property is optional. + */ + if (this.defaultMapping != null) { + state.setMappedSchema(defaultMapping); + state.setExplicitMapping(true); + return; + } + + /* + * The property is not present in the payload. This property SHOULD be required + * in the payload schema, as the behavior when the property is absent is + * undefined. + */ + /* + * By default this does not generate an assertion unless strict for + * discriminator is set to true. + * + * This default is in line with the intent that discriminator should be an + * annotation and not an assertion and shouldn't change the result which was why + * the specification changed from MUST to SHOULD. + */ + if (this.schemaContext.getSchemaRegistryConfig().isStrict("discriminator", Boolean.FALSE)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .messageKey("discriminator.missing_discriminating_value").arguments(this.propertyName).build()); + } + } + } + + /** + * Gets the property name of the discriminator. + * + * @return the property name + */ + public String getPropertyName() { + return propertyName; + } + + /** + * Gets the mapping to map the property name value to the schema name. + * + * @return the discriminator mappings + */ + public Map getMapping() { + return mapping; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java b/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java new file mode 100644 index 000000000..fc6d497dc --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.InvalidSchemaRefException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.ThreadSafeCachingSupplier; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; + +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * {@link KeywordValidator} that resolves $dynamicRef. + */ +public class DynamicRefValidator extends BaseKeywordValidator { + + public DynamicRefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.DYNAMIC_REF, schemaNode, schemaLocation, parentSchema, schemaContext); + } + + static SchemaRef getRefSchema(Schema parentSchema, String refValue, + ExecutionContext executionContext) { + String ref = resolve(parentSchema, refValue); + return new SchemaRef(getSupplier(() -> { + SchemaContext schemaContext = parentSchema.getSchemaContext(); + Schema refSchema = parentSchema.getSchemaContext().getDynamicAnchors().get(ref); + if (refSchema == null) { // This is a $dynamicRef without a matching $dynamicAnchor + // A $dynamicRef without a matching $dynamicAnchor in the same schema resource + // behaves like a normal $ref to $anchor + // A $dynamicRef without anchor in fragment behaves identical to $ref + SchemaRef r = RefValidator.getRefSchema(parentSchema, schemaContext, refValue); + if (r != null) { + refSchema = r.getSchema(); + } + } else { + // Check parents + Schema base; + int index = ref.indexOf("#"); + String anchor = ref.substring(index); + String absoluteIri = ref.substring(0, index); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + base = iter.next(); + String baseAbsoluteIri = base.getSchemaLocation().getAbsoluteIri() != null ? base.getSchemaLocation().getAbsoluteIri().toString() : ""; + if (!baseAbsoluteIri.equals(absoluteIri)) { + absoluteIri = baseAbsoluteIri; + String parentRef = SchemaLocation.resolve(base.getSchemaLocation(), anchor); + Schema parentRefSchema = base.getSchemaContext().getDynamicAnchors().get(parentRef); + if (parentRefSchema != null) { + refSchema = parentRefSchema; + } + } + } + } + return refSchema; + }, false)); + } + + static Supplier getSupplier(Supplier supplier, boolean cache) { + return cache ? new ThreadSafeCachingSupplier<>(supplier) : supplier; + } + + private static String resolve(Schema parentSchema, String refValue) { + // $ref prevents a sibling $id from changing the base uri + Schema base = parentSchema; + if (parentSchema.getId() != null && parentSchema.getParentSchema() != null) { + base = parentSchema.getParentSchema(); + } + return SchemaLocation.resolve(base.getSchemaLocation(), refValue); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + Schema refSchema = getSchemaRef(executionContext).getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.DYNAMIC_REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + refSchema.validate(executionContext, node, rootNode, instanceLocation); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, + // these schemas will be cached along with config. We have to replace the config for cached $ref references + // with the latest config. Reset the config. + Schema refSchema = getSchemaRef(executionContext).getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.DYNAMIC_REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + if (node == null) { + // Check for circular dependency + boolean circularDependency = false; + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); + if (check.getSchemaLocation().equals(schemaLocation)) { + circularDependency = true; + break; + } + } + if (circularDependency) { + return; + } + } + refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + + public SchemaRef getSchemaRef(ExecutionContext executionContext ) { + String refValue = schemaNode.asText(); + return getRefSchema(this.getParentSchema(), refValue, executionContext); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/EnumValidator.java b/src/main/java/com/networknt/schema/keyword/EnumValidator.java new file mode 100644 index 000000000..8ee06e794 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/EnumValidator.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; +import com.networknt.schema.SchemaContext; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * {@link KeywordValidator} for enum. + */ +public class EnumValidator extends BaseKeywordValidator implements KeywordValidator { + private final Set nodes; + private final String error; + + static String asText(JsonNode node) { + if (node.isObject() || node.isArray() || node.isTextual()) { + // toString for isTextual is so that there are quotes + return node.toString(); + } + return node.asText(); + } + + public EnumValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ENUM, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode != null && schemaNode.isArray()) { + nodes = new HashSet<>(); + StringBuilder sb = new StringBuilder(); + + sb.append('['); + String separator = ""; + + for (JsonNode n : schemaNode) { + if (n.isNumber()) { + // convert to DecimalNode for number comparison + nodes.add(processNumberNode(n)); + } else if (n.isArray()) { + ArrayNode a = processArrayNode((ArrayNode) n); + nodes.add(a); + } else { + nodes.add(n); + } + + sb.append(separator); + sb.append(asText(n)); + separator = ", "; + } + + // check if the parent schema declares the fields as nullable + if (schemaContext.isNullableKeywordEnabled()) { + JsonNode nullable = parentSchema.getSchemaNode().get("nullable"); + if (nullable != null && nullable.asBoolean()) { + nodes.add(NullNode.getInstance()); + separator = ", "; + sb.append(separator); + sb.append("null"); + } + } + sb.append(']'); + + error = sb.toString(); + } else { + nodes = Collections.emptySet(); + error = "[none]"; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (node.isNumber()) { + node = processNumberNode(node); + } else if (node.isArray()) { + node = processArrayNode((ArrayNode) node); + } + if (!nodes.contains(node) && !( this.schemaContext.getSchemaRegistryConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(error).build()); + } + } + + /** + * Check whether enum contains the value of the JsonNode if the typeLoose is enabled. + * + * @param node JsonNode to check + */ + private boolean isTypeLooseContainsInEnum(JsonNode node) { + if (TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()) == JsonType.STRING) { + String nodeText = node.textValue(); + for (JsonNode n : nodes) { + String value = n.asText(); + if (value != null && value.equals(nodeText)) { + return true; + } + } + } + return false; + } + + /** + * Processes the number and ensures trailing zeros are stripped. + * + * @param n the node + * @return the node + */ + protected JsonNode processNumberNode(JsonNode n) { + return DecimalNode.valueOf(new BigDecimal(n.decimalValue().toPlainString())); + } + + /** + * Processes the array and ensures that numbers within have trailing zeroes stripped. + * + * @param node the node + * @return the node + */ + protected ArrayNode processArrayNode(ArrayNode node) { + if (!hasNumber(node)) { + return node; + } + ArrayNode a = node.deepCopy(); + for (int x = 0; x < a.size(); x++) { + JsonNode v = a.get(x); + if (v.isNumber()) { + v = processNumberNode(v); + a.set(x, v); + } + } + return a; + } + + /** + * Determines if the array node contains a number. + * + * @param node the node + * @return the node + */ + protected boolean hasNumber(ArrayNode node) { + for (int x = 0; x < node.size(); x++) { + JsonNode v = node.get(x); + if (v.isNumber()) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java new file mode 100644 index 000000000..d7aa9f5d7 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; +import com.networknt.schema.utils.JsonType; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * {@link KeywordValidator} for exclusiveMaximum. + */ +public class ExclusiveMaximumValidator extends BaseKeywordValidator { + private final ThresholdMixin typedMaximum; + + public ExclusiveMaximumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.EXCLUSIVE_MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isNumber()) { + throw new SchemaException("exclusiveMaximum value is not a number"); + } + final String maximumText = schemaNode.asText(); + if ((schemaNode.isLong() || schemaNode.isInt()) && (JsonType.INTEGER.toString().equals(getNodeFieldType()))) { + // "integer", and within long range + final long lm = schemaNode.asLong(); + typedMaximum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (node.isBigInteger()) { + //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. + int compare = node.bigIntegerValue().compareTo(new BigInteger(schemaNode.asText())); + return compare > 0 || compare == 0; + + } else if (node.isTextual()) { + BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || compare == 0; + } + long val = node.asLong(); + return lm < val || lm == val; + } + + @Override + public String thresholdValue() { + return String.valueOf(lm); + } + }; + } else { + typedMaximum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + final BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || compare == 0; + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (!JsonNodeTypes.isNumber(node, schemaContext.getSchemaRegistryConfig())) { + // maximum only applies to numbers + return; + } + + if (typedMaximum.crossesThreshold(node)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(typedMaximum.thresholdValue()).build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java new file mode 100644 index 000000000..200965fd7 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; +import com.networknt.schema.utils.JsonType; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * {@link KeywordValidator} for exclusiveMinimum. + */ +public class ExclusiveMinimumValidator extends BaseKeywordValidator { + /** + * In order to limit number of `if` statements in `validate` method, all the + * logic of picking the right comparison is abstracted into a mixin. + */ + private final ThresholdMixin typedMinimum; + + public ExclusiveMinimumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.EXCLUSIVE_MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isNumber()) { + throw new SchemaException("exclusiveMinimum value is not a number"); + } + final String minimumText = schemaNode.asText(); + if ((schemaNode.isLong() || schemaNode.isInt()) && JsonType.INTEGER.toString().equals(getNodeFieldType())) { + // "integer", and within long range + final long lmin = schemaNode.asLong(); + typedMinimum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (node.isBigInteger()) { + //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. + int compare = node.bigIntegerValue().compareTo(new BigInteger(minimumText)); + return compare < 0 || compare == 0; + + } else if (node.isTextual()) { + BigDecimal min = new BigDecimal(minimumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(min); + return compare < 0 || compare == 0; + + } + long val = node.asLong(); + return lmin > val || lmin == val; + } + + @Override + public String thresholdValue() { + return String.valueOf(lmin); + } + }; + + } else { + typedMinimum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + // jackson's BIG_DECIMAL parsing is limited. see https://github.com/FasterXML/jackson-databind/issues/1770 + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + final BigDecimal min = new BigDecimal(minimumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(min); + return compare < 0 || compare == 0; + } + + @Override + public String thresholdValue() { + return minimumText; + } + }; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (!JsonNodeTypes.isNumber(node, this.schemaContext.getSchemaRegistryConfig())) { + // minimum only applies to numbers + return; + } + + if (typedMinimum.crossesThreshold(node)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(typedMinimum.thresholdValue()).build()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/FalseValidator.java b/src/main/java/com/networknt/schema/keyword/FalseValidator.java new file mode 100644 index 000000000..49025e2c1 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/FalseValidator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * {@link KeywordValidator} for false. + */ +public class FalseValidator extends BaseKeywordValidator implements KeywordValidator { + + public FalseValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.FALSE, schemaNode, schemaLocation, parentSchema, schemaContext); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + // For the false validator, it is always not valid + String reason = executionContext.getEvaluationPath().getParent().getName(-1); + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(reason).build()); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/FormatKeyword.java b/src/main/java/com/networknt/schema/keyword/FormatKeyword.java new file mode 100644 index 000000000..6e35bc112 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/FormatKeyword.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.format.Format; +import com.networknt.schema.SchemaContext; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * Format Keyword. + */ +public class FormatKeyword implements Keyword { + private final String value; + private final Map formats; + + public FormatKeyword(Map formats) { + this(KeywordType.FORMAT, formats); + } + + public FormatKeyword(Keyword type, Map formats) { + this(type.getValue(), formats); + } + + public FormatKeyword(String value, Map formats) { + this.value = value; + this.formats = formats; + } + + Collection getFormats() { + return Collections.unmodifiableCollection(this.formats.values()); + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + Format format = null; + if (schemaNode != null && schemaNode.isTextual()) { + String formatName = schemaNode.textValue(); + format = this.formats.get(formatName); + } + return new FormatValidator(schemaLocation, schemaNode, parentSchema, schemaContext, format, + this); + } + + @Override + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/FormatValidator.java b/src/main/java/com/networknt/schema/keyword/FormatValidator.java new file mode 100644 index 000000000..f9a6c5814 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/FormatValidator.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.format.BaseFormatValidator; +import com.networknt.schema.format.Format; +import com.networknt.schema.path.NodePath; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.regex.PatternSyntaxException; + +/** + * Validator for Format. + */ +public class FormatValidator extends BaseFormatValidator implements KeywordValidator { + private static final Logger logger = LoggerFactory.getLogger(FormatValidator.class); + + private final Format format; + + public FormatValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext, Format format, + Keyword keyword) { + super(schemaLocation, schemaNode, parentSchema, keyword, schemaContext); + this.format = format; + } + + /** + * Gets the annotation value. + * + * @return the annotation value + */ + protected Object getAnnotationValue() { + if (this.format != null) { + return this.format.getName(); + } + return this.schemaNode.isTextual() ? schemaNode.textValue() : null; + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + /* + * Annotations must be collected even if the format is unknown according to the specification. + */ + if (collectAnnotations(executionContext)) { + Object annotationValue = getAnnotationValue(); + if (annotationValue != null) { + putAnnotation(executionContext, + annotation -> annotation.instanceLocation(instanceLocation).value(annotationValue)); + } + } + + boolean assertionsEnabled = isAssertionsEnabled(executionContext); + if (this.format != null) { + try { + format.validate(executionContext, schemaContext, node, rootNode, instanceLocation, + assertionsEnabled, + () -> this.error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).messageKey(format.getMessageKey()) + .locale(executionContext.getExecutionConfig().getLocale()) + , + this); + } catch (PatternSyntaxException pse) { + // String is considered valid if pattern is invalid + logger.error("Failed to apply pattern on {}: Invalid RE syntax [{}]", instanceLocation, + format.getName(), pse); + } + } else { + validateUnknownFormat(executionContext, node, rootNode, instanceLocation); + } + } + + /** + * When the Format-Assertion vocabulary is specified, implementations MUST fail upon encountering unknown formats. + * + * @param executionContext the execution context + * @param node the node + * @param rootNode the root node + * @param instanceLocation the instance location + */ + protected void validateUnknownFormat(ExecutionContext executionContext, + JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + /* + * Unknown formats should create an assertion if the vocab is specified + * according to the specification. + */ + if (createUnknownFormatAssertions(executionContext) && this.schemaNode.isTextual()) { + executionContext.addError(error().instanceLocation(instanceLocation).instanceNode(node) + .evaluationPath(executionContext.getEvaluationPath()).messageKey("format.unknown").arguments(schemaNode.textValue()).build()); + } + } + + /** + * When the Format-Assertion vocabulary is specified, implementations MUST fail + * upon encountering unknown formats. + *

+ * Note that this is different from setting the setFormatAssertionsEnabled + * configuration option. + *

+ * The following logic will return true if the format assertions option is + * turned on and strict is enabled (default false) or the format assertion + * vocabulary is enabled. + * + * @param executionContext the execution context + * @return true if format assertions should be generated + */ + protected boolean createUnknownFormatAssertions(ExecutionContext executionContext) { + return (isAssertionsEnabled(executionContext) && isStrict(executionContext)) || (isFormatAssertionVocabularyEnabled()); + } + + /** + * Determines if strict handling. + *

+ * Note that this defaults to false. + * + * @param executionContext the execution context + * @return whether to perform strict handling + */ + protected boolean isStrict(ExecutionContext executionContext) { + return this.schemaContext.getSchemaRegistryConfig().isStrict(getKeyword(), Boolean.FALSE); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/IfValidator.java b/src/main/java/com/networknt/schema/keyword/IfValidator.java new file mode 100644 index 000000000..22e5053be --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/IfValidator.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for if. + */ +public class IfValidator extends BaseKeywordValidator { + private static final List KEYWORDS = Arrays.asList("if", "then", "else"); + + private final Schema ifSchema; + private final Schema thenSchema; + private final Schema elseSchema; + + public IfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.IF_THEN_ELSE, schemaNode, schemaLocation, parentSchema, schemaContext); + + Schema foundIfSchema = null; + Schema foundThenSchema = null; + Schema foundElseSchema = null; + + for (final String keyword : KEYWORDS) { + final JsonNode node = parentSchema.getSchemaNode().get(keyword); + final SchemaLocation schemaLocationOfSchema = parentSchema.getSchemaLocation().append(keyword); + if (keyword.equals("if")) { + foundIfSchema = schemaContext.newSchema(schemaLocationOfSchema, node, + parentSchema); + } else if (keyword.equals("then") && node != null) { + foundThenSchema = schemaContext.newSchema(schemaLocationOfSchema, node, + parentSchema); + } else if (keyword.equals("else") && node != null) { + foundElseSchema = schemaContext.newSchema(schemaLocationOfSchema, node, + parentSchema); + } + } + + this.ifSchema = foundIfSchema; + this.thenSchema = foundThenSchema; + this.elseSchema = foundElseSchema; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + boolean ifConditionPassed = false; + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + List existingErrors = executionContext.getErrors(); + List test = new ArrayList<>(); + executionContext.setErrors(test); + try { + executionContext.setFailFast(false); + this.ifSchema.validate(executionContext, node, rootNode, instanceLocation); + ifConditionPassed = test.isEmpty(); + } finally { + // Restore flag + executionContext.setErrors(existingErrors); + executionContext.setFailFast(failFast); + } + + if (ifConditionPassed && this.thenSchema != null) { + // The "if" keyword is a bit unusual as it actually handles multiple keywords + // This removes the "if" in the evaluation path so the rest of the evaluation paths will be correct + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } else if (!ifConditionPassed && this.elseSchema != null) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } + } + + @Override + public void preloadSchema() { + if (null != this.ifSchema) { + this.ifSchema.initializeValidators(); + } + if (null != this.thenSchema) { + this.thenSchema.initializeValidators(); + } + if (null != this.elseSchema) { + this.elseSchema.initializeValidators(); + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + // The "if" keyword is a bit unusual as it actually handles multiple keywords + // This removes the "if" in the evaluation path so the rest of the evaluation paths will be correct + + boolean checkCondition = node != null && shouldValidateSchema; + boolean ifConditionPassed = false; + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + List existingErrors = executionContext.getErrors(); + List test = new ArrayList<>(); + executionContext.setErrors(test); + try { + executionContext.setFailFast(false); + this.ifSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + ifConditionPassed = test.isEmpty(); + } finally { + // Restore flag + executionContext.setErrors(existingErrors); + executionContext.setFailFast(failFast); + } + if (!checkCondition) { + if (this.thenSchema != null) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } + if (this.elseSchema != null) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } + } else { + if (this.thenSchema != null && ifConditionPassed) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } else if (this.elseSchema != null && !ifConditionPassed) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java b/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java new file mode 100644 index 000000000..85e5c7d7e --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.SchemaRefs; + +/** + * {@link KeywordValidator} for items Draft 4 to Draft 2019-09. + */ +public class ItemsLegacyValidator extends BaseKeywordValidator { + private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems"; + + private final Schema schema; + private final List tupleSchema; + private final Boolean additionalItems; + private final Schema additionalSchema; + + private final SchemaLocation additionalItemsSchemaLocation; + private final JsonNode additionalItemsSchemaNode; + + public ItemsLegacyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ITEMS_LEGACY, schemaNode, schemaLocation, parentSchema, schemaContext); + + Boolean additionalItems = null; + + Schema foundSchema = null; + Schema foundAdditionalSchema = null; + JsonNode additionalItemsSchemaNode = null; + + if (schemaNode.isObject() || schemaNode.isBoolean()) { + foundSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + this.tupleSchema = Collections.emptyList(); + } else { + int i = 0; + this.tupleSchema = new ArrayList<>(schemaNode.size()); + for (JsonNode s : schemaNode) { + this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), + s, parentSchema)); + i++; + } + + JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS); + if (addItemNode != null) { + additionalItemsSchemaNode = addItemNode; + if (addItemNode.isBoolean()) { + additionalItems = addItemNode.asBoolean(); + } else if (addItemNode.isObject()) { + foundAdditionalSchema = schemaContext.newSchema( + parentSchema.getSchemaLocation().append(PROPERTY_ADDITIONAL_ITEMS), + addItemNode, parentSchema); + } + } + } + this.additionalItems = additionalItems; + this.schema = foundSchema; + this.additionalSchema = foundAdditionalSchema; + this.additionalItemsSchemaLocation = parentSchema.getSchemaLocation().append(PROPERTY_ADDITIONAL_ITEMS); + this.additionalItemsSchemaNode = additionalItemsSchemaNode; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (!node.isArray() && !this.schemaContext.getSchemaRegistryConfig().isTypeLoose()) { + // ignores non-arrays + return; + } + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); + + // Add items annotation + if (collectAnnotations || collectAnnotations(executionContext)) { + if (this.schema != null) { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } else if (this.tupleSchema != null) { + // Tuples + int items = node.isArray() ? node.size() : 1; + int schemas = this.tupleSchema.size(); + if (items > schemas) { + // More items than schemas so the keyword only applied to the number of schemas + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(schemas).build()); + } else { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } + + boolean hasAdditionalItem = false; + if (node.isArray()) { + int i = 0; + for (JsonNode n : node) { + if (doValidate(executionContext, i, n, rootNode, instanceLocation)) { + hasAdditionalItem = true; + } + i++; + } + } else { + if (doValidate(executionContext, 0, node, rootNode, instanceLocation)) { + hasAdditionalItem = true; + } + } + + if (hasAdditionalItem) { + if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) { + executionContext.evaluationPathAddLast("additionalItems"); + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.additionalItemsSchemaLocation) + .keyword("additionalItems").value(true).build()); + executionContext.evaluationPathRemoveLast(); + } + } + } + + private boolean doValidate(ExecutionContext executionContext, int i, JsonNode node, + JsonNode rootNode, NodePath instanceLocation) { + boolean isAdditionalItem = false; + NodePath path = instanceLocation.append(i); + + if (this.schema != null) { + // validate with item schema (the whole array has the same item + // schema) + this.schema.validate(executionContext, node, rootNode, path); + } else if (this.tupleSchema != null) { + if (i < this.tupleSchema.size()) { + // validate against tuple schema + executionContext.evaluationPathAddLast(i); + try { + this.tupleSchema.get(i).validate(executionContext, node, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } + + } else { + if ((this.additionalItems != null && this.additionalItems) || this.additionalSchema != null) { + isAdditionalItem = true; + } + + if (this.additionalSchema != null) { + // validate against additional item schema + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast("additionalItems"); + try { + this.additionalSchema.validate(executionContext, node, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } + } else if (this.additionalItems != null) { + if (this.additionalItems) { +// evaluatedItems.add(path); + } else { + // no additional item allowed, return error + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast("additionalItems"); + try { + executionContext.addError(error().instanceNode(rootNode).instanceLocation(instanceLocation) + .keyword("additionalItems") + .messageKey("additionalItems") + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.additionalItemsSchemaLocation) + .schemaNode(this.additionalItemsSchemaNode) + .locale(executionContext.getExecutionConfig().getLocale()) + .index(i) + .arguments(i).build()); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } + } + } + } + } + return isAdditionalItem; + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); + + // Add items annotation + if (collectAnnotations || collectAnnotations(executionContext)) { + if (this.schema != null) { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } else if (this.tupleSchema != null) { + // Tuples + int items = node.isArray() ? node.size() : 1; + int schemas = this.tupleSchema.size(); + if (items > schemas) { + // More items than schemas so the keyword only applied to the number of schemas + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(schemas).build()); + } else { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } + + if (this.schema != null) { + // Walk the schema. + if (node instanceof ArrayNode) { + int count = Math.max(1, node.size()); + ArrayNode arrayNode = (ArrayNode) node; + JsonNode defaultNode = null; + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { + defaultNode = getDefaultNode(this.schema, executionContext); + } + for (int i = 0; i < count; i++) { + JsonNode n = arrayNode.get(i); + if (n != null) { + if (n.isNull() && defaultNode != null) { + arrayNode.set(i, defaultNode); + n = defaultNode; + } + } + walkSchema(executionContext, this.schema, n, rootNode, instanceLocation.append(i), shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } + } else { + walkSchema(executionContext, this.schema, null, rootNode, instanceLocation.append(0), shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } + } + else if (this.tupleSchema != null) { + int prefixItems = this.tupleSchema.size(); + for (int i = 0; i < prefixItems; i++) { + // walk tuple schema + if (node instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) node; + JsonNode defaultNode = null; + JsonNode n = arrayNode.get(i); + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { + defaultNode = getDefaultNode(this.tupleSchema.get(i), executionContext); + } + if (n != null) { + if (n.isNull() && defaultNode != null) { + arrayNode.set(i, defaultNode); + n = defaultNode; + } + } + executionContext.evaluationPathAddLast(i); + try { + walkSchema(executionContext, this.tupleSchema.get(i), n, rootNode, instanceLocation.append(i), + shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } else { + executionContext.evaluationPathAddLast(i); + try { + walkSchema(executionContext, this.tupleSchema.get(i), null, rootNode, + instanceLocation.append(i), shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + if (this.additionalSchema != null) { + boolean hasAdditionalItem = false; + + int additionalItems = Math.max(1, (node != null ? node.size() : 0) - prefixItems); + for (int x = 0; x < additionalItems; x++) { + int i = x + prefixItems; + // walk additional item schema + if (node instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) node; + JsonNode defaultNode = null; + JsonNode n = arrayNode.get(i); + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { + defaultNode = getDefaultNode(this.additionalSchema, executionContext); + } + if (n != null) { + if (n.isNull() && defaultNode != null) { + arrayNode.set(i, defaultNode); + n = defaultNode; + } + } + walkSchema(executionContext, this.additionalSchema, n, rootNode, instanceLocation.append(i), + shouldValidateSchema, PROPERTY_ADDITIONAL_ITEMS); + if (n != null) { + hasAdditionalItem = true; + } + } else { + walkSchema(executionContext, this.additionalSchema, null, rootNode, instanceLocation.append(i), + shouldValidateSchema, PROPERTY_ADDITIONAL_ITEMS); + } + } + + if (hasAdditionalItem) { + if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) { + executionContext.evaluationPathAddLast("additionalItems"); + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.additionalItemsSchemaLocation) + .keyword("additionalItems").value(true).build()); + executionContext.evaluationPathRemoveLast(); + } + } + } + } + } + + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema(), executionContext); + } + } + return result; + } + + private void walkSchema(ExecutionContext executionContext, Schema walkSchema, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema, String keyword) { + boolean additionalItems = "additionalItems".equals(keyword); + if (additionalItems) { + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast(keyword); + } + try { + boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner() + .runPreWalkListeners(executionContext, keyword, node, rootNode, instanceLocation, walkSchema, this); + int currentErrors = executionContext.getErrors().size(); + if (executeWalk) { + walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, keyword, + node, rootNode, instanceLocation, walkSchema, this, + executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); + } finally { + if (additionalItems) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } + } + } + + public List getTupleSchema() { + return this.tupleSchema; + } + + public Schema getSchema() { + return this.schema; + } + + @Override + public void preloadSchema() { + if (null != this.schema) { + this.schema.initializeValidators(); + } + preloadSchemas(this.tupleSchema); + if (null != this.additionalSchema) { + this.additionalSchema.initializeValidators(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/ItemsValidator.java b/src/main/java/com/networknt/schema/keyword/ItemsValidator.java new file mode 100644 index 000000000..5877edaf3 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ItemsValidator.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.SchemaRefs; + +/** + * {@link KeywordValidator} for items from Draft 2012-12. + */ +public class ItemsValidator extends BaseKeywordValidator { + private final Schema schema; + private final int prefixCount; + private final boolean additionalItems; + + public ItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + + JsonNode prefixItems = parentSchema.getSchemaNode().get("prefixItems"); + if (prefixItems instanceof ArrayNode) { + this.prefixCount = prefixItems.size(); + } else if (null == prefixItems) { + this.prefixCount = 0; + } else { + throw new IllegalArgumentException("The value of 'prefixItems' must be an array of JSON Schema."); + } + + if (schemaNode.isObject() || schemaNode.isBoolean()) { + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } else { + throw new IllegalArgumentException("The value of 'items' MUST be a valid JSON Schema."); + } + + this.additionalItems = schemaNode.isBoolean() ? schemaNode.booleanValue() : true; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + + + // ignores non-arrays + if (node.isArray()) { + boolean evaluated = false; + for (int i = this.prefixCount; i < node.size(); ++i) { + NodePath path = instanceLocation.append(i); + // validate with item schema (the whole array has the same item schema) + if (additionalItems) { + this.schema.validate(executionContext, node.get(i), rootNode, path); + } else { + // This handles the case where "items": false as the boolean false schema doesn't + // generate a helpful message + int x = i; + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .index(x).arguments(x).build()); + } + evaluated = true; + } + if (evaluated) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + if (node instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) node; + JsonNode defaultNode = null; + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults() + && this.schema != null) { + defaultNode = getDefaultNode(this.schema, executionContext); + } + boolean evaluated = false; + for (int i = this.prefixCount; i < node.size(); ++i) { + JsonNode n = node.get(i); + if (n.isNull() && defaultNode != null) { + arrayNode.set(i, defaultNode); + n = defaultNode; + } + // Walk the schema. + walkSchema(executionContext, this.schema, n, rootNode, instanceLocation.append(i), shouldValidateSchema); + if (n != null) { + evaluated = true; + } + } + if (evaluated) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } else { + // If the node is not an ArrayNode, e.g. ObjectNode or null then the instance is null. + // The instance location starts at the end of the prefix count. + walkSchema(executionContext, this.schema, null, rootNode, instanceLocation.append(this.prefixCount), + shouldValidateSchema); + } + } + + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema(), executionContext); + } + } + return result; + } + + private void walkSchema(ExecutionContext executionContext, Schema walkSchema, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + //@formatter:off + boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner().runPreWalkListeners( + executionContext, + KeywordType.ITEMS.getValue(), + node, + rootNode, + instanceLocation, + walkSchema, this + ); + int currentErrors = executionContext.getErrors().size(); + if (executeWalk) { + walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners( + executionContext, + KeywordType.ITEMS.getValue(), + node, + rootNode, + instanceLocation, + walkSchema, + this, executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size()) + ); + //@formatter:on + } + + public Schema getSchema() { + return this.schema; + } + + @Override + public void preloadSchema() { + this.schema.initializeValidators(); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/Keyword.java b/src/main/java/com/networknt/schema/keyword/Keyword.java new file mode 100644 index 000000000..596c7ea62 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/Keyword.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; + +/** + * Represents a keyword. + */ +public interface Keyword { + /** + * Gets the keyword value. + * + * @return the keyword value + */ + String getValue(); + + /** + * Creates a new validator for the keyword. + * + * @param schemaLocation the schema location + * @param schemaNode the schema node + * @param parentSchema the parent schema + * @param schemaContext the schema context + * @return the validation + * @throws SchemaException the exception + * @throws Exception the exception + */ + KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception; +} diff --git a/src/main/java/com/networknt/schema/keyword/KeywordFactory.java b/src/main/java/com/networknt/schema/keyword/KeywordFactory.java new file mode 100644 index 000000000..f1f6967d2 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/KeywordFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import com.networknt.schema.SchemaContext; + +/** + * Factory for {@link Keyword}. + */ +@FunctionalInterface +public interface KeywordFactory { + /** + * Gets the keyword given the keyword value. + * + * @param value the keyword value + * @param schemaContext the schemaContext + * @return the keyword + */ + Keyword getKeyword(String value, SchemaContext schemaContext); +} diff --git a/src/main/java/com/networknt/schema/keyword/KeywordType.java b/src/main/java/com/networknt/schema/keyword/KeywordType.java new file mode 100644 index 000000000..a199cde1b --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/KeywordType.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.SpecificationVersionRange; +import com.networknt.schema.SchemaContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@FunctionalInterface +interface ValidatorFactory { + KeywordValidator newInstance(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext); +} + +public enum KeywordType implements Keyword { + ADDITIONAL_PROPERTIES("additionalProperties", AdditionalPropertiesValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + ALL_OF("allOf", AllOfValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + ANY_OF("anyOf", AnyOfValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + CONST("const", ConstValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), + CONTAINS("contains", ContainsValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), + CONTENT_ENCODING("contentEncoding", ContentEncodingValidator::new, SpecificationVersionRange.DRAFT_7), + CONTENT_MEDIA_TYPE("contentMediaType", ContentMediaTypeValidator::new, SpecificationVersionRange.DRAFT_7), + DEPENDENCIES("dependencies", DependenciesValidator::new, SpecificationVersionRange.ALL_VERSIONS), + DEPENDENT_REQUIRED("dependentRequired", DependentRequired::new, SpecificationVersionRange.NONE), + DEPENDENT_SCHEMAS("dependentSchemas", DependentSchemas::new, SpecificationVersionRange.NONE), + DISCRIMINATOR("discriminator", DiscriminatorValidator::new, SpecificationVersionRange.NONE), + DYNAMIC_REF("$dynamicRef", DynamicRefValidator::new, SpecificationVersionRange.NONE), + ENUM("enum", EnumValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + EXCLUSIVE_MAXIMUM("exclusiveMaximum", ExclusiveMaximumValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), + EXCLUSIVE_MINIMUM("exclusiveMinimum", ExclusiveMinimumValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), + FALSE("false", FalseValidator::new, SpecificationVersionRange.MIN_DRAFT_6), + FORMAT("format", null, SpecificationVersionRange.MAX_DRAFT_7) { + @Override public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + throw new UnsupportedOperationException("Use FormatKeyword instead"); + } + }, + ID("id", null, SpecificationVersionRange.ALL_VERSIONS), + IF_THEN_ELSE("if", IfValidator::new, SpecificationVersionRange.DRAFT_7), + ITEMS("items", ItemsValidator::new, SpecificationVersionRange.NONE), + ITEMS_LEGACY("items", ItemsLegacyValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MAX_CONTAINS("maxContains",MinMaxContainsValidator::new, SpecificationVersionRange.NONE), + MAX_ITEMS("maxItems", MaxItemsValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MAX_LENGTH("maxLength", MaxLengthValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MAX_PROPERTIES("maxProperties", MaxPropertiesValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MAXIMUM("maximum", MaximumValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MIN_CONTAINS("minContains", MinMaxContainsValidator::new, SpecificationVersionRange.NONE), + MIN_ITEMS("minItems", MinItemsValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MIN_LENGTH("minLength", MinLengthValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MIN_PROPERTIES("minProperties", MinPropertiesValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MINIMUM("minimum", MinimumValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + MULTIPLE_OF("multipleOf", MultipleOfValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + NOT_ALLOWED("notAllowed", NotAllowedValidator::new, SpecificationVersionRange.ALL_VERSIONS), + NOT("not", NotValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + ONE_OF("oneOf", OneOfValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + PATTERN_PROPERTIES("patternProperties", PatternPropertiesValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + PATTERN("pattern", PatternValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + PREFIX_ITEMS("prefixItems", PrefixItemsValidator::new, SpecificationVersionRange.NONE), + PROPERTIES("properties", PropertiesValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + PROPERTY_DEPENDENCIES("propertyDependencies", PropertyDependenciesValidator::new, SpecificationVersionRange.NONE), + PROPERTY_NAMES("propertyNames", PropertyNamesValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), + READ_ONLY("readOnly", ReadOnlyValidator::new, SpecificationVersionRange.DRAFT_7), + RECURSIVE_REF("$recursiveRef", RecursiveRefValidator::new, SpecificationVersionRange.NONE), + REF("$ref", RefValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + REQUIRED("required", RequiredValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + TRUE("true", TrueValidator::new, SpecificationVersionRange.MIN_DRAFT_6), + TYPE("type", TypeValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + UNEVALUATED_ITEMS("unevaluatedItems", UnevaluatedItemsValidator::new, SpecificationVersionRange.NONE), + UNEVALUATED_PROPERTIES("unevaluatedProperties",UnevaluatedPropertiesValidator::new,SpecificationVersionRange.NONE), + UNION_TYPE("unionType", UnionTypeValidator::new, SpecificationVersionRange.NONE), + UNIQUE_ITEMS("uniqueItems", UniqueItemsValidator::new, SpecificationVersionRange.MAX_DRAFT_7), + WRITE_ONLY("writeOnly", WriteOnlyValidator::new, SpecificationVersionRange.DRAFT_7), + ; + + private static final Map CONSTANTS = new HashMap<>(); + + static { + for (KeywordType c : values()) { + CONSTANTS.put(c.value, c); + } + } + + private final String value; + private final ValidatorFactory validatorFactory; + private final SpecificationVersionRange specificationVersionRange; + + KeywordType(String value, ValidatorFactory validatorFactory, SpecificationVersionRange specificationVersionRange) { + this.value = value; + this.validatorFactory = validatorFactory; + this.specificationVersionRange = specificationVersionRange; + } + + public static List getKeywords(SpecificationVersion specificationVersion) { + final List result = new ArrayList<>(); + for (KeywordType keyword : values()) { + if (keyword.getSpecificationVersionRange().getVersions().contains(specificationVersion)) { + result.add(keyword); + } + } + return result; + } + + public static KeywordType fromValue(String value) { + KeywordType constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } + return constant; + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + if (this.validatorFactory == null) { + throw new UnsupportedOperationException("No suitable validator for " + getValue()); + } + return validatorFactory.newInstance(schemaLocation, schemaNode, parentSchema, + schemaContext); + } + + @Override + public String toString() { + return this.value; + } + + @Override + public String getValue() { + return this.value; + } + + public SpecificationVersionRange getSpecificationVersionRange() { + return this.specificationVersionRange; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/KeywordValidator.java b/src/main/java/com/networknt/schema/keyword/KeywordValidator.java new file mode 100644 index 000000000..dbf561da8 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/KeywordValidator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.networknt.schema.SchemaException; +import com.networknt.schema.Validator; + +/** + * KeywordValidator interface implemented by all keyword validators. + */ +public interface KeywordValidator extends Validator { + /** + * In case the {@link com.networknt.schema.keyword.KeywordValidator} has a related {@link com.networknt.schema.Schema} or several + * ones, calling preloadSchema will actually load the schema document(s) eagerly. + * + * @throws SchemaException (a {@link java.lang.RuntimeException}) in case the {@link com.networknt.schema.Schema} or nested schemas + * are invalid (like $ref not resolving) + * @since 1.0.54 + */ + default void preloadSchema() throws SchemaException { + // do nothing by default - to be overridden in subclasses + } + + /** + * The keyword of the validator. + * + * @return the keyword + */ + String getKeyword(); +} diff --git a/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java b/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java new file mode 100644 index 000000000..049101155 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for maxItems. + */ +public class MaxItemsValidator extends BaseKeywordValidator implements KeywordValidator { + private final int max; + + public MaxItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.canConvertToExactIntegral()) { + this.max = schemaNode.intValue(); + } else { + this.max = 0; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (node.isArray()) { + if (node.size() > this.max) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.max, node.size()).build()); + } + } else if (this.schemaContext.getSchemaRegistryConfig().isTypeLoose()) { + if (1 > this.max) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.max, 1).build()); + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java b/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java new file mode 100644 index 000000000..0e1cda817 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for maxLength. + */ +public class MaxLengthValidator extends BaseKeywordValidator implements KeywordValidator { + private final int maxLength; + + public MaxLengthValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAX_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { + this.maxLength = schemaNode.intValue(); + } else { + this.maxLength = Integer.MAX_VALUE; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + // ignore no-string typs + return; + } + if (node.textValue().codePointCount(0, node.textValue().length()) > this.maxLength) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.maxLength).build()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java new file mode 100644 index 000000000..edef1cd95 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator}for maxProperties. + */ +public class MaxPropertiesValidator extends BaseKeywordValidator implements KeywordValidator { + private final int max; + + public MaxPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, + SchemaContext schemaContext) { + super(KeywordType.MAX_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.canConvertToExactIntegral()) { + max = schemaNode.intValue(); + } else { + max = Integer.MAX_VALUE; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (node.isObject()) { + if (node.size() > max) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(max).build()); + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MaximumValidator.java b/src/main/java/com/networknt/schema/keyword/MaximumValidator.java new file mode 100644 index 000000000..8b5db4096 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MaximumValidator.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; +import com.networknt.schema.utils.JsonType; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * {@link KeywordValidator} for maxmimum. + */ +public class MaximumValidator extends BaseKeywordValidator { + private static final String PROPERTY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum"; + + private final boolean excludeEqual; + + private final ThresholdMixin typedMaximum; + + + public MaximumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isNumber()) { + throw new SchemaException("maximum value is not a number"); + } + + JsonNode exclusiveMaximumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MAXIMUM); + if (exclusiveMaximumNode != null && exclusiveMaximumNode.isBoolean()) { + this.excludeEqual = exclusiveMaximumNode.booleanValue(); + } else { + this.excludeEqual = false; + } + + final String maximumText = schemaNode.asText(); + if ((schemaNode.isLong() || schemaNode.isInt()) && (JsonType.INTEGER.toString().equals(getNodeFieldType()))) { + // "integer", and within long range + final long lm = schemaNode.asLong(); + this.typedMaximum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (node.isBigInteger()) { + //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. + int compare = node.bigIntegerValue().compareTo(new BigInteger(schemaNode.asText())); + return compare > 0 || (excludeEqual && compare == 0); + + } else if (node.isTextual()) { + BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + long val = node.asLong(); + return lm < val || (excludeEqual && lm == val); + } + + @Override + public String thresholdValue() { + return String.valueOf(lm); + } + }; + } else { + this.typedMaximum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + final BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (!JsonNodeTypes.isNumber(node, this.schemaContext.getSchemaRegistryConfig())) { + // maximum only applies to numbers + return; + } + + if (this.typedMaximum.crossesThreshold(node)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.typedMaximum.thresholdValue()).build()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java b/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java new file mode 100644 index 000000000..64a1b8aae --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for minItems. + */ +public class MinItemsValidator extends BaseKeywordValidator implements KeywordValidator { + private int min = 0; + + public MinItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MIN_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.canConvertToExactIntegral()) { + min = schemaNode.intValue(); + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (node.isArray()) { + if (node.size() < min) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(min, node.size()) + .build()); + } + } else if (this.schemaContext.getSchemaRegistryConfig().isTypeLoose()) { + if (1 < min) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(min, 1).build()); + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java b/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java new file mode 100644 index 000000000..c19f70709 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for minLength. + */ +public class MinLengthValidator extends BaseKeywordValidator implements KeywordValidator { + private int minLength; + + public MinLengthValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MIN_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext); + minLength = Integer.MIN_VALUE; + if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { + minLength = schemaNode.intValue(); + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + // ignore non-string types + return; + } + + if (node.textValue().codePointCount(0, node.textValue().length()) < minLength) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(minLength).build()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java b/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java new file mode 100644 index 000000000..2ba469004 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java @@ -0,0 +1,97 @@ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * {@link KeywordValidator} for {@literal maxContains} and {@literal minContains} in a schema. + *

+ * This validator only checks that the schema is valid. The functionality for + * testing whether an instance array conforms to the {@literal maxContains} + * and {@literal minContains} constraints exists within {@code ContainsValidator}. + */ +public class MinMaxContainsValidator extends BaseKeywordValidator { + private final Set analysis; + + public MinMaxContainsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, + SchemaContext schemaContext) { + super(KeywordType.MAX_CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext); + + Set analysis = null; + int min = 1; + int max = Integer.MAX_VALUE; + + JsonNode minNode = parentSchema.getSchemaNode().get("minContains"); + if (null != minNode) { + if (!minNode.isNumber() || !minNode.canConvertToExactIntegral() || minNode.intValue() < 0) { + if (analysis == null) { + analysis = new LinkedHashSet<>(); + } + analysis.add(new Analysis("minContains", schemaLocation)); + } else { + min = minNode.intValue(); + } + } + + JsonNode maxNode = parentSchema.getSchemaNode().get("maxContains"); + if (null != maxNode) { + if (!maxNode.isNumber() || !maxNode.canConvertToExactIntegral() || maxNode.intValue() < 0) { + if (analysis == null) { + analysis = new LinkedHashSet<>(); + } + analysis.add(new Analysis("maxContains", schemaLocation)); + } else { + max = maxNode.intValue(); + } + } + + if (max < min) { + if (analysis == null) { + analysis = new LinkedHashSet<>(); + } + analysis.add(new Analysis("minContainsVsMaxContains", schemaLocation)); + } + this.analysis = analysis; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + if (this.analysis != null) { + this.analysis.stream() + .map(analysis -> error().instanceNode(node) + .instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .messageKey(analysis.getMessageKey()).locale(executionContext.getExecutionConfig().getLocale()) + .keyword(analysis.getMessageKey()) + .arguments(parentSchema.getSchemaNode().toString()).build()) + .forEach(executionContext::addError); + } + } + + public static class Analysis { + public String getMessageKey() { + return messageKey; + } + + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + private final String messageKey; + private final SchemaLocation schemaLocation; + + public Analysis(String messageKey, SchemaLocation schemaLocation) { + super(); + this.messageKey = messageKey; + this.schemaLocation = schemaLocation; + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java new file mode 100644 index 000000000..69fea4e8a --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for minProperties. + */ +public class MinPropertiesValidator extends BaseKeywordValidator implements KeywordValidator { + protected final int min; + + public MinPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, + SchemaContext schemaContext) { + super(KeywordType.MIN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.canConvertToExactIntegral()) { + min = schemaNode.intValue(); + } else { + min = 0; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (node.isObject()) { + if (node.size() < min) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(min).build()); + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MinimumValidator.java b/src/main/java/com/networknt/schema/keyword/MinimumValidator.java new file mode 100644 index 000000000..627b6895c --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MinimumValidator.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; +import com.networknt.schema.utils.JsonType; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * {@link KeywordValidator} for minimum. + */ +public class MinimumValidator extends BaseKeywordValidator { + private static final String PROPERTY_EXCLUSIVE_MINIMUM = "exclusiveMinimum"; + + private final boolean excludeEqual; + + /** + * In order to limit number of `if` statements in `validate` method, all the + * logic of picking the right comparison is abstracted into a mixin. + */ + private final ThresholdMixin typedMinimum; + + public MinimumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); + + if (!schemaNode.isNumber()) { + throw new SchemaException("minimum value is not a number"); + } + + JsonNode exclusiveMinimumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MINIMUM); + if (exclusiveMinimumNode != null && exclusiveMinimumNode.isBoolean()) { + this.excludeEqual = exclusiveMinimumNode.booleanValue(); + } else { + this.excludeEqual = false; + } + + final String minimumText = schemaNode.asText(); + if ((schemaNode.isLong() || schemaNode.isInt()) && JsonType.INTEGER.toString().equals(getNodeFieldType())) { + // "integer", and within long range + final long lmin = schemaNode.asLong(); + this.typedMinimum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + if (node.isBigInteger()) { + //node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number. + int compare = node.bigIntegerValue().compareTo(new BigInteger(minimumText)); + return compare < 0 || (excludeEqual && compare == 0); + + } else if (node.isTextual()) { + BigDecimal min = new BigDecimal(minimumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(min); + return compare < 0 || (excludeEqual && compare == 0); + + } + long val = node.asLong(); + return lmin > val || (excludeEqual && lmin == val); + } + + @Override + public String thresholdValue() { + return String.valueOf(lmin); + } + }; + + } else { + this.typedMinimum = new ThresholdMixin() { + @Override + public boolean crossesThreshold(JsonNode node) { + // jackson's BIG_DECIMAL parsing is limited. see https://github.com/FasterXML/jackson-databind/issues/1770 + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + final BigDecimal min = new BigDecimal(minimumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(min); + return compare < 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return minimumText; + } + }; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (!JsonNodeTypes.isNumber(node, this.schemaContext.getSchemaRegistryConfig())) { + // minimum only applies to numbers + return; + } + + if (this.typedMinimum.crossesThreshold(node)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.typedMinimum.thresholdValue()).build()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java new file mode 100644 index 000000000..a2326601c --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; + +import java.math.BigDecimal; + +/** + * {@link KeywordValidator} for multipleOf. + */ +public class MultipleOfValidator extends BaseKeywordValidator implements KeywordValidator { + private final BigDecimal divisor; + + public MultipleOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MULTIPLE_OF, schemaNode, schemaLocation, parentSchema, schemaContext); + this.divisor = getDivisor(schemaNode); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + + if (this.divisor != null) { + BigDecimal dividend = getDividend(node); + if (dividend != null) { + if (dividend.divideAndRemainder(this.divisor)[1].abs().compareTo(BigDecimal.ZERO) > 0) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.divisor) + .build()); + } + } + } + } + + /** + * Gets the divisor to use. + * + * @param schemaNode the schema node + * @return the divisor or null if the input is not correct + */ + protected BigDecimal getDivisor(JsonNode schemaNode) { + if (schemaNode.isNumber()) { + double divisor = schemaNode.doubleValue(); + if (divisor != 0) { + // convert to BigDecimal since double type is not accurate enough to do the + // division and multiple + return schemaNode.isBigDecimal() ? schemaNode.decimalValue() : BigDecimal.valueOf(divisor); + } + } + return null; + } + + /** + * Gets the dividend to use. + * + * @param node the node + * @return the dividend or null if the type is incorrect + */ + protected BigDecimal getDividend(JsonNode node) { + if (node.isNumber()) { + // convert to BigDecimal since double type is not accurate enough to do the + // division and multiple + return node.isBigDecimal() ? node.decimalValue() : BigDecimal.valueOf(node.doubleValue()); + } else if (this.schemaContext.getSchemaRegistryConfig().isTypeLoose() + && JsonNodeTypes.isNumber(node, this.schemaContext.getSchemaRegistryConfig())) { + // handling for type loose + return new BigDecimal(node.textValue()); + } + return null; + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java b/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java new file mode 100644 index 000000000..e49236084 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.Iterator; +import java.util.Map.Entry; + +/** + * Used for Keywords that have no validation aspect, but are part of the metaschema. + */ +public class NonValidationKeyword extends AbstractKeyword { + + private static final class Validator extends AbstractKeywordValidator { + public Validator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext, Keyword keyword) { + super(keyword, schemaNode, schemaLocation); + String id = schemaContext.resolveSchemaId(schemaNode); + String anchor = schemaContext.getDialect().readAnchor(schemaNode); + String dynamicAnchor = schemaContext.getDialect().readDynamicAnchor(schemaNode); + if (id != null || anchor != null || dynamicAnchor != null) { + // Used to register schema resources with $id + schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } + if ("$defs".equals(keyword.getValue()) || "definitions".equals(keyword.getValue())) { + for (Iterator> field = schemaNode.fields(); field.hasNext(); ) { + Entry property = field.next(); + SchemaLocation location = schemaLocation.append(property.getKey()); + Schema schema = schemaContext.newSchema(location, + property.getValue(), parentSchema); + schemaContext.getSchemaReferences().put(location.toString(), schema); + } + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + // Do nothing + } + } + + public NonValidationKeyword(String keyword) { + super(keyword); + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + return new Validator(schemaLocation, schemaNode, parentSchema, schemaContext, this); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java b/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java new file mode 100644 index 000000000..7f09b7382 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for notAllowed. + */ +public class NotAllowedValidator extends BaseKeywordValidator implements KeywordValidator { + private final List fieldNames; + + public NotAllowedValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.NOT_ALLOWED, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.isArray()) { + int size = schemaNode.size(); + this.fieldNames = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + fieldNames.add(schemaNode.get(i).asText()); + } + } else { + this.fieldNames = Collections.emptyList(); + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + for (String fieldName : fieldNames) { + JsonNode propertyNode = node.get(fieldName); + + if (propertyNode != null) { + executionContext.addError(error().property(fieldName).instanceNode(node) + .evaluationPath(executionContext.getEvaluationPath()).instanceLocation(instanceLocation.append(fieldName)) + .locale(executionContext.getExecutionConfig().getLocale()) + .arguments(fieldName).build()); + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/NotValidator.java b/src/main/java/com/networknt/schema/keyword/NotValidator.java new file mode 100644 index 000000000..a183c7636 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/NotValidator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for not. + */ +public class NotValidator extends BaseKeywordValidator { + private final Schema schema; + + public NotValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.NOT, schemaNode, schemaLocation, parentSchema, schemaContext); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + + + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + List existingErrors = executionContext.getErrors(); + List test = new ArrayList<>(); + executionContext.setErrors(test); + try { + executionContext.setFailFast(false); + if (!walk) { + this.schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + this.schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + // Restore flag + executionContext.setFailFast(failFast); + executionContext.setErrors(existingErrors); + } + if (test.isEmpty()) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.schemaNode.toString()) + .build()); + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + if (shouldValidateSchema && node != null) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + + this.schema.walk(executionContext, node, rootNode, instanceLocation, false); + } + + @Override + public void preloadSchema() { + if (null != this.schema) { + this.schema.initializeValidators(); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/OneOfValidator.java b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java new file mode 100644 index 000000000..4358daadf --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * {@link KeywordValidator} for oneOf. + */ +public class OneOfValidator extends BaseKeywordValidator { + private final List schemas; + + public OneOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ONE_OF, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isArray()) { + JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); + throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) + .messageKey("type").arguments(nodeType.toString(), "array").build()); + } + int size = schemaNode.size(); + this.schemas = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + JsonNode childNode = schemaNode.get(i); + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), childNode, + parentSchema)); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + int numberOfValidSchema = 0; + int index = 0; + List indexes = null; + List existingErrors = executionContext.getErrors(); + List allErrors = null; // Keeps track of all the errors for reporting if in the end none or more than + // one sub schema matches + List discriminatorErrors = null; // The errors from the sub schema that match the discriminator + List subSchemaErrors = new ArrayList<>(); // Temporary errors from each sub schema execution + + executionContext.setErrors(subSchemaErrors); + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + Boolean canShortCircuit = null; + int schemaIndex = 0; + try { + executionContext.setFailFast(false); + for (Schema schema : this.schemas) { + subSchemaErrors.clear(); + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + + // check if any validation errors have occurred + if (subSchemaErrors.isEmpty()) { // No new errors + numberOfValidSchema++; + if (indexes == null) { + indexes = new ArrayList<>(); + } + indexes.add(Integer.toString(index)); + } + + if (numberOfValidSchema > 1) { + if (canShortCircuit == null) { + canShortCircuit = canShortCircuit(executionContext); + } + if (canShortCircuit) { + // short-circuit + // note that the short circuit means that only 2 valid schemas are reported even + // if could be more + break; + } + } + + if (this.schemaContext.isDiscriminatorKeywordEnabled()) { + boolean discriminatorMatchFound = false; + DiscriminatorState discriminator = executionContext.getDiscriminatorMapping().get(instanceLocation); + JsonNode refNode = schema.getSchemaNode().get("$ref"); + if (discriminator != null && refNode != null && discriminator.hasDiscriminatingValue()) { + discriminatorMatchFound = discriminator.matches(refNode.asText()); + if (discriminatorMatchFound) { + /* + * Note that discriminator cannot change the outcome of the evaluation but can + * be used to filter off any additional messages + * + * The discriminator will cause all messages other than the one with the // + * matching discriminator to be discarded. + */ + if (!subSchemaErrors.isEmpty()) { + /* + * This means that the discriminated value has errors and doesn't match so these + * errors are the only ones that will be reported *IF* there are no other + * schemas that successfully validate to meet the requirement of anyOf. + * + * If there are any successful schemas as per anyOf, all these errors will be + * discarded. + */ + discriminatorErrors = new ArrayList<>(subSchemaErrors); + allErrors = null; // This is no longer needed + } + } + } else { + // This is the normal handling when discriminators aren't enabled + if (discriminatorErrors == null) { + if (allErrors == null) { + allErrors = new ArrayList<>(); + } + allErrors.addAll(subSchemaErrors); + } + } + } else if (!subSchemaErrors.isEmpty() && reportChildErrors(executionContext)) { + // This is the normal handling when discriminators aren't enabled + if (allErrors == null) { + allErrors = new ArrayList<>(); + } + allErrors.addAll(subSchemaErrors); + } + index++; + } + + if (this.schemaContext.isDiscriminatorKeywordEnabled()) { + /* + * The only case where the discriminator can change the outcome of the result is + * if the discriminator value does not match an implicit or explicit mapping + */ + /* + * If the discriminator value does not match an implicit or explicit mapping, no + * schema can be determined and validation SHOULD fail. Mapping keys MUST be + * string values, but tooling MAY convert response values to strings for + * comparison. + * + * https://spec.openapis.org/oas/v3.1.2#examples-0 + */ + DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation); + if (state != null && !state.hasMatchedSchema() && state.hasDiscriminatingValue()) { + // The check for state.hasDiscriminatingValue is due to issue 988 + // Note that this is related to the DiscriminatorValidator by default not + // generating an assertion + // if the discriminatingValue is not set in the payload + existingErrors + .add(error().keyword("discriminator").instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .messageKey("discriminator.oneOf.no_match_found") + .arguments(state.getDiscriminatingValue()).build()); + } + } + } finally { + // Restore flag + executionContext.setFailFast(failFast); + } + + if (numberOfValidSchema != 1) { + /* + * Ensure there is always an "oneOf" error reported if number of valid schemas + * is not equal to 1 + */ + Error message = error().instanceNode(node).instanceLocation(instanceLocation) + .messageKey(numberOfValidSchema > 1 ? "oneOf.indexes" : "oneOf") + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(Integer.toString(numberOfValidSchema), + numberOfValidSchema > 1 ? String.join(", ", indexes) : "") + .build(); + existingErrors.add(message); + + if (discriminatorErrors != null) { + existingErrors.addAll(discriminatorErrors); + } else if (allErrors != null) { + existingErrors.addAll(allErrors); + } + } + executionContext.setErrors(existingErrors); + return; + } + + /** + * Determines if child errors should be reported. + * + * @param executionContext the execution context + * @return true if child errors should be reported + */ + protected boolean reportChildErrors(ExecutionContext executionContext) { + // check the original flag if it's going to fail fast anyway + // no point aggregating all the errors + return !executionContext.getExecutionConfig().isFailFast(); + } + + protected boolean canShortCircuit(ExecutionContext executionContext) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext)) { + return false; + } + if (hasUnevaluatedPropertiesInEvaluationPath(executionContext)) { + return false; + } + return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled(); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, + boolean shouldValidateSchema) { + if (shouldValidateSchema && node != null) { + validate(executionContext, node, rootNode, instanceLocation, true); + } else { + int schemaIndex = 0; + for (Schema schema : this.schemas) { + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + } + } + } + + @Override + public void preloadSchema() { + for (Schema schema : this.schemas) { + schema.initializeValidators(); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java new file mode 100644 index 000000000..6b262ed79 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.regex.RegularExpression; +import java.util.*; + +/** + * {@link KeywordValidator} for patternProperties. + */ +public class PatternPropertiesValidator extends BaseKeywordValidator { + public static final String PROPERTY = "patternProperties"; + private final Map schemas = new IdentityHashMap<>(); + + public PatternPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, + SchemaContext schemaContext) { + super(KeywordType.PATTERN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + if (!schemaNode.isObject()) { + throw new SchemaException("patternProperties must be an object node"); + } + Iterator names = schemaNode.fieldNames(); + while (names.hasNext()) { + String name = names.next(); + RegularExpression pattern = RegularExpression.compile(name, schemaContext); + schemas.put(pattern, schemaContext.newSchema(schemaLocation.append(name), + schemaNode.get(name), parentSchema)); + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (!node.isObject()) { + return; + } + Set matchedInstancePropertyNames = null; + Iterator names = node.fieldNames(); + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) || collectAnnotations(executionContext); + while (names.hasNext()) { + String name = names.next(); + JsonNode n = node.get(name); + for (Map.Entry entry : schemas.entrySet()) { + if (entry.getKey().matches(name)) { + NodePath path = instanceLocation.append(name); + int currentErrors = executionContext.getErrors().size(); + Schema schema = entry.getValue(); + executionContext.evaluationPathAddLast(schema.getSchemaLocation().getFragment().getElement(-1).toString()); + try { + schema.validate(executionContext, n, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } + if (currentErrors == executionContext.getErrors().size()) { // No new errors + if (collectAnnotations) { + if (matchedInstancePropertyNames == null) { + matchedInstancePropertyNames = new LinkedHashSet<>(); + } + matchedInstancePropertyNames.add(name); + } + } + } + } + } + if (collectAnnotations) { + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()) + .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames + : Collections.emptySet()) + .build()); + } + } + + @Override + public void preloadSchema() { + preloadSchemas(schemas.values()); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/PatternValidator.java b/src/main/java/com/networknt/schema/keyword/PatternValidator.java new file mode 100644 index 000000000..b5fcc51b1 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PatternValidator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.FailFastAssertionException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.regex.RegularExpression; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class PatternValidator extends BaseKeywordValidator { + private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class); + private final String pattern; + private final RegularExpression compiledPattern; + + public PatternValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PATTERN, schemaNode, schemaLocation, parentSchema, schemaContext); + + this.pattern = Optional.ofNullable(schemaNode).filter(JsonNode::isTextual).map(JsonNode::textValue).orElse(null); + try { + this.compiledPattern = RegularExpression.compile(this.pattern, schemaContext); + } catch (RuntimeException e) { + e.setStackTrace(new StackTraceElement[0]); + logger.error("Failed to compile pattern '{}': {}", this.pattern, e.getMessage()); + throw e; + } + } + + private boolean matches(String value) { + return this.compiledPattern.matches(value); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.STRING) { + return; + } + + try { + if (!matches(node.asText())) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(this.pattern).build()); + return; + } + } catch (SchemaException | FailFastAssertionException e) { + throw e; + } catch (RuntimeException e) { + logger.error("Failed to apply pattern '{}' at {}: {}", this.pattern, instanceLocation, e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java new file mode 100644 index 000000000..a81f6c60d --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2022 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.SchemaRefs; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link KeywordValidator} for prefixItems. + */ +public class PrefixItemsValidator extends BaseKeywordValidator { + private final List tupleSchema; + + public PrefixItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PREFIX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + + if (schemaNode instanceof ArrayNode && !schemaNode.isEmpty()) { + int i = 0; + this.tupleSchema = new ArrayList<>(schemaNode.size()); + for (JsonNode s : schemaNode) { + this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), s, + parentSchema)); + i++; + } + } else { + throw new IllegalArgumentException("The value of 'prefixItems' MUST be a non-empty array of valid JSON Schemas."); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + // ignores non-arrays + if (node.isArray()) { + int count = Math.min(node.size(), this.tupleSchema.size()); + for (int i = 0; i < count; ++i) { + NodePath path = instanceLocation.append(i); + executionContext.evaluationPathAddLast(i); + try { + this.tupleSchema.get(i).validate(executionContext, node.get(i), rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + + // Add annotation + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { + // Tuples + int items = node.isArray() ? node.size() : 1; + int schemas = this.tupleSchema.size(); + if (items > schemas) { + // More items than schemas so the keyword only applied to the number of schemas + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(schemas).build()); + } else { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + if (node instanceof ArrayNode) { + ArrayNode array = (ArrayNode) node; + int count = this.tupleSchema.size(); + for (int i = 0; i < count; ++i) { + JsonNode n = node.get(i); + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { + JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i), executionContext); + if (n != null) { + // Defaults only set if array index is explicitly null + if (n.isNull() && defaultNode != null) { + array.set(i, defaultNode); + n = defaultNode; + } + } + } + doWalk(executionContext, i, n, rootNode, instanceLocation, shouldValidateSchema); + } + + // Add annotation + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { + // Tuples + int items = node.isArray() ? node.size() : 1; + int schemas = this.tupleSchema.size(); + if (items > schemas) { + // More items than schemas so the keyword only applied to the number of schemas + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(schemas).build()); + } else { + // Applies to all + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(true).build()); + } + } + } else { + int count = this.tupleSchema.size(); + for (int i = 0; i < count; ++i) { + doWalk(executionContext, i, null, rootNode, instanceLocation, shouldValidateSchema); + } + } + } + + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema(), executionContext); + } + } + return result; + } + + private void doWalk(ExecutionContext executionContext, int i, + JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + walkSchema(executionContext, i, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i), + shouldValidateSchema); + } + + private void walkSchema(ExecutionContext executionContext, int schemaIndex, Schema walkSchema, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + //@formatter:off + boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner().runPreWalkListeners( + executionContext, + KeywordType.PREFIX_ITEMS.getValue(), + node, + rootNode, + instanceLocation, + walkSchema, this + ); + int currentErrors = executionContext.getErrors().size(); + if (executeWalk) { + executionContext.evaluationPathAddLast(schemaIndex); + try { + walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners( + executionContext, + KeywordType.PREFIX_ITEMS.getValue(), + node, + rootNode, + instanceLocation, + walkSchema, + this, executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size()) + ); + //@formatter:on + } + + public List getTupleSchema() { + return this.tupleSchema; + } + + @Override + public void preloadSchema() { + preloadSchemas(this.tupleSchema); + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java new file mode 100644 index 000000000..884e0e880 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.SchemaRefs; +import com.networknt.schema.walk.WalkListenerRunner; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * {@link KeywordValidator} for properties. + */ +public class PropertiesValidator extends BaseKeywordValidator { + public static final String PROPERTY = "properties"; + private final Map schemas = new LinkedHashMap<>(); + + public PropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + for (Iterator> it = schemaNode.fields(); it.hasNext();) { + Entry entry = it.next(); + String pname = entry.getKey(); + this.schemas.put(pname, schemaContext.newSchema(schemaLocation.append(pname), + entry.getValue(), parentSchema)); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + Set matchedInstancePropertyNames = null; + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) || collectAnnotations(executionContext); + for (Entry entry : this.schemas.entrySet()) { + JsonNode propertyNode = node.get(entry.getKey()); + if (propertyNode != null) { + NodePath path = instanceLocation.append(entry.getKey()); + if (collectAnnotations) { + if (matchedInstancePropertyNames == null) { + matchedInstancePropertyNames = new LinkedHashSet<>(); + } + matchedInstancePropertyNames.add(entry.getKey()); + } + executionContext.evaluationPathAddLast(entry.getKey()); + try { + if (!walk) { + //validate the child element(s) + entry.getValue().validate(executionContext, propertyNode, rootNode, path); + } else { + // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. + walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + } else { + if (walk) { + // This tries to make the walk listener consistent between when validation is + // enabled or disabled as when validation is disabled it will walk where node is + // null. + // The actual walk needs to be skipped as the validators assume that node is not + // null. + executionContext.evaluationPathAddLast(entry.getKey()); + try { + walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, + executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + } + if (collectAnnotations) { + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword(getKeyword()).value(matchedInstancePropertyNames == null ? Collections.emptySet() + : matchedInstancePropertyNames) + .build()); + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyPropertyDefaults() && null != node + && node.getNodeType() == JsonNodeType.OBJECT) { + applyPropertyDefaults((ObjectNode) node, executionContext); + } + if (shouldValidateSchema) { + validate(executionContext, node == null ? MissingNode.getInstance() : node, rootNode, + instanceLocation, true); + } else { + WalkListenerRunner propertyWalkListenerRunner = executionContext.getWalkConfig().getPropertyWalkListenerRunner(); + for (Map.Entry entry : this.schemas.entrySet()) { + executionContext.evaluationPathAddLast(entry.getKey()); + try { + walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, propertyWalkListenerRunner); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + } + + private void applyPropertyDefaults(ObjectNode node, ExecutionContext executionContext) { + for (Map.Entry entry : this.schemas.entrySet()) { + JsonNode propertyNode = node.get(entry.getKey()); + + JsonNode defaultNode = getDefaultNode(entry.getValue(), executionContext); + if (defaultNode == null) { + continue; + } + boolean applyDefault = propertyNode == null || (propertyNode.isNull() && executionContext.getWalkConfig() + .getApplyDefaultsStrategy().shouldApplyPropertyDefaultsIfNull()); + if (applyDefault) { + node.set(entry.getKey(), defaultNode); + } + } + } + + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema(), executionContext); + } + } + return result; + } + + private void walkSchema(ExecutionContext executionContext, Map.Entry entry, JsonNode node, + JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema, WalkListenerRunner propertyWalkListenerRunner) { + Schema propertySchema = entry.getValue(); + JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); + NodePath path = instanceLocation.append(entry.getKey()); + boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(executionContext, + KeywordType.PROPERTIES.getValue(), propertyNode, rootNode, path, + propertySchema, this); + if (propertyNode == null && node != null) { + // Attempt to get the property node again in case the propertyNode was updated + propertyNode = node.get(entry.getKey()); + } + int currentErrors = executionContext.getErrors().size(); + if (executeWalk) { + propertySchema.walk(executionContext, propertyNode, rootNode, path, shouldValidateSchema); + } + propertyWalkListenerRunner.runPostWalkListeners(executionContext, KeywordType.PROPERTIES.getValue(), + propertyNode, rootNode, path, propertySchema, this, + executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); + } + + public Map getSchemas() { + return this.schemas; + } + + @Override + public void preloadSchema() { + preloadSchemas(this.schemas.values()); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java new file mode 100644 index 000000000..fc257ec49 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * {@link KeywordValidator} for propertyDependencies. + */ +public class PropertyDependenciesValidator extends BaseKeywordValidator implements KeywordValidator { + /* + * Property Name -> Property Value -> Schema + */ + private final Map> propertyDependencies; + + public PropertyDependenciesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PROPERTY_DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext); + Set> properties = schemaNode.properties(); + this.propertyDependencies = new LinkedHashMap<>(properties.size()); + for (Entry property : properties) { + String propertyName = property.getKey(); + SchemaLocation propertySchemaLocation = schemaLocation.append(propertyName); + + Set> propertyValues = property.getValue().properties(); + for (Entry propertyValue : propertyValues) { + Map valueSchemas = this.propertyDependencies.computeIfAbsent(propertyName, + key -> new LinkedHashMap<>()); + valueSchemas.put(propertyValue.getKey(), + schemaContext.newSchema(propertySchemaLocation.append(propertyValue.getKey()), + propertyValue.getValue(), + parentSchema)); + } + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + validate(executionContext, node, rootNode, instanceLocation, false); + } + + protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean walk) { + Set> properties = node.properties(); + for (Entry property : properties) { + String propertyName = property.getKey(); + String propertyValue = property.getValue().asText(); + if (propertyValue != null) { + Map propertySchemas = this.propertyDependencies.get(propertyName); + if (propertySchemas != null) { + executionContext.evaluationPathAddLast(propertyName); + try { + Schema schema = propertySchemas.get(propertyValue); + if (schema != null) { + executionContext.evaluationPathAddLast(propertyValue); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + } + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, + boolean shouldValidateSchema) { + if (shouldValidateSchema) { + validate(executionContext, node, rootNode, instanceLocation, true); + return; + } + + for (Entry> property : this.propertyDependencies.entrySet()) { + String propertyName = property.getKey(); + executionContext.evaluationPathAddLast(propertyName); + try { + for (Entry propertyValue : property.getValue().entrySet()) { + executionContext.evaluationPathAddLast(propertyValue.getKey()); + try { + propertyValue.getValue().walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } + + @Override + public void preloadSchema() { + for (Map properties : propertyDependencies.values()) { + + for (Schema schema : properties.values()) { + schema.initializeValidators(); + } + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java new file mode 100644 index 000000000..df64c28ca --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +public class PropertyNamesValidator extends BaseKeywordValidator implements KeywordValidator { + private final Schema innerSchema; + public PropertyNamesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PROPERTY_NAMES, schemaNode, schemaLocation, parentSchema, schemaContext); + innerSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + List existingErrors = executionContext.getErrors(); + List schemaErrors = new ArrayList<>(); + executionContext.setErrors(schemaErrors); + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + final String pname = it.next(); + final TextNode pnameText = TextNode.valueOf(pname); + innerSchema.validate(executionContext, pnameText, node, instanceLocation.append(pname)); + for (final Error schemaError : schemaErrors) { + existingErrors.add( + error().property(pname).instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(pname, schemaError.getMessage()).build()); + } + schemaErrors.clear(); + } + executionContext.setErrors(existingErrors); + } + + + @Override + public void preloadSchema() { + innerSchema.initializeValidators(); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java b/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java new file mode 100644 index 000000000..cd83d4679 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for readOnly. + */ +public class ReadOnlyValidator extends BaseKeywordValidator { + private static final Logger logger = LoggerFactory.getLogger(ReadOnlyValidator.class); + + public ReadOnlyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.READ_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext); + logger.debug("Loaded ReadOnlyValidator for property {} as {}", parentSchema, "read mode"); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + if (Boolean.TRUE.equals(executionContext.getExecutionConfig().getReadOnly())) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .build()); + } + return; + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java new file mode 100644 index 000000000..158528755 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.InvalidSchemaRefException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; + +import java.util.Iterator; + +/** + * {@link KeywordValidator} that resolves $recursiveRef. + */ +public class RecursiveRefValidator extends BaseKeywordValidator { + + public RecursiveRefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.RECURSIVE_REF, schemaNode, schemaLocation, parentSchema, schemaContext); + + String refValue = schemaNode.asText(); + if (!"#".equals(refValue)) { + Error error = error() + .keyword(KeywordType.RECURSIVE_REF.getValue()).messageKey("internal.invalidRecursiveRef") + .message("The value of a $recursiveRef must be '#' but is '{0}'").instanceLocation(schemaLocation.getFragment()) + .instanceNode(this.schemaNode) + .arguments(refValue).build(); + throw new SchemaException(error); + } + } + + static Schema getSchema(Schema parentSchema, ExecutionContext executionContext) { + Schema refSchema = parentSchema.findSchemaResourceRoot(); // Get the document + Schema current = refSchema; + Schema check = null; + String base = null; + String baseCheck = null; + if (refSchema != null) { + base = current.getSchemaLocation().getAbsoluteIri() != null ? current.getSchemaLocation().getAbsoluteIri().toString() : ""; + if (current.isRecursiveAnchor()) { + // Check dynamic scope + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + current = iter.next(); + baseCheck = current.getSchemaLocation().getAbsoluteIri() != null ? current.getSchemaLocation().getAbsoluteIri().toString() : ""; + if (!base.equals(baseCheck)) { + base = baseCheck; + // Check if it has a dynamic anchor + check = current.findSchemaResourceRoot(); + if (check.isRecursiveAnchor()) { + refSchema = check; + } + } + } + } + } + return refSchema; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + Schema refSchema = getSchemaRef(executionContext).getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.RECURSIVE_REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + refSchema.validate(executionContext, node, rootNode, instanceLocation); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + + // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, + // these schemas will be cached along with config. We have to replace the config for cached $ref references + // with the latest config. Reset the config. + Schema refSchema = getSchemaRef(executionContext).getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.RECURSIVE_REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + if (node == null) { + // Check for circular dependency + boolean circularDependency = false; + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); + if (check.getSchemaLocation().equals(schemaLocation)) { + circularDependency = true; + break; + } + } + if (circularDependency) { + return; + } + } + refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + + public SchemaRef getSchemaRef(ExecutionContext executionContext) { + return new SchemaRef(() -> getSchema(this.parentSchema, executionContext)); + } +} diff --git a/src/main/java/com/networknt/schema/keyword/RefValidator.java b/src/main/java/com/networknt/schema/keyword/RefValidator.java new file mode 100644 index 000000000..4ea868615 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/RefValidator.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.InvalidSchemaRefException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.ThreadSafeCachingSupplier; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; + +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * {@link KeywordValidator} that resolves $ref. + */ +public class RefValidator extends BaseKeywordValidator { + protected final SchemaRef schema; + + private static final String REF_CURRENT = "#"; + + public RefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.REF, schemaNode, schemaLocation, parentSchema, schemaContext); + String refValue = schemaNode.asText(); + this.schema = getRefSchema(parentSchema, schemaContext, refValue); + } + + static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, String refValue) { + // The evaluationPath is used to derive the keywordLocation + final String refValueOriginal = refValue; + + if (!refValue.startsWith(REF_CURRENT)) { + // This will be the uri extracted from the refValue (this may be a relative or absolute uri). + final String refUri; + final int index = refValue.indexOf(REF_CURRENT); + if (index > 0) { + refUri = refValue.substring(0, index); + } else { + refUri = refValue; + } + + // This will determine the correct absolute uri for the refUri. This decision will take into + // account the current uri of the parent schema. + String schemaUriFinal = resolve(parentSchema, refUri); + SchemaLocation schemaLocation = SchemaLocation.of(schemaUriFinal); + // This should retrieve schemas regardless of the protocol that is in the uri. + return new SchemaRef(getSupplier(() -> { + Schema schemaResource = schemaContext.getSchemaResources().get(schemaUriFinal); + if (schemaResource == null) { + schemaResource = schemaContext.getSchemaRegistry().loadSchema(schemaLocation); + if (schemaResource != null) { + copySchemaResources(schemaContext, schemaResource); + } + } + if (index < 0) { + if (schemaResource == null) { + return null; + } + return schemaResource; + } else { + String newRefValue = refValue.substring(index); + String find = schemaLocation.getAbsoluteIri() + newRefValue; + Schema findSchemaResource = schemaContext.getSchemaResources().get(find); + if (findSchemaResource == null) { + findSchemaResource = schemaContext.getDynamicAnchors().get(find); + } + if (findSchemaResource != null) { + schemaResource = findSchemaResource; + } else { + schemaResource = getSchema(schemaResource, schemaContext, newRefValue, refValueOriginal + ); + } + if (schemaResource == null) { + return null; + } + return schemaResource; + } + }, schemaContext.getSchemaRegistryConfig().isCacheRefs())); + + } else if (SchemaLocation.Fragment.isAnchorFragment(refValue)) { + String absoluteIri = resolve(parentSchema, refValue); + // Schema resource needs to update the parent and evaluation path + return new SchemaRef(getSupplier(() -> { + Schema schemaResource = schemaContext.getSchemaResources().get(absoluteIri); + if (schemaResource == null) { + schemaResource = schemaContext.getDynamicAnchors().get(absoluteIri); + } + if (schemaResource == null) { + schemaResource = getSchema(parentSchema, schemaContext, refValue, refValueOriginal); + } + if (schemaResource == null) { + return null; + } + return schemaResource; + }, schemaContext.getSchemaRegistryConfig().isCacheRefs())); + } + if (refValue.equals(REF_CURRENT)) { + return new SchemaRef( + getSupplier(() -> parentSchema.findSchemaResourceRoot(), + schemaContext.getSchemaRegistryConfig().isCacheRefs())); + } + return new SchemaRef(getSupplier( + () -> getSchema(parentSchema, schemaContext, refValue, refValueOriginal), + schemaContext.getSchemaRegistryConfig().isCacheRefs())); + } + + static Supplier getSupplier(Supplier supplier, boolean cache) { + return cache ? new ThreadSafeCachingSupplier<>(supplier) : supplier; + } + + private static void copySchemaResources(SchemaContext schemaContext, Schema schemaResource) { + if (!schemaResource.getSchemaContext().getSchemaResources().isEmpty()) { + schemaContext.getSchemaResources() + .putAll(schemaResource.getSchemaContext().getSchemaResources()); + } + if (!schemaResource.getSchemaContext().getSchemaReferences().isEmpty()) { + schemaContext.getSchemaReferences() + .putAll(schemaResource.getSchemaContext().getSchemaReferences()); + } + if (!schemaResource.getSchemaContext().getDynamicAnchors().isEmpty()) { + schemaContext.getDynamicAnchors() + .putAll(schemaResource.getSchemaContext().getDynamicAnchors()); + } + } + + private static String resolve(Schema parentSchema, String refValue) { + // $ref prevents a sibling $id from changing the base uri + Schema base = parentSchema; + if (parentSchema.getId() != null && parentSchema.getParentSchema() != null) { + base = parentSchema.getParentSchema(); + } + return SchemaLocation.resolve(base.getSchemaLocation(), refValue); + } + + private static Schema getSchema(Schema parent, + SchemaContext schemaContext, + String refValue, + String refValueOriginal + ) { + String schemaReference = resolve(parent, refValueOriginal); + // ConcurrentHashMap computeIfAbsent does not allow calls that result in a + // recursive update to the map. + // The getSubSchema potentially recurses to call back to getJsonSchema again + Schema result = schemaContext.getSchemaReferences().get(schemaReference); + if (result == null) { + // This should be processing json pointer fragments only + NodePath fragment = SchemaLocation.Fragment.of(refValue); + synchronized (schemaContext.getSchemaRegistry()) { // acquire lock on shared factory object to prevent deadlock + result = schemaContext.getSchemaReferences().get(schemaReference); + if (result == null) { + result = parent.getSubSchema(fragment); + if (result != null) { + schemaContext.getSchemaReferences().put(schemaReference, result); + } + } + } + } + return result; + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + Schema refSchema = this.schema.getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + refSchema.validate(executionContext, node, rootNode, instanceLocation); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + + // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, + // these schemas will be cached along with config. We have to replace the config for cached $ref references + // with the latest config. Reset the config. + Schema refSchema = this.schema.getSchema(); + if (refSchema == null) { + Error error = error().keyword(KeywordType.REF.getValue()) + .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .arguments(schemaNode.asText()).build(); + throw new InvalidSchemaRefException(error); + } + if (node == null) { + // Check for circular dependency + boolean circularDependency = false; + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); + if (check.getSchemaLocation().equals(schemaLocation)) { + circularDependency = true; + break; + } + } + if (circularDependency) { + return; + } + } + refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + + public SchemaRef getSchemaRef() { + return this.schema; + } + + @Override + public void preloadSchema() { + Schema jsonSchema = null; + try { + jsonSchema = this.schema.getSchema(); + } catch (SchemaException e) { + throw e; + } catch (RuntimeException e) { + throw new SchemaException(e); + } + jsonSchema.initializeValidators(); + // Check for circular dependency + // Only one cycle is pre-loaded + // The rest of the cycles will load at execution time depending on the input + // data + /* + SchemaLocation schemaLocation = jsonSchema.getSchemaLocation(); + Schema check = jsonSchema; + boolean circularDependency = false; + int depth = 0; + while (check.getEvaluationParentSchema() != null) { + depth++; + check = check.getEvaluationParentSchema(); + if (check.getSchemaLocation().equals(schemaLocation)) { + circularDependency = true; + break; + } + } + if (this.schemaContext.getSchemaRegistryConfig().isCacheRefs() && !circularDependency + && depth < this.schemaContext.getSchemaRegistryConfig().getPreloadSchemaRefMaxNestingDepth()) { + jsonSchema.initializeValidators(); + } + */ + } +} diff --git a/src/main/java/com/networknt/schema/keyword/RequiredValidator.java b/src/main/java/com/networknt/schema/keyword/RequiredValidator.java new file mode 100644 index 000000000..7f4dde24e --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/RequiredValidator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.*; + +/** + * {@link KeywordValidator} for required. + */ +public class RequiredValidator extends BaseKeywordValidator implements KeywordValidator { + private final List fieldNames; + + public RequiredValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.isArray()) { + this.fieldNames = new ArrayList<>(schemaNode.size()); + for (JsonNode fieldNme : schemaNode) { + fieldNames.add(fieldNme.asText()); + } + } else { + this.fieldNames = Collections.emptyList(); + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (!node.isObject()) { + return; + } + + for (String fieldName : fieldNames) { + JsonNode propertyNode = node.get(fieldName); + + if (propertyNode == null) { + Boolean readOnly = executionContext.getExecutionConfig().getReadOnly(); + Boolean writeOnly = executionContext.getExecutionConfig().getWriteOnly(); + if (Boolean.TRUE.equals(readOnly)) { + JsonNode readOnlyNode = getFieldKeyword(fieldName, "readOnly"); + if (readOnlyNode != null && readOnlyNode.booleanValue()) { + continue; + } + } else if(Boolean.TRUE.equals(writeOnly)) { + JsonNode writeOnlyNode = getFieldKeyword(fieldName, "writeOnly"); + if (writeOnlyNode != null && writeOnlyNode.booleanValue()) { + continue; + } + } + /** + * Note that for the required validation the instanceLocation does not contain the missing property + *

+ * @see Basic + */ + executionContext.addError(error().instanceNode(node).property(fieldName).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(fieldName).build()); + } + } + } + + protected JsonNode getFieldKeyword(String fieldName, String keyword) { + JsonNode propertiesNode = this.parentSchema.getSchemaNode().get("properties"); + if (propertiesNode != null) { + JsonNode fieldNode = propertiesNode.get(fieldName); + if (fieldNode != null) { + return fieldNode.get(keyword); + } + } + return null; + } +} diff --git a/src/main/java/com/networknt/schema/ThresholdMixin.java b/src/main/java/com/networknt/schema/keyword/ThresholdMixin.java similarity index 95% rename from src/main/java/com/networknt/schema/ThresholdMixin.java rename to src/main/java/com/networknt/schema/keyword/ThresholdMixin.java index 99399fb23..de67ea498 100644 --- a/src/main/java/com/networknt/schema/ThresholdMixin.java +++ b/src/main/java/com/networknt/schema/keyword/ThresholdMixin.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.networknt.schema; +package com.networknt.schema.keyword; import com.fasterxml.jackson.databind.JsonNode; diff --git a/src/main/java/com/networknt/schema/keyword/TrueValidator.java b/src/main/java/com/networknt/schema/keyword/TrueValidator.java new file mode 100644 index 000000000..fad0d0573 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/TrueValidator.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for true. + */ +public class TrueValidator extends BaseKeywordValidator implements KeywordValidator { + public TrueValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TRUE, schemaNode, schemaLocation, parentSchema, schemaContext); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + // For the true validator, it is always valid which means there is no Error. + } +} diff --git a/src/main/java/com/networknt/schema/keyword/TypeValidator.java b/src/main/java/com/networknt/schema/keyword/TypeValidator.java new file mode 100644 index 000000000..70abe92e6 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/TypeValidator.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.utils.JsonNodeTypes; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * {@link KeywordValidator} for type. + */ +public class TypeValidator extends BaseKeywordValidator { + private final JsonType schemaType; + private final UnionTypeValidator unionTypeValidator; + + public TypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); + this.schemaType = TypeFactory.getSchemaNodeType(schemaNode); + if (this.schemaType == JsonType.UNION) { + this.unionTypeValidator = new UnionTypeValidator(schemaLocation, schemaNode, parentSchema, schemaContext); + } else { + this.unionTypeValidator = null; + } + } + + public JsonType getSchemaType() { + return this.schemaType; + } + + public boolean equalsToSchemaType(JsonNode node, ExecutionContext executionContext) { + return JsonNodeTypes.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.schemaContext, executionContext); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (this.schemaType == JsonType.UNION) { + this.unionTypeValidator.validate(executionContext, node, rootNode, instanceLocation); + return; + } + + if (!equalsToSchemaType(node, executionContext)) { + JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(nodeType.toString(), this.schemaType.toString()).build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java b/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java new file mode 100644 index 000000000..ddeedd319 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2023 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; + +import static com.networknt.schema.SpecificationVersionRange.MIN_DRAFT_2020_12; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * {@link KeywordValidator} for unevaluatedItems. + */ +public class UnevaluatedItemsValidator extends BaseKeywordValidator { + private final Schema schema; + + private final boolean isMinV202012; + + public UnevaluatedItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.UNEVALUATED_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + isMinV202012 = MIN_DRAFT_2020_12.getVersions().contains(schemaContext.getDialect().getSpecificationVersion()); + if (schemaNode.isObject() || schemaNode.isBoolean()) { + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } else { + throw new IllegalArgumentException("The value of 'unevaluatedItems' MUST be a valid JSON Schema."); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (!node.isArray()) { + return; + } + + + /* + * Keywords renamed in 2020-12 + * + * items -> prefixItems additionalItems -> items + */ + String itemsKeyword = isMinV202012 ? "prefixItems" : "items"; + String additionalItemsKeyword = isMinV202012 ? "items" : "additionalItems"; + + boolean valid = false; + int validCount = 0; + + // This indicates whether the "unevaluatedItems" subschema was used for + // evaluated for setting the annotation + boolean evaluated = false; + + // Get all the valid adjacent annotations + Predicate validEvaluationPathFilter = a -> a.isValid(); + //Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); + + Predicate adjacentEvaluationPathFilter = a -> a.getEvaluationPath() + .startsWith(executionContext.getEvaluationPath().getParent()); + + List instanceLocationAnnotations = executionContext.getAnnotations().asMap() + .getOrDefault(instanceLocation, Collections.emptyList()); + + // If schema is "unevaluatedItems: true" this is valid + if (getSchemaNode().isBoolean() && getSchemaNode().booleanValue()) { + valid = true; + // No need to actually evaluate since the schema is true but if there are any + // items the annotation needs to be set + if (!node.isEmpty()) { + evaluated = true; + } + } else { + // Get all the "items" for the instanceLocation + List items = instanceLocationAnnotations.stream() + .filter(a -> itemsKeyword.equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + if (items.isEmpty()) { + // The "items" wasn't applied meaning it is unevaluated if there is content + valid = false; + } else { + // Annotation results for "items" keywords from multiple schemas applied to the + // same instance location are combined by setting the combined result to true if + // any of the values are true, and otherwise retaining the largest numerical + // value. + for (Annotation annotation : items) { + if (annotation.getValue() instanceof Number) { + Number value = annotation.getValue(); + int existing = value.intValue(); + if (existing > validCount) { + validCount = existing; + } + } else if (annotation.getValue() instanceof Boolean) { + // The annotation "items: true" + valid = true; + } + } + } + if (!valid) { + // Check the additionalItems annotation + // If the "additionalItems" subschema is applied to any positions within the + // instance array, it produces an annotation result of boolean true, analogous + // to the single schema behavior of "items". If any "additionalItems" keyword + // from any subschema applied to the same instance location produces an + // annotation value of true, then the combined result from these keywords is + // also true. + List additionalItems = instanceLocationAnnotations.stream() + .filter(a -> additionalItemsKeyword.equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : additionalItems) { + if (annotation.getValue() instanceof Boolean && Boolean.TRUE.equals(annotation.getValue())) { + // The annotation "additionalItems: true" + valid = true; + } + } + } + if (!valid) { + // Unevaluated + // Check if there are any "unevaluatedItems" annotations + List unevaluatedItems = instanceLocationAnnotations.stream() + .filter(a -> "unevaluatedItems".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : unevaluatedItems) { + if (annotation.getValue() instanceof Boolean && Boolean.TRUE.equals(annotation.getValue())) { + // The annotation "unevaluatedItems: true" + valid = true; + } + } + } + } + if (!valid) { + int currentErrors = executionContext.getErrors().size(); + // Get all the "contains" for the instanceLocation + List contains = instanceLocationAnnotations.stream() + .filter(a -> "contains".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + + Set containsEvaluated = new HashSet<>(); + boolean containsEvaluatedAll = false; + for (Annotation a : contains) { + if (a.getValue() instanceof List) { + List values = a.getValue(); + containsEvaluated.addAll(values); + } else if (a.getValue() instanceof Boolean) { + containsEvaluatedAll = true; + } + } + + if (!containsEvaluatedAll) { + // Start evaluating from the valid count + for (int x = validCount; x < node.size(); x++) { + // The schema is either "false" or an object schema + if (!containsEvaluated.contains(x)) { + if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) { + // All fails as "unevaluatedItems: false" + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation).arguments(x) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .build()); + } else { + // Schema errors will be reported as is + this.schema.validate(executionContext, node.get(x), node, instanceLocation.append(x)); + } + evaluated = true; + } + } + } + if (currentErrors == executionContext.getErrors().size()) { // No new errors + valid = true; + } + } + // If the "unevaluatedItems" subschema is applied to any positions within the + // instance array, it produces an annotation result of boolean true, analogous + // to the single schema behavior of "items". If any "unevaluatedItems" keyword + // from any subschema applied to the same instance location produces an + // annotation value of true, then the combined result from these keywords is + // also true. + if (evaluated) { + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) + .keyword("unevaluatedItems").value(true).build()); + } + } +} diff --git a/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java new file mode 100644 index 000000000..f92f15dd5 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.path.NodePath; + +/** + * {@link KeywordValidator} for unevaluatedProperties. + */ +public class UnevaluatedPropertiesValidator extends BaseKeywordValidator { + private final Schema schema; + + public UnevaluatedPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.UNEVALUATED_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); + + if (schemaNode.isObject() || schemaNode.isBoolean()) { + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); + } else { + throw new IllegalArgumentException("The value of 'unevaluatedProperties' MUST be a valid JSON Schema."); + } + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + if (!node.isObject()) { + return; + } + + + // Get all the valid adjacent annotations + Predicate validEvaluationPathFilter = a -> a.isValid(); + //Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); + + Predicate adjacentEvaluationPathFilter = a -> a.getEvaluationPath() + .startsWith(executionContext.getEvaluationPath().getParent()); + + List instanceLocationAnnotations = executionContext.getAnnotations().asMap() + .getOrDefault(instanceLocation, Collections.emptyList()); + + Set evaluatedProperties = new LinkedHashSet<>(); // The properties that unevaluatedProperties schema + Set existingEvaluatedProperties = new LinkedHashSet<>(); + // Get all the "properties" for the instanceLocation + List properties = instanceLocationAnnotations.stream() + .filter(a -> "properties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : properties) { + if (annotation.getValue() instanceof Set) { + Set p = annotation.getValue(); + existingEvaluatedProperties.addAll(p); + } + } + + // Get all the "patternProperties" for the instanceLocation + List patternProperties = instanceLocationAnnotations.stream() + .filter(a -> "patternProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : patternProperties) { + if (annotation.getValue() instanceof Set) { + Set p = annotation.getValue(); + existingEvaluatedProperties.addAll(p); + } + } + + // Get all the "patternProperties" for the instanceLocation + List additionalProperties = instanceLocationAnnotations.stream() + .filter(a -> "additionalProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : additionalProperties) { + if (annotation.getValue() instanceof Set) { + Set p = annotation.getValue(); + existingEvaluatedProperties.addAll(p); + } + } + + // Get all the "unevaluatedProperties" for the instanceLocation + List unevaluatedProperties = instanceLocationAnnotations.stream() + .filter(a -> "unevaluatedProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter) + .filter(validEvaluationPathFilter).collect(Collectors.toList()); + for (Annotation annotation : unevaluatedProperties) { + if (annotation.getValue() instanceof Set) { + Set p = annotation.getValue(); + existingEvaluatedProperties.addAll(p); + } + } + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + try { + executionContext.setFailFast(false); + for (Iterator it = node.fieldNames(); it.hasNext();) { + String fieldName = it.next(); + if (!existingEvaluatedProperties.contains(fieldName)) { + evaluatedProperties.add(fieldName); + if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) { + // All fails as "unevaluatedProperties: false" + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation).property(fieldName) + .evaluationPath(executionContext.getEvaluationPath()).arguments(fieldName).locale(executionContext.getExecutionConfig().getLocale()) + .build()); + } else { + // Schema errors will be reported as is + this.schema.validate(executionContext, node.get(fieldName), node, + instanceLocation.append(fieldName)); + } + } + } + } finally { + executionContext.setFailFast(failFast); // restore flag + } + executionContext.getAnnotations() + .put(Annotation.builder().instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.schemaLocation).keyword(getKeyword()).value(evaluatedProperties).build()); + + return; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java b/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java new file mode 100644 index 000000000..be4f29429 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaException; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.Validator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link KeywordValidator} for type union. + */ +public class UnionTypeValidator extends BaseKeywordValidator implements KeywordValidator { + private final List schemas; + private final String error; + + public UnionTypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); + StringBuilder errorBuilder = new StringBuilder(); + + String sep = ""; + errorBuilder.append('['); + + if (!schemaNode.isArray()) { + throw new SchemaException("Expected array for type property on Union Type Definition."); + } + + int i = 0; + this.schemas = new ArrayList<>(schemaNode.size()); + for (JsonNode n : schemaNode) { + JsonType t = TypeFactory.getSchemaNodeType(n); + errorBuilder.append(sep).append(t); + sep = ", "; + + if (n.isObject()) { + schemas.add(schemaContext.newSchema(schemaLocation.append(i), n, parentSchema)); + } else { + schemas.add(new TypeValidator(schemaLocation.append(i), n, parentSchema, schemaContext)); + } + i++; + } + + errorBuilder.append(']'); + + error = errorBuilder.toString(); + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + JsonType nodeType = TypeFactory.getValueNodeType(node, schemaContext.getSchemaRegistryConfig()); + + boolean valid = false; + + // Save flag as nested schema evaluation shouldn't trigger fail fast + boolean failFast = executionContext.isFailFast(); + List existingErrors = executionContext.getErrors(); + try { + List test = new ArrayList<>(); + executionContext.setFailFast(false); + executionContext.setErrors(test); + int schemaIndex = 0; + for (Validator schema : schemas) { + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; + if (test.isEmpty()) { + valid = true; + break; + } else { + test.clear(); + } + } + } finally { + // Restore flag + executionContext.setFailFast(failFast); + executionContext.setErrors(existingErrors); + } + + if (!valid) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .keyword("type") + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .arguments(nodeType.toString(), error) + .build()); + } + } + + @Override + public void preloadSchema() { + for (final Validator validator : schemas) { + if (validator instanceof KeywordValidator) { + ((KeywordValidator) validator).preloadSchema(); + } else if (validator instanceof Schema) { + ((Schema) validator).initializeValidators(); + } + } + } + + @Override + public String getKeyword() { + return "type"; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java new file mode 100644 index 000000000..05f5bc080 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +import java.util.HashSet; +import java.util.Set; + +/** + * {@link KeywordValidator} for uniqueItems. + */ +public class UniqueItemsValidator extends BaseKeywordValidator implements KeywordValidator { + private final boolean unique; + + public UniqueItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.UNIQUE_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); + if (schemaNode.isBoolean()) { + unique = schemaNode.booleanValue(); + } else { + unique = false; + } + } + + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + + if (unique) { + Set set = new HashSet<>(); + for (JsonNode n : node) { + if (!set.add(n)) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .build()); + } + } + } + } + +} diff --git a/src/main/java/com/networknt/schema/keyword/UnknownKeywordFactory.java b/src/main/java/com/networknt/schema/keyword/UnknownKeywordFactory.java new file mode 100644 index 000000000..5df3d89b3 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/UnknownKeywordFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.networknt.schema.SchemaContext; + +/** + * Unknown keyword factory. + *

+ * This treats unknown keywords as annotations. + */ +public class UnknownKeywordFactory implements KeywordFactory { + private static final Logger logger = LoggerFactory.getLogger(UnknownKeywordFactory.class); + + private final Map keywords = new ConcurrentHashMap<>(); + + @Override + public Keyword getKeyword(String value, SchemaContext schemaContext) { + return this.keywords.computeIfAbsent(value, keyword -> { + logger.warn( + "Unknown keyword {} - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword or if it should generate annotations AnnotationKeyword", + keyword); + return new AnnotationKeyword(keyword); + }); + } + + private static class Holder { + private static final UnknownKeywordFactory INSTANCE = new UnknownKeywordFactory(); + } + + public static UnknownKeywordFactory getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java b/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java new file mode 100644 index 000000000..3537395d6 --- /dev/null +++ b/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java @@ -0,0 +1,34 @@ +package com.networknt.schema.keyword; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.SchemaContext; + +/** + * {@link KeywordValidator} for writeOnly. + */ +public class WriteOnlyValidator extends BaseKeywordValidator { + private static final Logger logger = LoggerFactory.getLogger(WriteOnlyValidator.class); + + public WriteOnlyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.WRITE_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext); + logger.debug("Loaded WriteOnlyValidator for property {} as {}", parentSchema, "write mode"); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + + if (Boolean.TRUE.equals(executionContext.getExecutionConfig().getWriteOnly())) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .build()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java b/src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java new file mode 100644 index 000000000..736d79882 --- /dev/null +++ b/src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.output; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.Set; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.Error; + +/** + * HierarchicalOutputUnitFormatter. + */ +public class HierarchicalOutputUnitFormatter { + public static OutputUnit format(OutputUnit root, OutputUnitData data, NodePath rootPath) { + Map valid = data.getValid(); + Map> errors = data.getErrors(); + Map> annotations = data.getAnnotations(); + Map> droppedAnnotations = data.getDroppedAnnotations(); + + // Evaluation path to output unit + Map> index = new LinkedHashMap<>(); + Map r = new LinkedHashMap<>(); + r.put(rootPath, root); + index.put(rootPath, r); + + // Get all the evaluation paths with data + // This is a map of evaluation path to instance location + Map> keys = new LinkedHashMap<>(); + errors.keySet().stream().forEach(k -> keys.computeIfAbsent(k.getEvaluationPath(), a -> new LinkedHashSet<>()) + .add(k.getInstanceLocation())); + annotations.keySet().stream().forEach(k -> keys + .computeIfAbsent(k.getEvaluationPath(), a -> new LinkedHashSet<>()).add(k.getInstanceLocation())); + droppedAnnotations.keySet().stream().forEach(k -> keys + .computeIfAbsent(k.getEvaluationPath(), a -> new LinkedHashSet<>()).add(k.getInstanceLocation())); + + errors.keySet().stream().forEach(k -> buildIndex(k, index, keys, root)); + annotations.keySet().stream().forEach(k -> buildIndex(k, index, keys, root)); + droppedAnnotations.keySet().stream().forEach(k -> buildIndex(k, index, keys, root)); + + // Process all the data + for (Entry> error : errors.entrySet()) { + OutputUnitKey key = error.getKey(); + OutputUnit unit = index.get(key.getEvaluationPath()).get(key.getInstanceLocation()); + unit.setInstanceLocation(key.getInstanceLocation().toString()); + unit.setSchemaLocation(key.getSchemaLocation().toString()); + unit.setValid(false); + unit.setErrors(error.getValue()); + } + + for (Entry> annotation : annotations.entrySet()) { + OutputUnitKey key = annotation.getKey(); + OutputUnit unit = index.get(key.getEvaluationPath()).get(key.getInstanceLocation()); + String instanceLocation = key.getInstanceLocation().toString(); + String schemaLocation = key.getSchemaLocation().toString(); + if (unit.getInstanceLocation() != null && !unit.getInstanceLocation().equals(instanceLocation)) { + throw new IllegalArgumentException(); + } + if (unit.getSchemaLocation() != null && !unit.getSchemaLocation().equals(schemaLocation)) { + throw new IllegalArgumentException(); + } + unit.setInstanceLocation(instanceLocation); + unit.setSchemaLocation(schemaLocation); + unit.setAnnotations(annotation.getValue()); + unit.setValid(valid.get(key)); + } + + for (Entry> droppedAnnotation : droppedAnnotations.entrySet()) { + OutputUnitKey key = droppedAnnotation.getKey(); + OutputUnit unit = index.get(key.getEvaluationPath()).get(key.getInstanceLocation()); + String instanceLocation = key.getInstanceLocation().toString(); + String schemaLocation = key.getSchemaLocation().toString(); + if (unit.getInstanceLocation() != null && !unit.getInstanceLocation().equals(instanceLocation)) { + throw new IllegalArgumentException(); + } + if (unit.getSchemaLocation() != null && !unit.getSchemaLocation().equals(schemaLocation)) { + throw new IllegalArgumentException(); + } + unit.setInstanceLocation(instanceLocation); + unit.setSchemaLocation(schemaLocation); + unit.setDroppedAnnotations(droppedAnnotation.getValue()); + unit.setValid(valid.get(key)); + } + return root; + } + + public static OutputUnit format(Schema jsonSchema, List errors, + ExecutionContext executionContext, SchemaContext schemaContext, + Function errorMapper) { + OutputUnit root = new OutputUnit(); + root.setValid(errors.isEmpty()); + + root.setInstanceLocation(schemaContext.getSchemaRegistryConfig().getPathType().getRoot()); + root.setEvaluationPath(schemaContext.getSchemaRegistryConfig().getPathType().getRoot()); + root.setSchemaLocation(jsonSchema.getSchemaLocation().toString()); + + OutputUnitData data = OutputUnitData.from(errors, executionContext, errorMapper); + + return format(root, data, new NodePath(schemaContext.getSchemaRegistryConfig().getPathType())); + } + + /** + * Builds in the index of evaluation path to output units to be populated later + * and modify the root to add the appropriate children. + * + * @param key the current key to process + * @param index contains all the mappings from evaluation path to output units + * @param keys that contain all the evaluation paths with instance data + * @param root the root output unit + */ + protected static void buildIndex(OutputUnitKey key, Map> index, + Map> keys, OutputUnit root) { + if (index.containsKey(key.getEvaluationPath())) { + return; + } + // Ensure the path is created + NodePath path = key.getEvaluationPath(); + Deque stack = new ArrayDeque<>(); + while (path != null && path.getElement(-1) != null) { + stack.push(path); + path = path.getParent(); + } + + OutputUnit parent = root; + while (!stack.isEmpty()) { + NodePath current = stack.pop(); + if (!index.containsKey(current) && keys.containsKey(current)) { + // the index doesn't contain this path but this is a path with data + for (NodePath instanceLocation : keys.get(current)) { + OutputUnit child = new OutputUnit(); + child.setValid(true); + child.setEvaluationPath(current.toString()); + child.setInstanceLocation(instanceLocation.toString()); + index.computeIfAbsent(current, n -> new LinkedHashMap<>()).put(instanceLocation, child); + if (parent.getDetails() == null) { + parent.setDetails(new ArrayList<>()); + } + parent.getDetails().add(child); + } + } + + // If exists in the index this is the new parent + // Otherwise this is an evaluation path with no data and hence should be skipped + // InstanceLocation to OutputUnit + Map result = index.get(current); + if (result != null) { + for (Entry entry : result.entrySet()) { + if (key.getInstanceLocation().startsWith(entry.getKey())) { + parent = entry.getValue(); + break; + } + } + } + } + } +} diff --git a/src/main/java/com/networknt/schema/output/ListOutputUnitFormatter.java b/src/main/java/com/networknt/schema/output/ListOutputUnitFormatter.java new file mode 100644 index 000000000..e8fef126a --- /dev/null +++ b/src/main/java/com/networknt/schema/output/ListOutputUnitFormatter.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.output; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.Error; + +/** + * ListOutputUnitFormatter. + */ +public class ListOutputUnitFormatter { + public static OutputUnit format(OutputUnit root, OutputUnitData data) { + Map valid = data.getValid(); + Map> errors = data.getErrors(); + Map> annotations = data.getAnnotations(); + Map> droppedAnnotations = data.getDroppedAnnotations(); + + // Process the list + for (Entry entry : valid.entrySet()) { + OutputUnit output = new OutputUnit(); + OutputUnitKey key = entry.getKey(); + output.setValid(entry.getValue()); + output.setEvaluationPath(key.getEvaluationPath().toString()); + output.setSchemaLocation(key.getSchemaLocation().toString()); + output.setInstanceLocation(key.getInstanceLocation().toString()); + + // Errors + Map errorMap = errors.get(key); + if (errorMap != null && !errorMap.isEmpty()) { + if (output.getErrors() == null) { + output.setErrors(new LinkedHashMap<>()); + } + for (Entry errorEntry : errorMap.entrySet()) { + output.getErrors().put(errorEntry.getKey(), errorEntry.getValue()); + } + } + + // Annotations + Map annotationsMap = annotations.get(key); + if (annotationsMap != null && !annotationsMap.isEmpty()) { + if (output.getAnnotations() == null) { + output.setAnnotations(new LinkedHashMap<>()); + } + for (Entry annotationEntry : annotationsMap.entrySet()) { + output.getAnnotations().put(annotationEntry.getKey(), annotationEntry.getValue()); + } + } + + // Dropped Annotations + Map droppedAnnotationsMap = droppedAnnotations.get(key); + if (droppedAnnotationsMap != null && !droppedAnnotationsMap.isEmpty()) { + if (output.getDroppedAnnotations() == null) { + output.setDroppedAnnotations(new LinkedHashMap<>()); + } + for (Entry droppedAnnotationEntry : droppedAnnotationsMap.entrySet()) { + output.getDroppedAnnotations().put(droppedAnnotationEntry.getKey(), + droppedAnnotationEntry.getValue()); + } + } + + List details = root.getDetails(); + if (details == null) { + details = new ArrayList<>(); + root.setDetails(details); + } + details.add(output); + } + + return root; + } + + public static OutputUnit format(List errors, ExecutionContext executionContext, + SchemaContext schemaContext, Function errorMapper) { + OutputUnit root = new OutputUnit(); + root.setValid(errors.isEmpty()); + return format(root, OutputUnitData.from(errors, executionContext, errorMapper)); + } +} diff --git a/src/main/java/com/networknt/schema/output/OutputFlag.java b/src/main/java/com/networknt/schema/output/OutputFlag.java new file mode 100644 index 000000000..413bce06d --- /dev/null +++ b/src/main/java/com/networknt/schema/output/OutputFlag.java @@ -0,0 +1,47 @@ +package com.networknt.schema.output; + +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * The Flag output results. + */ +public class OutputFlag { + private final boolean valid; + + public OutputFlag(boolean valid) { + this.valid = valid; + } + + public boolean isValid() { + return this.valid; + } + + @Override + public int hashCode() { + return Objects.hash(valid); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OutputFlag other = (OutputFlag) obj; + return valid == other.valid; + } + + @Override + public String toString() { + try { + return JsonMapperFactory.getInstance().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + return "OutputFlag [valid=" + valid + "]"; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/output/OutputUnit.java b/src/main/java/com/networknt/schema/output/OutputUnit.java new file mode 100644 index 000000000..e85a05b54 --- /dev/null +++ b/src/main/java/com/networknt/schema/output/OutputUnit.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.output; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Represents an output unit. + * + * @see A + * Specification for Machine-Readable Output for JSON Schema Validation and + * Annotation + */ +@JsonInclude(Include.NON_NULL) +@JsonPropertyOrder({ "valid", "evaluationPath", "schemaLocation", "instanceLocation", "errors", "annotations", + "droppedAnnotations", "details" }) +public class OutputUnit { + private boolean valid; + + private String evaluationPath = null; + private String schemaLocation = null; + private String instanceLocation = null; + + private Map errors = null; + + private Map annotations = null; + + private Map droppedAnnotations = null; + + private List details = null; + + public boolean isValid() { + return valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + public String getEvaluationPath() { + return evaluationPath; + } + + public void setEvaluationPath(String evaluationPath) { + this.evaluationPath = evaluationPath; + } + + public String getSchemaLocation() { + return schemaLocation; + } + + public void setSchemaLocation(String schemaLocation) { + this.schemaLocation = schemaLocation; + } + + public String getInstanceLocation() { + return instanceLocation; + } + + public void setInstanceLocation(String instanceLocation) { + this.instanceLocation = instanceLocation; + } + + public Map getErrors() { + return errors; + } + + public void setErrors(Map errors) { + this.errors = errors; + } + + public Map getAnnotations() { + return annotations; + } + + public void setAnnotations(Map annotations) { + this.annotations = annotations; + } + + public Map getDroppedAnnotations() { + return droppedAnnotations; + } + + public void setDroppedAnnotations(Map droppedAnnotations) { + this.droppedAnnotations = droppedAnnotations; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } + + @Override + public int hashCode() { + return Objects.hash(annotations, details, droppedAnnotations, errors, evaluationPath, instanceLocation, + schemaLocation, valid); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OutputUnit other = (OutputUnit) obj; + return Objects.equals(annotations, other.annotations) && Objects.equals(details, other.details) + && Objects.equals(droppedAnnotations, other.droppedAnnotations) && Objects.equals(errors, other.errors) + && Objects.equals(evaluationPath, other.evaluationPath) + && Objects.equals(instanceLocation, other.instanceLocation) + && Objects.equals(schemaLocation, other.schemaLocation) && valid == other.valid; + } + + @Override + public String toString() { + try { + return JsonMapperFactory.getInstance().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + return "OutputUnit [valid=" + valid + ", evaluationPath=" + evaluationPath + ", schemaLocation=" + + schemaLocation + ", instanceLocation=" + instanceLocation + ", errors=" + errors + + ", annotations=" + annotations + ", droppedAnnotations=" + droppedAnnotations + ", details=" + + details + "]"; + } + } +} diff --git a/src/main/java/com/networknt/schema/output/OutputUnitData.java b/src/main/java/com/networknt/schema/output/OutputUnitData.java new file mode 100644 index 000000000..b76e8ded4 --- /dev/null +++ b/src/main/java/com/networknt/schema/output/OutputUnitData.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.output; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.Error; +import com.networknt.schema.annotation.Annotation; + +/** + * Output Unit Data. + */ +public class OutputUnitData { + private final Map valid = new LinkedHashMap<>(); + private final Map> errors = new LinkedHashMap<>(); + private final Map> annotations = new LinkedHashMap<>(); + private final Map> droppedAnnotations = new LinkedHashMap<>(); + + public Map getValid() { + return valid; + } + + public Map> getErrors() { + return errors; + } + + public Map> getAnnotations() { + return annotations; + } + + public Map> getDroppedAnnotations() { + return droppedAnnotations; + } + + public static String formatError(Error error) { + return error.getMessage(); + } + + @SuppressWarnings("unchecked") + public static OutputUnitData from(List validationErrors, ExecutionContext executionContext, + Function errorMapper) { + OutputUnitData data = new OutputUnitData(); + + Map valid = data.valid; + Map> errors = data.errors; + Map> annotations = data.annotations; + Map> droppedAnnotations = data.droppedAnnotations; + + for (Error error : validationErrors) { + SchemaLocation assertionSchemaLocation = new SchemaLocation(error.getSchemaLocation().getAbsoluteIri(), + error.getSchemaLocation().getFragment().getParent()); + OutputUnitKey key = new OutputUnitKey(error.getEvaluationPath().getParent(), + assertionSchemaLocation, error.getInstanceLocation()); + valid.put(key, false); + Map errorMap = errors.computeIfAbsent(key, k -> new LinkedHashMap<>()); + Object value = errorMap.get(error.getKeyword()); + if (value == null) { + errorMap.put(error.getKeyword(), errorMapper.apply(error)); + } else { + // Existing error, make it into a list + if (value instanceof List) { + ((List) value).add(errorMapper.apply(error)); + } else { + List values = new ArrayList<>(); + values.add(value.toString()); + values.add(errorMapper.apply(error)); + errorMap.put(error.getKeyword(), values); + } + } + } + + for (List annotationsResult : executionContext.getAnnotations().asMap().values()) { + for (Annotation annotation : annotationsResult) { + // As some annotations are required for computation, filter those that are not + // required for reporting + if (executionContext.getExecutionConfig().getAnnotationCollectionFilter() + .test(annotation.getKeyword())) { + SchemaLocation annotationSchemaLocation = new SchemaLocation( + annotation.getSchemaLocation().getAbsoluteIri(), + annotation.getSchemaLocation().getFragment().getParent()); + + OutputUnitKey key = new OutputUnitKey(annotation.getEvaluationPath().getParent(), + annotationSchemaLocation, annotation.getInstanceLocation()); +// boolean validResult = executionContext.getInstanceResults().isValid(annotation.getInstanceLocation(), +// annotation.getEvaluationPath()); + boolean validResult = annotation.isValid(); + valid.put(key, validResult); + if (validResult) { + // annotations + Map annotationMap = annotations.computeIfAbsent(key, + k -> new LinkedHashMap<>()); + annotationMap.put(annotation.getKeyword(), annotation.getValue()); + } else { + // dropped annotations + Map droppedAnnotationMap = droppedAnnotations.computeIfAbsent(key, + k -> new LinkedHashMap<>()); + droppedAnnotationMap.put(annotation.getKeyword(), annotation.getValue()); + } + } + } + } + return data; + } +} diff --git a/src/main/java/com/networknt/schema/output/OutputUnitKey.java b/src/main/java/com/networknt/schema/output/OutputUnitKey.java new file mode 100644 index 000000000..b5b23ebc5 --- /dev/null +++ b/src/main/java/com/networknt/schema/output/OutputUnitKey.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.output; + +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Output Unit Key. + */ +public class OutputUnitKey { + @JsonSerialize(using = ToStringSerializer.class) + final NodePath evaluationPath; + @JsonSerialize(using = ToStringSerializer.class) + final SchemaLocation schemaLocation; + @JsonSerialize(using = ToStringSerializer.class) + final NodePath instanceLocation; + + public OutputUnitKey(NodePath evaluationPath, SchemaLocation schemaLocation, NodePath instanceLocation) { + super(); + this.evaluationPath = evaluationPath; + this.schemaLocation = schemaLocation; + this.instanceLocation = instanceLocation; + } + + public NodePath getEvaluationPath() { + return evaluationPath; + } + + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + public NodePath getInstanceLocation() { + return instanceLocation; + } + + @Override + public int hashCode() { + return Objects.hash(evaluationPath, instanceLocation, schemaLocation); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OutputUnitKey other = (OutputUnitKey) obj; + return Objects.equals(evaluationPath, other.evaluationPath) + && Objects.equals(instanceLocation, other.instanceLocation) + && Objects.equals(schemaLocation, other.schemaLocation); + } + + @Override + public String toString() { + try { + return JsonMapperFactory.getInstance().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + return "OutputUnitKey [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation + + ", instanceLocation=" + instanceLocation + "]"; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/path/NodePath.java b/src/main/java/com/networknt/schema/path/NodePath.java new file mode 100644 index 000000000..bb0d953d2 --- /dev/null +++ b/src/main/java/com/networknt/schema/path/NodePath.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.path; + +import java.util.ArrayDeque; +import java.util.Objects; + +/** + * Represents a path to a JSON node. + */ +public class NodePath implements Comparable, Path { + private final PathType type; + private final NodePath parent; + + private final String pathSegment; + private final int pathSegmentIndex; + + private volatile String value = null; // computed lazily + private int hash = 0; // computed lazily + + public NodePath(PathType type) { + this.type = type; + this.parent = null; + this.pathSegment = null; + this.pathSegmentIndex = -1; + } + + private NodePath(NodePath parent, String pathSegment) { + this.parent = parent; + this.type = parent.type; + this.pathSegment = pathSegment; + this.pathSegmentIndex = -1; + } + + private NodePath(NodePath parent, int pathSegmentIndex) { + this.parent = parent; + this.type = parent.type; + this.pathSegment = null; + this.pathSegmentIndex = pathSegmentIndex; + } + + /** + * Returns the parent path, or null if this path does not have a parent. + * + * @return the parent + */ + public NodePath getParent() { + return this.parent; + } + + /** + * Append the child token to the path. + * + * @param token the child token + * @return the path + */ + public NodePath append(String token) { + return new NodePath(this, token); + } + + /** + * Append the index to the path. + * + * @param index the index + * @return the path + */ + public NodePath append(int index) { + return new NodePath(this, index); + } + + /** + * Gets the {@link PathType}. + * + * @return the path type + */ + public PathType getPathType() { + return this.type; + } + + /** + * Gets the name element given an index. + *

+ * The index parameter is the index of the name element to return. The element + * that is closest to the root has index 0. The element that is farthest from + * the root has index count -1. + * + * @param index to return + * @return the name element + */ + public String getName(int index) { + Object element = getElement(index); + if (element != null) { + return element.toString(); + } + return null; + } + + /** + * Gets the element given an index. + *

+ * The index parameter is the index of the element to return. The element that + * is closest to the root has index 0. The element that is farthest from the + * root has index count -1. + * + * @param index to return + * @return the element either a String or Integer + */ + public Object getElement(int index) { + if (index == -1) { + if (this.pathSegmentIndex != -1) { + return this.pathSegmentIndex; + } else { + return this.pathSegment; + } + } + int nameCount = getNameCount(); + if (nameCount - 1 == index) { + return this.getElement(-1); + } + int count = nameCount - index - 1; + if (count < 0) { + throw new IllegalArgumentException(""); + } + NodePath current = this; + for (int x = 0; x < count; x++) { + current = current.parent; + } + return current.getElement(-1); + } + + /** + * Gets the number of name elements in the path. + * + * @return the number of elements in the path or 0 if this is the root element + */ + public int getNameCount() { + int current = this.pathSegmentIndex == -1 && this.pathSegment == null ? 0 : 1; + int parent = this.parent != null ? this.parent.getNameCount() : 0; + return current + parent; + } + + /** + * Tests if this path starts with the other path. + * + * @param other the other path + * @return true if the path starts with the other path + */ + public boolean startsWith(NodePath other) { + int count = getNameCount(); + int otherCount = other.getNameCount(); + + if (otherCount > count) { + return false; + } else if (otherCount == count) { + return this.equals(other); + } else { + NodePath compare = this; + int x = count - otherCount; + while (x > 0) { + compare = compare.getParent(); + x--; + } + return other.equals(compare); + } + } + + /** + * Tests if this path contains a string segment that is an exact match. + *

+ * This will not match if the segment is a number. + * + * @param segment the segment to test + * @return true if the string segment is found + */ + public boolean contains(String segment) { + boolean result = segment.equals(this.pathSegment); + if (result) { + return true; + } + NodePath path = this.getParent(); + while (path != null) { + if (segment.equals(path.pathSegment)) { + return true; + } + path = path.getParent(); + } + return false; + } + + @Override + public String toString() { + if (this.value == null) { + if (pathSegmentIndex == -1 && this.pathSegment == null) { + this.value = type.getRoot(); + return this.value; + } + + ArrayDeque pathSegments = new ArrayDeque(); + if (pathSegmentIndex != -1) { + pathSegments.push(this.pathSegmentIndex); + } else if (this.pathSegment != null) { + pathSegments.push(this.pathSegment); + } + NodePath parent = getParent(); + while (parent != null) { + if (parent.pathSegmentIndex != -1) { + pathSegments.push(parent.pathSegmentIndex); + } else if (parent.pathSegment != null) { + pathSegments.push(parent.pathSegment); + } + parent = parent.getParent(); + } + + StringBuilder builder = new StringBuilder(); + String root = type.getRoot(); + if (root != null) { + builder.append(root); + } + for (Object pathSegment : pathSegments) { + if (pathSegment instanceof Number) { + type.append(builder, ((Number) pathSegment).intValue()); + } else { + type.append(builder, pathSegment.toString()); + } + } + this.value = builder.toString(); + } + return this.value; + } + + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + h = Objects.hash(parent, pathSegment, pathSegmentIndex, type); + hash = h; + } + return h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NodePath other = (NodePath) obj; + return Objects.equals(pathSegment, other.pathSegment) && pathSegmentIndex == other.pathSegmentIndex + && type == other.type && Objects.equals(parent, other.parent); + } + + @Override + public int compareTo(NodePath other) { + if (this.parent != null && other.parent == null) { + return 1; + } else if (this.parent == null && other.parent != null) { + return -1; + } else if (this.parent != null && other.parent != null) { + int result = this.parent.compareTo(other.parent); + if (result != 0) { + return result; + } + } + String thisValue = this.getName(-1); + String otherValue = other.getName(-1); + if (thisValue == null && otherValue == null) { + return 0; + } else if (thisValue != null && otherValue == null) { + return 1; + } else if (thisValue == null && otherValue != null) { + return -1; + } else { + return thisValue.compareTo(otherValue); + } + } +} diff --git a/src/main/java/com/networknt/schema/path/Path.java b/src/main/java/com/networknt/schema/path/Path.java new file mode 100644 index 000000000..c72cc8393 --- /dev/null +++ b/src/main/java/com/networknt/schema/path/Path.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.path; + +/** + * Represents a path. + */ +public interface Path { + String toString(); +} diff --git a/src/main/java/com/networknt/schema/path/PathType.java b/src/main/java/com/networknt/schema/path/PathType.java new file mode 100644 index 000000000..f1c637f48 --- /dev/null +++ b/src/main/java/com/networknt/schema/path/PathType.java @@ -0,0 +1,273 @@ +package com.networknt.schema.path; + +import java.util.function.BiConsumer; +import java.util.function.IntPredicate; + +/** + * Enumeration defining the different approached available to generate the paths added to validation messages. + */ +public enum PathType { + + /** + * The legacy approach, loosely based on JSONPath (but not guaranteed to give valid JSONPath expressions). + */ + LEGACY("$", (currentPath, token) -> { + currentPath.append(".").append(replaceCommonSpecialCharactersIfPresent(token)); + }, (currentPath, index) -> { + currentPath.append("[").append(index).append("]"); + }), + + /** + * Paths as JSONPath expressions. + */ + JSON_PATH("$", (currentPath, token) -> { + if (token.isEmpty()) { + throw new IllegalArgumentException("A JSONPath selector cannot be empty"); + } + /* + * Accepted characters for shorthand paths: + * - 'a' through 'z' + * - 'A' through 'Z' + * - '0' through '9' + * - Underscore ('_') + * - any non-ASCII Unicode character + */ + if (JSONPath.isShorthand(token)) { + currentPath.append(".").append(token); + return; + } + + // Replace single quote (used to wrap property names when not shorthand form). + if (token.indexOf('\'') != -1) token = token.replace("'", "\\'"); + // Replace other special characters. + token = replaceCommonSpecialCharactersIfPresent(token); + currentPath.append("['").append(token).append("']"); + }, (currentPath, index) -> { + currentPath.append("[").append(index).append("]"); + }), + + /** + * Paths as JSONPointer expressions. + */ + JSON_POINTER("", (currentPath, token) -> { + /* + * Escape '~' with '~0' and '/' with '~1'. + */ + if (token.indexOf('~') != -1) token = token.replace("~", "~0"); + if (token.indexOf('/') != -1) token = token.replace("/", "~1"); + // Replace other special characters. + token = replaceCommonSpecialCharactersIfPresent(token); + currentPath.append("/").append(token); + }, (currentPath, index) -> { + currentPath.append("/").append(index); + }), + + /** + * Paths as a URI reference. + */ + URI_REFERENCE("", (currentPath, token) -> { + if (!(currentPath.length() == 0)) { + currentPath.append("/").append(token); + } else { + currentPath.append(token); + } + }, (currentPath, index) -> { + currentPath.append("/").append(index); + }); + + /** + * The default path generation approach to use. + */ + public static final PathType DEFAULT = LEGACY; + private final String rootToken; + private final BiConsumer appendTokenFn; + private final BiConsumer appendIndexFn; + + /** + * Constructor. + * + * @param rootToken The token representing the document root. + * @param appendTokenFn A function used to define the path fragment used to append a token (e.g. property) to an existing path. + * @param appendIndexFn A function used to append an index (for arrays) to an existing path. + */ + PathType(String rootToken, BiConsumer appendTokenFn, BiConsumer appendIndexFn) { + this.rootToken = rootToken; + this.appendTokenFn = appendTokenFn; + this.appendIndexFn = appendIndexFn; + } + + /** + * Replace common special characters that are to be considered for all types of paths. + * + * @param token The path token (property name or selector). + * @return The token to use in the path. + */ + private static String replaceCommonSpecialCharactersIfPresent(String token) { + if (token.indexOf('\n') != -1) token = token.replace("\n", "\\n"); + if (token.indexOf('\t') != -1) token = token.replace("\t", "\\t"); + if (token.indexOf('\r') != -1) token = token.replace("\r", "\\r"); + if (token.indexOf('\b') != -1) token = token.replace("\b", "\\b"); + if (token.indexOf('\f') != -1) token = token.replace("\f", "\\f"); + return token; + } + + /** + * Append the given child token to the provided current path. + * + * @param currentPath The path to append to. + * @param child The child token. + */ + public void append(StringBuilder currentPath, String child) { + this.appendTokenFn.accept(currentPath, child); + } + + /** + * Append the given index to the provided current path. + * + * @param currentPath The path to append to. + * @param index The index to append. + */ + public void append(StringBuilder currentPath, int index) { + this.appendIndexFn.accept(currentPath, index); + } + + /** + * Return the representation of the document root. + * + * @return The root token. + */ + public String getRoot() { + return this.rootToken; + } + + public String convertToJsonPointer(String path) { + switch (this) { + case JSON_POINTER: return path; + case JSON_PATH: return fromJsonPath(path); + default: return fromLegacy(path); + } + } + + static String fromLegacy(String path) { + return path + .replace("\"", "") + .replace("]", "") + .replace('[', '/') + .replace('.', '/') + .replace("$", ""); + } + + static String fromJsonPath(String str) { + if (null == str || str.isEmpty() || '$' != str.charAt(0)) { + throw new IllegalArgumentException("JSON Path must start with '$'"); + } + + String tail = str.substring(1); + if (tail.isEmpty()) { + return ""; + } + + int len = tail.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len;) { + char c = tail.charAt(i); + switch (c) { + case '.': sb.append('/'); i = parseShorthand(sb, tail, i + 1); break; + case '[': sb.append('/'); i = parseSelector(sb, tail, i + 1); break; + default: throw new IllegalArgumentException("JSONPath must reference a property or array index"); + } + } + return sb.toString(); + } + + /** + * Parses a JSONPath shorthand selector + * @param sb receives the result + * @param s the source string + * @param pos the index into s immediately following the dot + * @return the index following the selector name + */ + static int parseShorthand(StringBuilder sb, String s, int pos) { + int len = s.length(); + int i = pos; + for (; i < len; ++i) { + char c = s.charAt(i); + switch (c) { + case '.': + case '[': + break; + default: + sb.append(c); + break; + } + } + return i; + } + + /** + * Parses a JSONPath selector + * @param sb receives the result + * @param s the source string + * @param pos the index into s immediately following the open bracket + * @return the index following the closing bracket + */ + static int parseSelector(StringBuilder sb, String s, int pos) { + int close = s.indexOf(']', pos); + if (-1 == close) { + throw new IllegalArgumentException("JSONPath contains an unterminated selector"); + } + + if ('\'' == s.charAt(pos)) { + parseQuote(sb, s, pos + 1); + } else { + sb.append(s, pos, close); + } + + return close + 1; + } + + /** + * Parses a single-quoted string. + * @param sb receives the result + * @param s the source string + * @param pos the index into s immediately following the open quote + * @return the index following the closing quote + */ + static int parseQuote(StringBuilder sb, String s, int pos) { + int close = pos; + do { + close = s.indexOf('\'', close); + if (-1 == close) { + throw new IllegalArgumentException("JSONPath contains an unterminated quoted string"); + } + } while ('\\' == s.charAt(close - 1)) ; + sb.append(s, pos, close); + return close + 1; + } + + static class JSONPath { + public static final IntPredicate ALPHA = c -> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + public static final IntPredicate DIGIT = c -> c >= '0' && c <= '9'; + public static final IntPredicate NON_ASCII = c -> (c >= 0x80 && c <= 0x10FFFF); + public static final IntPredicate UNDERSCORE = c -> '_' == c; + + public static final IntPredicate NAME_FIRST = ALPHA.or(UNDERSCORE).or(NON_ASCII); + public static final IntPredicate NAME_CHAR = NAME_FIRST.or(DIGIT); + + public static boolean isShorthand(String selector) { + if (null == selector || selector.isEmpty()) { + throw new IllegalArgumentException("A JSONPath selector cannot be empty"); + } + + /* + * Accepted characters for shorthand paths: + * - 'a' through 'z' + * - 'A' through 'Z' + * - '0' through '9' + * - Underscore ('_') + * - any non-ASCII Unicode character + */ + return NAME_FIRST.test(selector.codePointAt(0)) && selector.codePoints().skip(1).allMatch(NAME_CHAR); + } + } +} diff --git a/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java new file mode 100644 index 000000000..333273e33 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import java.util.function.Predicate; + +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.Error; + +/** + * {@link RegularExpressionFactory} that allows regular expressions to be used. + */ +public class AllowRegularExpressionFactory implements RegularExpressionFactory { + private final RegularExpressionFactory delegate; + private final Predicate allowed; + + public AllowRegularExpressionFactory(RegularExpressionFactory delegate, Predicate allowed) { + this.delegate = delegate; + this.allowed = allowed; + } + + @Override + public RegularExpression getRegularExpression(String regex) { + if (this.allowed.test(regex)) { + // Allowed to delegate + return this.delegate.getRegularExpression(regex); + } + throw new InvalidSchemaException(Error.builder() + .message("Regular expression ''{0}'' is not allowed to be used.").arguments(regex).build()); + } +} diff --git a/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java new file mode 100644 index 000000000..72c5fecd1 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import com.networknt.schema.utils.Classes; + +/** + * ECMAScript regular expression factory that chooses between GraalJS or Joni + * implementations depending on which is on the classpath. + */ +public class ECMAScriptRegularExpressionFactory implements RegularExpressionFactory { + private static final boolean JONI_PRESENT = Classes.isPresent("org.joni.Regex", + ECMAScriptRegularExpressionFactory.class.getClassLoader()); + private static final boolean GRAALJS_PRESENT = Classes.isPresent("com.oracle.truffle.js.parser.GraalJSEvaluator", + ECMAScriptRegularExpressionFactory.class.getClassLoader()); + + private static final RegularExpressionFactory DELEGATE = GRAALJS_PRESENT + ? GraalJSRegularExpressionFactory.getInstance() + : JoniRegularExpressionFactory.getInstance(); + + public static final ECMAScriptRegularExpressionFactory INSTANCE = new ECMAScriptRegularExpressionFactory(); + + public static ECMAScriptRegularExpressionFactory getInstance() { + if (!JONI_PRESENT && !GRAALJS_PRESENT) { + throw new IllegalArgumentException( + "Either org.jruby.joni:joni or org.graalvm.js:js needs to be present in the classpath"); + } + return INSTANCE; + } + + @Override + public RegularExpression getRegularExpression(String regex) { + return DELEGATE.getRegularExpression(regex); + } +} diff --git a/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java b/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java new file mode 100644 index 000000000..7e4166966 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/GraalJSContextFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import org.graalvm.polyglot.Context; + +/** + * Factory for the js {@link Context}. + */ +public class GraalJSContextFactory { + /** + * The holder defers the classloading until it is used. + */ + private static class Holder { + private static final Context INSTANCE = Context.newBuilder("js").option("engine.WarnInterpreterOnly", "false") + .build(); + } + + /** + * Gets the singleton instance of the Context. + *

+ * This may need to be closed to release resources if no longer needed. + * + * @return the Context + */ + public static Context getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java new file mode 100644 index 000000000..0ab33cd80 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpression.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import org.graalvm.polyglot.Value; + +/** + * GraalJS {@link RegularExpression}. + *

+ * This requires a dependency on org.graalvm.js:js which along with its + * dependency libraries are 50 mb. + */ +class GraalJSRegularExpression implements RegularExpression { + private final GraalJSRegularExpressionContext context; + private final Value function; + + GraalJSRegularExpression(String regex, GraalJSRegularExpressionContext context) { + this.context = context; + synchronized(context.getContext()) { + this.function = context.getRegExpBuilder().execute(regex); + } + } + + @Override + public boolean matches(String value) { + synchronized(context.getContext()) { + return !function.execute(value).isNull(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java new file mode 100644 index 000000000..14c63a8af --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionContext.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; + +/** + * GraalJSRegularExpressionContext. + */ +public class GraalJSRegularExpressionContext { + private static final String SOURCE = "pattern => {\n" + + " const regex = new RegExp(pattern, 'u');\n" + + " return text => text.match(regex)\n" + + "};"; + + private final Context context; + private final Value regExpBuilder; + + /** + * Constructor. + *

+ * It is the caller's responsibility to release the context when it is no longer + * required. + * + * @param context the context + */ + public GraalJSRegularExpressionContext(Context context) { + this.context = context; + synchronized(this.context) { + this.regExpBuilder = this.context.eval("js", SOURCE); + } + } + + /** + * Operations must synchronize on the {@link Context} as only a single thread + * can access the {@link Context} and {@link #getRegExpBuilder()} at one time. + * + * @return the context + */ + public Context getContext() { + return context; + } + + /** + * Gets the RegExp builder. + * + * @return the regexp builder + */ + public Value getRegExpBuilder() { + return regExpBuilder; + } +} diff --git a/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java new file mode 100644 index 000000000..113da0209 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/GraalJSRegularExpressionFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import org.graalvm.polyglot.Context; + +/** + * GraalJS {@link RegularExpressionFactory}. + *

+ * This requires a dependency on org.graalvm.js:js which along with its + * dependency libraries are 50 MB. + */ +public class GraalJSRegularExpressionFactory implements RegularExpressionFactory { + private static class Holder { + private static final GraalJSRegularExpressionFactory INSTANCE = new GraalJSRegularExpressionFactory(); + } + + private final GraalJSRegularExpressionContext context; + + public static GraalJSRegularExpressionFactory getInstance() { + return Holder.INSTANCE; + } + + /** + * Constructor. + *

+ * This uses the context from {@link GraalJSContextFactory#getInstance()}. + *

+ * It is the caller's responsibility to release the context when it is no longer + * required by using GraalJSContextFactory.getInstance().close(). + */ + public GraalJSRegularExpressionFactory() { + this(GraalJSContextFactory.getInstance()); + } + + /** + * Constructor. + *

+ * It is the caller's responsibility to release the context when it is no longer + * required. + * + * @param context the context + */ + public GraalJSRegularExpressionFactory(Context context) { + this.context = new GraalJSRegularExpressionContext(context); + } + + @Override + public RegularExpression getRegularExpression(String regex) { + return new GraalJSRegularExpression(regex, this.context); + } +} diff --git a/src/main/java/com/networknt/schema/regex/JDKRegularExpression.java b/src/main/java/com/networknt/schema/regex/JDKRegularExpression.java new file mode 100644 index 000000000..f3353cb27 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/JDKRegularExpression.java @@ -0,0 +1,22 @@ +package com.networknt.schema.regex; + +import java.util.regex.Pattern; + +/** + * JDK {@link RegularExpression}. + */ +class JDKRegularExpression implements RegularExpression { + private final Pattern pattern; + + JDKRegularExpression(String regex) { + this.pattern = Pattern.compile(regex); + } + + @Override + public boolean matches(String value) { + /* + * Note that the matches function is not used here as it implicitly adds anchors + */ + return this.pattern.matcher(value).find(); + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java new file mode 100644 index 000000000..0e773a3ab --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/JDKRegularExpressionFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +/** + * JDK {@link RegularExpressionFactory}. + */ +public class JDKRegularExpressionFactory implements RegularExpressionFactory { + private static final JDKRegularExpressionFactory INSTANCE = new JDKRegularExpressionFactory(); + + public static JDKRegularExpressionFactory getInstance() { + return INSTANCE; + } + + @Override + public RegularExpression getRegularExpression(String regex) { + return new JDKRegularExpression(regex); + } +} diff --git a/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java b/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java new file mode 100644 index 000000000..6d2d6697a --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java @@ -0,0 +1,263 @@ +package com.networknt.schema.regex; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.jcodings.ApplyAllCaseFoldFunction; +import org.jcodings.CaseFoldCodeItem; +import org.jcodings.CodeRange; +import org.jcodings.Encoding; +import org.jcodings.IntHolder; +import org.jcodings.constants.CharacterType; +import org.jcodings.specific.UTF8Encoding; +import org.jcodings.unicode.UnicodeCodeRange; +import org.joni.Option; +import org.joni.Regex; +import org.joni.Syntax; +import org.joni.constants.SyntaxProperties; +import org.joni.exception.SyntaxException; + +/** + * Joni {@link RegularExpression}. + *

+ * This requires a dependency on org.jruby.joni:joni which along with its + * dependency libraries are 2 MB. + */ +class JoniRegularExpression implements RegularExpression { + private final Regex pattern; + private final Pattern INVALID_ESCAPE_PATTERN = Pattern.compile( + ".*\\\\([aeg-jl-moqyzACE-OQ-RT-VX-Z1-9]|k([^<]|$)|c$|[pP]([^{]|$)|u([^{0-9]|$)|x([0-9a-fA-F][^0-9a-fA-F]|[^0-9a-fA-F][0-9a-fA-F]|[^0-9a-fA-F][^0-9a-fA-F]|.?$)).*"); + + /** + * This is a custom syntax as Syntax.ECMAScript doesn't seem to be correct. + *

+ * + * @see OP2_QMARK_LT_NAMED_GROUP + * @see OP2_ESC_K_NAMED_BACKREF + */ + private static final Syntax SYNTAX = new Syntax(Syntax.ECMAScript.name, Syntax.ECMAScript.op, + Syntax.ECMAScript.op2 | SyntaxProperties.OP2_QMARK_LT_NAMED_GROUP + | SyntaxProperties.OP2_ESC_K_NAMED_BACKREF, + Syntax.ECMAScript.op3, Syntax.ECMAScript.behavior, Syntax.ECMAScript.options, + Syntax.ECMAScript.metaCharTable); + + JoniRegularExpression(String regex) { + this(regex, SYNTAX); + } + + JoniRegularExpression(String regex, Syntax syntax) { + validate(regex); + byte[] bytes = regex.getBytes(StandardCharsets.UTF_8); + this.pattern = new Regex(bytes, 0, bytes.length, Option.SINGLELINE, ECMAScriptUTF8Encoding.INSTANCE, syntax); + } + + protected void validate(String regex) { + // Joni is not strict with escapes + if (INVALID_ESCAPE_PATTERN.matcher(regex).matches()) { + /* + * One option considered was a custom Encoding implementation that rejects + * certain code points, but it is unable to distinguish \a vs \cG for instance as + * both translate to BEL + */ + throw new SyntaxException("Invalid escape"); + } + } + + @Override + public boolean matches(String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + return this.pattern.matcher(bytes).search(0, bytes.length, Option.NONE) >= 0; + } + + static class Arrays { + public static boolean equals(byte[] a, byte[] a2, int p, int end) { + if (a==a2) { + return true; + } + if (a==null || a2==null) { + return false; + } + + int length = a.length; + if ((end - p) != length) { + return false; + } + + for (int i=0; i + * This can be used to customize the behavior of implementations that are final. + */ + public static class DelegatingEncoding extends Encoding { + protected final Encoding delegate; + protected DelegatingEncoding(Encoding delegate) { + super(new String(delegate.getName()), delegate.minLength(), delegate.maxLength()); + this.delegate = delegate; + } + @Override + public Charset getCharset() { + return delegate.getCharset(); + } + @Override + public String getCharsetName() { + return delegate.getCharsetName(); + } + @Override + public int length(byte c) { + return delegate.length(c); + } + @Override + public int length(byte[] bytes, int p, int end) { + return delegate.length(bytes, p, end); + } + @Override + public boolean isNewLine(byte[] bytes, int p, int end) { + return delegate.isNewLine(bytes, p, end); + } + @Override + public int mbcToCode(byte[] bytes, int p, int end) { + return delegate.mbcToCode(bytes, p, end); + } + @Override + public int codeToMbcLength(int code) { + return delegate.codeToMbcLength(code); + } + @Override + public int codeToMbc(int code, byte[] bytes, int p) { + return delegate.codeToMbc(code, bytes, p); + } + @Override + public int mbcCaseFold(int flag, byte[] bytes, IntHolder pp, int end, byte[] to) { + return delegate.mbcCaseFold(flag, bytes, pp, end, to); + } + @Override + public byte[] toLowerCaseTable() { + return delegate.toLowerCaseTable(); + } + @Override + public void applyAllCaseFold(int flag, ApplyAllCaseFoldFunction fun, Object arg) { + delegate.applyAllCaseFold(flag, fun, arg); + } + @Override + public CaseFoldCodeItem[] caseFoldCodesByString(int flag, byte[] bytes, int p, int end) { + return delegate.caseFoldCodesByString(flag, bytes, p, end); + } + @Override + public int propertyNameToCType(byte[] bytes, int p, int end) { + return delegate.propertyNameToCType(bytes, p, end); + } + @Override + public boolean isCodeCType(int code, int ctype) { + return delegate.isCodeCType(code, ctype); + } + @Override + public int[] ctypeCodeRange(int ctype, IntHolder sbOut) { + return delegate.ctypeCodeRange(ctype, sbOut); + } + @Override + public int leftAdjustCharHead(byte[] bytes, int p, int s, int end) { + return delegate.leftAdjustCharHead(bytes, p, s, end); + } + @Override + public boolean isReverseMatchAllowed(byte[] bytes, int p, int end) { + return delegate.isReverseMatchAllowed(bytes, p, end); + } + @Override + public int caseMap(IntHolder flagP, byte[] bytes, IntHolder pp, int end, byte[] to, int toP, int toEnd) { + return delegate.caseMap(flagP, bytes, pp, end, to, toP, toEnd); + } + @Override + public int strLength(byte[] bytes, int p, int end) { + return delegate.strLength(bytes, p, end); + } + @Override + public int strCodeAt(byte[] bytes, int p, int end, int index) { + return delegate.strCodeAt(bytes, p, end, index); + } + @Override + public boolean isMbcCrnl(byte[] bytes, int p, int end) { + return delegate.isMbcCrnl(bytes, p, end); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java new file mode 100644 index 000000000..2d451ae39 --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/JoniRegularExpressionFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +/** + * Joni {@link RegularExpressionFactory}. + *

+ * This requires a dependency on org.jruby.joni:joni which along with its + * dependency libraries are 2 MB. + */ +public class JoniRegularExpressionFactory implements RegularExpressionFactory { + private static final JoniRegularExpressionFactory INSTANCE = new JoniRegularExpressionFactory(); + + public static JoniRegularExpressionFactory getInstance() { + return INSTANCE; + } + + @Override + public RegularExpression getRegularExpression(String regex) { + return new JoniRegularExpression(regex); + } +} diff --git a/src/main/java/com/networknt/schema/regex/RegularExpression.java b/src/main/java/com/networknt/schema/regex/RegularExpression.java new file mode 100644 index 000000000..e3cb6950e --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/RegularExpression.java @@ -0,0 +1,17 @@ +package com.networknt.schema.regex; + +import com.networknt.schema.SchemaContext; + +/** + * Regular expression. + */ +@FunctionalInterface +public interface RegularExpression { + boolean matches(String value); + + static RegularExpression compile(String regex, SchemaContext schemaContext) { + if (null == regex) return s -> true; + return schemaContext.getSchemaRegistryConfig().getRegularExpressionFactory().getRegularExpression(regex); + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java new file mode 100644 index 000000000..5aca068bf --- /dev/null +++ b/src/main/java/com/networknt/schema/regex/RegularExpressionFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +/** + * Factory for {@link RegularExpression}. + */ +public interface RegularExpressionFactory { + /** + * Gets a {@link RegularExpression}. + * + * @param regex the regular expression text value + * @return the regular expression + */ + RegularExpression getRegularExpression(String regex); +} diff --git a/src/main/java/com/networknt/schema/resource/ClasspathResourceLoader.java b/src/main/java/com/networknt/schema/resource/ClasspathResourceLoader.java new file mode 100644 index 000000000..1c283a444 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/ClasspathResourceLoader.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.function.Supplier; + +import com.networknt.schema.AbsoluteIri; + +/** + * Loads from classpath. + */ +public class ClasspathResourceLoader implements ResourceLoader { + private static class Holder { + private static final ClasspathResourceLoader INSTANCE = new ClasspathResourceLoader(); + } + + public static ClasspathResourceLoader getInstance() { + return Holder.INSTANCE; + } + + private final Supplier classLoaderSource; + + /** + * Constructor. + */ + public ClasspathResourceLoader() { + this(ClasspathResourceLoader::getClassLoader); + } + + /** + * Constructor. + * + * @param classLoaderSource the class loader source + */ + public ClasspathResourceLoader(Supplier classLoaderSource) { + this.classLoaderSource = classLoaderSource; + } + + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + String iri = absoluteIri != null ? absoluteIri.toString() : ""; + String name = null; + if (iri.startsWith("classpath:")) { + name = iri.substring(10); + } else if (iri.startsWith("resource:")) { + name = iri.substring(9); + } + if (name != null) { + ClassLoader classLoader = this.classLoaderSource.get(); + if (name.startsWith("//")) { + name = name.substring(2); + } + String resource = name; + return () -> { + InputStream result = classLoader.getResourceAsStream(resource); + if (result == null) { + result = classLoader.getResourceAsStream(resource.substring(1)); + } + if (result == null) { + throw new FileNotFoundException(iri); + } + return result; + }; + } + return null; + } + + protected static ClassLoader getClassLoader() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = ResourceLoader.class.getClassLoader(); + } + return classLoader; + } +} diff --git a/src/main/java/com/networknt/schema/resource/InputStreamSource.java b/src/main/java/com/networknt/schema/resource/InputStreamSource.java new file mode 100644 index 000000000..11831afa0 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/InputStreamSource.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream source. + */ +@FunctionalInterface +public interface InputStreamSource { + /** + * Opens a new inputstream to the resource. + * + * @return a new inputstream + * @throws IOException if an I/O error occurs + */ + InputStream getInputStream() throws IOException; +} diff --git a/src/main/java/com/networknt/schema/resource/IriResourceLoader.java b/src/main/java/com/networknt/schema/resource/IriResourceLoader.java new file mode 100644 index 000000000..456c61dfe --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/IriResourceLoader.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; + +import com.networknt.schema.AbsoluteIri; +import com.networknt.schema.utils.AbsoluteIris; + +/** + * Loads from iri. + */ +public class IriResourceLoader implements ResourceLoader { + private static class Holder { + private static final IriResourceLoader INSTANCE = new IriResourceLoader(); + } + + public static IriResourceLoader getInstance() { + return Holder.INSTANCE; + } + + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + URI uri = toURI(absoluteIri); + URL url = toURL(uri); + return () -> { + URLConnection conn = url.openConnection(); + return this.openConnectionCheckRedirects(conn); + }; + } + + /** + * Converts an AbsoluteIRI to a URI. + *

+ * Internationalized domain names will be converted using java.net.IDN.toASCII. + * + * @param absoluteIri the absolute IRI + * @return the URI + */ + protected URI toURI(AbsoluteIri absoluteIri) { + return URI.create(AbsoluteIris.toUri(absoluteIri)); + } + + /** + * Converts a URI to a URL. + *

+ * This will throw if the URI is not a valid URL. For instance if the URI is not + * absolute. + * + * @param uri the URL + * @return the URL + */ + protected URL toURL(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + // https://www.cs.mun.ca/java-api-1.5/guide/deployment/deployment-guide/upgrade-guide/article-17.html + protected InputStream openConnectionCheckRedirects(URLConnection c) throws IOException { + boolean redir; + int redirects = 0; + InputStream in = null; + do { + if (c instanceof HttpURLConnection) { + ((HttpURLConnection) c).setInstanceFollowRedirects(false); + } + // We want to open the input stream before getting headers + // because getHeaderField() et al swallow IOExceptions. + in = c.getInputStream(); + redir = false; + if (c instanceof HttpURLConnection) { + HttpURLConnection http = (HttpURLConnection) c; + int stat = http.getResponseCode(); + if (stat >= 300 && stat <= 307 && stat != 306 && stat != HttpURLConnection.HTTP_NOT_MODIFIED) { + URL base = http.getURL(); + String loc = http.getHeaderField("Location"); + URL target = null; + if (loc != null) { + target = new URL(base, loc); + } + http.disconnect(); + // Redirection should be allowed only for HTTP and HTTPS + // and should be limited to 5 redirections at most. + if (target == null || !(target.getProtocol().equals("http") || target.getProtocol().equals("https")) + || redirects >= 5) { + throw new SecurityException("Maximum number of redirects exceeded"); + } + redir = true; + c = target.openConnection(); + redirects++; + } + } + } while (redir); + return in; + } +} diff --git a/src/main/java/com/networknt/schema/resource/MapResourceLoader.java b/src/main/java/com/networknt/schema/resource/MapResourceLoader.java new file mode 100644 index 000000000..24912f3f9 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/MapResourceLoader.java @@ -0,0 +1,68 @@ +package com.networknt.schema.resource; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.function.Function; + +import com.networknt.schema.AbsoluteIri; + +/** + * Map implementation of {@link ResourceLoader}. + */ +public class MapResourceLoader implements ResourceLoader { + private final Function mappings; + + /** + * Sets the resource data by absolute IRI. + * + * @param mappings the mappings + */ + public MapResourceLoader(Map mappings) { + this(mappings::get); + } + + /** + * Sets the resource data by absolute IRI function. + * + * @param mappings the mappings + */ + public MapResourceLoader(Function mappings) { + this.mappings = mappings; + } + + /** + * Sets the resource data by using two mapping functions. + *

+ * Firstly to map the IRI to an object. If the object is null no mapping is + * performed. + *

+ * Next to map the object to the resource data. + * + * @param the type of the object + * @param mapIriToObject the mapping of IRI to object + * @param mapObjectToData the mappingof object to resource data + */ + public MapResourceLoader(Function mapIriToObject, Function mapObjectToData) { + this.mappings = iri -> { + T result = mapIriToObject.apply(iri); + if (result != null) { + return mapObjectToData.apply(result); + } + return null; + }; + } + + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + try { + String result = mappings.apply(absoluteIri.toString()); + if (result != null) { + return () -> new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + // Do nothing + } + return null; + } +} diff --git a/src/main/java/com/networknt/schema/resource/MapSchemaIdResolver.java b/src/main/java/com/networknt/schema/resource/MapSchemaIdResolver.java new file mode 100644 index 000000000..bfd4d6893 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/MapSchemaIdResolver.java @@ -0,0 +1,47 @@ +package com.networknt.schema.resource; + +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +import com.networknt.schema.AbsoluteIri; + +/** + * Map implementation of {@link SchemaIdResolver}. + */ +public class MapSchemaIdResolver implements SchemaIdResolver { + private final Function mappings; + + public MapSchemaIdResolver(Map mappings) { + this(mappings::get); + } + + public MapSchemaIdResolver(Function mappings) { + this.mappings = mappings; + } + + /** + * Apply the mapping function if the predicate is true. + * + * @param test the predicate + * @param mappings the mapping + */ + public MapSchemaIdResolver(Predicate test, Function mappings) { + this.mappings = iri -> { + if (test.test(iri)) { + return mappings.apply(iri); + } + return null; + }; + } + + @Override + public AbsoluteIri resolve(AbsoluteIri absoluteIRI) { + String mapped = this.mappings.apply(absoluteIRI.toString()); + if (mapped != null) { + return AbsoluteIri.of(mapped); + } + return null; + } + +} diff --git a/src/main/java/com/networknt/schema/resource/MetaSchemaIdResolver.java b/src/main/java/com/networknt/schema/resource/MetaSchemaIdResolver.java new file mode 100644 index 000000000..f26d18ae5 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/MetaSchemaIdResolver.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import com.networknt.schema.AbsoluteIri; + +/** + * Maps the JSON Schema meta schema to the class path location. + */ +public class MetaSchemaIdResolver implements SchemaIdResolver { + private static class Holder { + private static MetaSchemaIdResolver INSTANCE = new MetaSchemaIdResolver(); + } + + public static MetaSchemaIdResolver getInstance() { + return Holder.INSTANCE; + } + + private static final char ANCHOR = '#'; + private static final String CLASSPATH_PREFIX = "classpath:"; + private static final String HTTP_JSON_SCHEMA_ORG_PREFIX = "http://json-schema.org/"; + private static final String HTTPS_JSON_SCHEMA_ORG_PREFIX = "https://json-schema.org/"; + + @Override + public AbsoluteIri resolve(AbsoluteIri absoluteIRI) { + String absoluteIRIString = absoluteIRI != null ? absoluteIRI.toString() : null; + if (absoluteIRIString != null) { + if (absoluteIRIString.startsWith(HTTPS_JSON_SCHEMA_ORG_PREFIX)) { + return AbsoluteIri.of(CLASSPATH_PREFIX + absoluteIRIString.substring(24)); + } else if (absoluteIRIString.startsWith(HTTP_JSON_SCHEMA_ORG_PREFIX)) { + int endIndex = absoluteIRIString.length(); + if (absoluteIRIString.charAt(endIndex - 1) == ANCHOR) { + endIndex = endIndex - 1; + } + return AbsoluteIri.of(CLASSPATH_PREFIX + absoluteIRIString.substring(23, endIndex)); + } + } + return null; + } +} diff --git a/src/main/java/com/networknt/schema/resource/PrefixSchemaIdResolver.java b/src/main/java/com/networknt/schema/resource/PrefixSchemaIdResolver.java new file mode 100644 index 000000000..13caa7664 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/PrefixSchemaIdResolver.java @@ -0,0 +1,25 @@ +package com.networknt.schema.resource; + +import com.networknt.schema.AbsoluteIri; + +/** + * Prefix implementation of {@link SchemaIdResolver}. + */ +public class PrefixSchemaIdResolver implements SchemaIdResolver { + private final String source; + private final String replacement; + + public PrefixSchemaIdResolver(String source, String replacement) { + this.source = source; + this.replacement = replacement; + } + + @Override + public AbsoluteIri resolve(AbsoluteIri absoluteIRI) { + String absoluteIRIString = absoluteIRI != null ? absoluteIRI.toString() : null; + if (absoluteIRIString != null && absoluteIRIString.startsWith(source)) { + return AbsoluteIri.of(replacement + absoluteIRIString.substring(source.length())); + } + return null; + } +} diff --git a/src/main/java/com/networknt/schema/resource/ResourceLoader.java b/src/main/java/com/networknt/schema/resource/ResourceLoader.java new file mode 100644 index 000000000..aff14b9f5 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/ResourceLoader.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import com.networknt.schema.AbsoluteIri; + +/** + * Resource Loader used to load data using a retrieval IRI. + */ +@FunctionalInterface +public interface ResourceLoader { + /** + * Loads data given the retrieval IRI. + * + * @param location the retrieval IRI + * @return the input stream source + */ + InputStreamSource getResource(AbsoluteIri location); +} diff --git a/src/main/java/com/networknt/schema/resource/ResourceLoaders.java b/src/main/java/com/networknt/schema/resource/ResourceLoaders.java new file mode 100644 index 000000000..85525b53f --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/ResourceLoaders.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Resource Loaders used to load a resource given the retrieval IRI. + */ +public class ResourceLoaders extends ArrayList { + private static final long serialVersionUID = 1L; + + public ResourceLoaders() { + super(); + } + + public ResourceLoaders(Collection c) { + super(c); + } + + public ResourceLoaders(int initialCapacity) { + super(initialCapacity); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final ResourceLoaders values = new ResourceLoaders(); + + public Builder() { + } + + public Builder(Builder copy) { + this.values.addAll(copy.values); + } + + public Builder with(Builder builder) { + if (!builder.values.isEmpty()) { + this.values.addAll(builder.values); + } + return this; + } + + /** + * Customize the resource loaders. + * + * @param customizer the customizer + * @return the builder + */ + public Builder values(Consumer> customizer) { + customizer.accept(this.values); + return this; + } + + /** + * Adds a resource loader. + * + * @param resourceLoader the resource loader + * @return the builder + */ + public Builder add(ResourceLoader resourceLoader) { + this.values.add(resourceLoader); + return this; + } + + /** + * Sets the resource data by absolute IRI. + * + * @param resources the map of IRI to resource data + * @return the builder + */ + public Builder resources(Map resources) { + this.values.add(new MapResourceLoader(resources)); + return this; + } + + /** + * Sets the resource data by absolute IRI function. + * + * @param resources the function that returns resource data given IRI + * @return the builder + */ + public Builder resources(Function resources) { + this.values.add(new MapResourceLoader(resources)); + return this; + } + + /** + * Sets the resource data by using two mapping functions. + *

+ * Firstly to map the IRI to an object. If the object is null no mapping is + * performed. + *

+ * Next to map the object to the schema data. + * + * @param the type of the object + * @param mapIriToObject the mapping of IRI to object + * @param mapObjectToData the mappingof object to schema data + * @return the builder + */ + public Builder resources(Function mapIriToObject, Function mapObjectToData) { + this.values.add(new MapResourceLoader(mapIriToObject, mapObjectToData)); + return this; + } + + /** + * Builds a {@link ResourceLoaders}. + * + * @return the resource loaders + */ + public ResourceLoaders build() { + return values; + } + } +} diff --git a/src/main/java/com/networknt/schema/resource/SchemaIdResolver.java b/src/main/java/com/networknt/schema/resource/SchemaIdResolver.java new file mode 100644 index 000000000..f5b307563 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/SchemaIdResolver.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import com.networknt.schema.AbsoluteIri; + +/** + * Schema ID resolver used to map the schema $id indicated by an absolute IRI to a retrieval + * IRI. + */ +@FunctionalInterface +public interface SchemaIdResolver { + /** + * Resolves a schema $id indicated by an absolute IRI to a retrieval IRI. + * + * @param schemaId the $id of the schema + * @return the retrieval IRI or null if this resolver doesn't support the mapping + */ + AbsoluteIri resolve(AbsoluteIri schemaId); +} diff --git a/src/main/java/com/networknt/schema/resource/SchemaIdResolvers.java b/src/main/java/com/networknt/schema/resource/SchemaIdResolvers.java new file mode 100644 index 000000000..f56ad8991 --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/SchemaIdResolvers.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Schema Mappers used to map an ID indicated by an absolute IRI to a retrieval + * IRI. + */ +public class SchemaIdResolvers extends ArrayList { + private static final long serialVersionUID = 1L; + + public SchemaIdResolvers() { + super(); + } + + public SchemaIdResolvers(Collection c) { + super(c); + } + + public SchemaIdResolvers(int initialCapacity) { + super(initialCapacity); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final SchemaIdResolvers values = new SchemaIdResolvers(); + + public Builder() { + } + + public Builder(Builder copy) { + this.values.addAll(copy.values); + } + + public Builder with(Builder builder) { + if (!builder.values.isEmpty()) { + this.values.addAll(builder.values); + } + return this; + } + + /** + * Customize the schema id resolvers. + * + * @param customizer the customizer + * @return the builder + */ + public Builder values(Consumer> customizer) { + customizer.accept(this.values); + return this; + } + + /** + * Adds a schema mapper. + * + * @param schemaIdResolver the schema mapper + * @return the builder + */ + public Builder add(SchemaIdResolver schemaIdResolver) { + this.values.add(schemaIdResolver); + return this; + } + + /** + * Maps a schema given a source prefix with a replacement. + * + * @param source the source prefix + * @param replacement the replacement prefix + * @return the builder + */ + public Builder mapPrefix(String source, String replacement) { + this.values.add(new PrefixSchemaIdResolver(source, replacement)); + return this; + } + + /** + * Sets the mappings. + * + * @param mappings the mappings + * @return the builder + */ + public Builder mappings(Map mappings) { + this.values.add(new MapSchemaIdResolver(mappings)); + return this; + } + + /** + * Sets the function that maps the IRI to another IRI. + * + * @param mappings the mappings + * @return the builder + */ + public Builder mappings(Function mappings) { + this.values.add(new MapSchemaIdResolver(mappings)); + return this; + } + + /** + * Sets the function that maps the IRI to another IRI if the predicate is true. + * + * @param test the predicate + * @param mappings the mappings + * @return the builder + */ + public Builder mappings(Predicate test, Function mappings) { + this.values.add(new MapSchemaIdResolver(test, mappings)); + return this; + } + + /** + * Builds a {@link SchemaIdResolvers} + * + * @return the schema mappers + */ + public SchemaIdResolvers build() { + return values; + } + } + +} diff --git a/src/main/java/com/networknt/schema/resource/SchemaLoader.java b/src/main/java/com/networknt/schema/resource/SchemaLoader.java new file mode 100644 index 000000000..d6cb49d7d --- /dev/null +++ b/src/main/java/com/networknt/schema/resource/SchemaLoader.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import com.networknt.schema.AbsoluteIri; +import com.networknt.schema.Error; +import com.networknt.schema.InvalidSchemaException; + +/** + * Schema Loader used to load the schema resource from the schema $id. + *

+ * By default the SchemaLoader does not fetch remote resources. This must be + * explicitly configured using {@link Builder#fetchRemoteResources}. + */ +public class SchemaLoader { + private static final MetaSchemaIdResolver META_SCHEMA_ID_RESOLVER = MetaSchemaIdResolver.getInstance(); + private static final ClasspathResourceLoader CLASSPATH_RESOURCE_LOADER = ClasspathResourceLoader.getInstance(); + + private static class DefaultHolder { + private static final SchemaLoader DEFAULT = new SchemaLoader(Collections.emptyList(), Collections.emptyList()); + } + + private static class RemoteFetcher { + private static final SchemaLoader REMOTE_FETCHER = SchemaLoader.builder().fetchRemoteResources().build(); + } + + /** + * Gets the default schema loader. + *

+ * By default this does not fetch remote resources and must be explicitly + * configured to do so. + * + * @return the default schema loader + */ + public static SchemaLoader getDefault() { + return DefaultHolder.DEFAULT; + } + + /** + * Gets the schema loader the does remote fetching. + * + * @return the schema loader that does remote fetching + */ + public static SchemaLoader getRemoteFetcher() { + return RemoteFetcher.REMOTE_FETCHER; + } + + protected final List resourceLoaders; + protected final List schemaIdResolvers; + protected final Predicate allow; + protected final Predicate block; + + public SchemaLoader(ResourceLoader resourceLoader) { + this(Collections.emptyList(), Collections.singletonList(resourceLoader)); + } + + public SchemaLoader(SchemaIdResolver schemaIdResolver, ResourceLoader resourceLoader) { + this(Collections.singletonList(schemaIdResolver), Collections.singletonList(resourceLoader)); + } + + public SchemaLoader(List schemaIdResolvers, List resourceLoaders) { + this(schemaIdResolvers, resourceLoaders, null, null); + } + + public SchemaLoader(List schemaIdResolvers, List resourceLoaders, + Predicate allow, Predicate block) { + this.schemaIdResolvers = schemaIdResolvers; + this.resourceLoaders = resourceLoaders; + this.allow = allow; + this.block = block; + } + + public SchemaLoader(SchemaLoader copy) { + this(new ArrayList<>(copy.schemaIdResolvers), new ArrayList<>(copy.resourceLoaders)); + } + + public InputStreamSource getSchemaResource(AbsoluteIri absoluteIri) { + if (this.allow != null) { + if (!this.allow.test(absoluteIri)) { + throw new InvalidSchemaException(Error.builder() + .message("Schema from ''{0}'' is not allowed to be loaded.").arguments(absoluteIri).build()); + } + } + if (this.block != null) { + if (this.block.test(absoluteIri)) { + throw new InvalidSchemaException(Error.builder() + .message("Schema from ''{0}'' is not allowed to be loaded.").arguments(absoluteIri).build()); + } + } + AbsoluteIri mappedResult = absoluteIri; + for (SchemaIdResolver mapper : schemaIdResolvers) { + AbsoluteIri mapped = mapper.resolve(mappedResult); + if (mapped != null) { + mappedResult = mapped; + } + } + AbsoluteIri mapped = resolveMetaSchemaId(absoluteIri); + if (mapped != null) { + mappedResult = mapped; + } + InputStreamSource result = getClasspathResource(mappedResult); + if (result != null) { + return result; + } + for (ResourceLoader loader : resourceLoaders) { + result = loader.getResource(mappedResult); + if (result != null) { + return result; + } + } + return null; + } + + protected AbsoluteIri resolveMetaSchemaId(AbsoluteIri absoluteIri) { + return META_SCHEMA_ID_RESOLVER.resolve(absoluteIri); + } + + protected InputStreamSource getClasspathResource(AbsoluteIri absoluteIri) { + return CLASSPATH_RESOURCE_LOADER.getResource(absoluteIri); + } + + public List getResourceLoaders() { + return this.resourceLoaders; + } + + public List getSchemaIdResolvers() { + return this.schemaIdResolvers; + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(SchemaLoader copy) { + Builder builder = new Builder(); + if (!copy.getResourceLoaders().isEmpty()) { + builder.resourceLoaders(r -> r.values(c -> c.addAll(copy.resourceLoaders))); + } + if (!copy.getSchemaIdResolvers().isEmpty()) { + builder.schemaIdResolvers(r -> r.values(c -> c.addAll(copy.schemaIdResolvers))); + } + builder.allow(copy.allow); + builder.block(copy.block); + return builder; + } + + public static class Builder { + private SchemaIdResolvers.Builder schemaIdResolversBuilder = new SchemaIdResolvers.Builder(); + private ResourceLoaders.Builder resourceLoadersBuilder = new ResourceLoaders.Builder(); + private Predicate allow = null; + private Predicate block = null; + private boolean fetchRemoteResources = false; + + public Builder() { + } + + public Builder schemaIdResolvers(Consumer customizer) { + customizer.accept(schemaIdResolversBuilder); + return this; + } + + public Builder resourceLoaders(Consumer customizer) { + customizer.accept(resourceLoadersBuilder); + return this; + } + + public Builder allow(Predicate allow) { + this.allow = allow; + return this; + } + + public Builder block(Predicate block) { + this.block = block; + return this; + } + + public Builder fetchRemoteResources(boolean fetch) { + this.fetchRemoteResources = fetch; + return this; + } + + /** + * Adds the IriResourceLoader to allow fetching of remote resources. + * + * @return the builder + */ + public Builder fetchRemoteResources() { + return fetchRemoteResources(true); + } + + public SchemaIdResolvers.Builder getSchemaIdResolversBuilder() { + return schemaIdResolversBuilder; + } + + public ResourceLoaders.Builder getResourceLoadersBuilder() { + return resourceLoadersBuilder; + } + + public SchemaLoader build() { + if (this.fetchRemoteResources) { + // This ensures the IriResourceLoader is added at the end + this.resourceLoadersBuilder.add(IriResourceLoader.getInstance()); + } + return new SchemaLoader(schemaIdResolversBuilder.build(), resourceLoadersBuilder.build(), this.allow, + this.block); + } + } +} diff --git a/src/main/java/com/networknt/schema/result/InstanceResult.java b/src/main/java/com/networknt/schema/result/InstanceResult.java new file mode 100644 index 000000000..7925d13c9 --- /dev/null +++ b/src/main/java/com/networknt/schema/result/InstanceResult.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.result; + +import java.util.Objects; + +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * Instance results. + */ +public class InstanceResult { + private final NodePath instanceLocation; + private final SchemaLocation schemaLocation; + private final NodePath evaluationPath; + private final boolean valid; + + public InstanceResult(NodePath instanceLocation, SchemaLocation schemaLocation, NodePath evaluationPath, + boolean valid) { + super(); + this.instanceLocation = instanceLocation; + this.schemaLocation = schemaLocation; + this.evaluationPath = evaluationPath; + this.valid = valid; + } + + public NodePath getInstanceLocation() { + return instanceLocation; + } + + public SchemaLocation getSchemaLocation() { + return schemaLocation; + } + + public NodePath getEvaluationPath() { + return evaluationPath; + } + + public boolean isValid() { + return valid; + } + + @Override + public String toString() { + return "JsonNodeResult [instanceLocation=" + instanceLocation + ", schemaLocation=" + schemaLocation + + ", evaluationPath=" + evaluationPath + ", valid=" + valid + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(evaluationPath, instanceLocation, schemaLocation, valid); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + InstanceResult other = (InstanceResult) obj; + return Objects.equals(evaluationPath, other.evaluationPath) + && Objects.equals(instanceLocation, other.instanceLocation) + && Objects.equals(schemaLocation, other.schemaLocation) && valid == other.valid; + } + +} diff --git a/src/main/java/com/networknt/schema/result/InstanceResults.java b/src/main/java/com/networknt/schema/result/InstanceResults.java new file mode 100644 index 000000000..88e446ea6 --- /dev/null +++ b/src/main/java/com/networknt/schema/result/InstanceResults.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.result; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.path.NodePath; + +/** + * Instance results. + */ +public class InstanceResults { + + /** + * Stores the invalid results. + */ + private final Map> values = new HashMap<>(); + + public void setResult(NodePath instanceLocation, SchemaLocation schemaLocation, NodePath evaluationPath, + boolean valid) { + InstanceResult result = new InstanceResult(instanceLocation, schemaLocation, evaluationPath, valid); + List v = values.computeIfAbsent(instanceLocation, k -> new ArrayList<>()); + v.add(result); + } + + public boolean isValid(NodePath instanceLocation, NodePath evaluationPath) { + List instance = values.get(instanceLocation); + if (instance != null) { + for (InstanceResult result : instance) { + if (evaluationPath.startsWith(result.getEvaluationPath())) { + if(!result.isValid()) { + return false; + } + } + } + } + return true; + } + +} diff --git a/src/main/java/com/networknt/schema/serialization/BasicNodeReader.java b/src/main/java/com/networknt/schema/serialization/BasicNodeReader.java new file mode 100644 index 000000000..08d8c106a --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/BasicNodeReader.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.serialization; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.InputFormat; + +/** + * Basic implementation of {@link NodeReader}. + */ +public class BasicNodeReader implements NodeReader { + private static class Holder { + private static final BasicNodeReader INSTANCE = new BasicNodeReader(); + } + + public static BasicNodeReader getInstance() { + return Holder.INSTANCE; + } + + protected BasicNodeReader() { + } + + @Override + public JsonNode readTree(String content, InputFormat inputFormat) throws IOException { + return getObjectMapper(inputFormat).readTree(content); + } + + @Override + public JsonNode readTree(InputStream content, InputFormat inputFormat) throws IOException { + return getObjectMapper(inputFormat).readTree(content); + } + + /** + * Gets the object mapper for the input format. + * + * @param inputFormat the input format + * @return the object mapper + */ + protected ObjectMapper getObjectMapper(InputFormat inputFormat) { + if (InputFormat.JSON.equals(inputFormat)) { + return JsonMapperFactory.getInstance(); + } else if (InputFormat.YAML.equals(inputFormat)) { + return YamlMapperFactory.getInstance(); + } + throw new IllegalArgumentException("Unsupported input format "+inputFormat); + } +} diff --git a/src/main/java/com/networknt/schema/serialization/DefaultNodeReader.java b/src/main/java/com/networknt/schema/serialization/DefaultNodeReader.java new file mode 100644 index 000000000..242887fd5 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/DefaultNodeReader.java @@ -0,0 +1,173 @@ +package com.networknt.schema.serialization; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.InputFormat; +import com.networknt.schema.serialization.node.JsonNodeFactoryFactory; +import com.networknt.schema.serialization.node.LocationJsonNodeFactoryFactory; +import com.networknt.schema.utils.JsonNodes; + +/** + * Default {@link NodeReader}. + */ +public class DefaultNodeReader implements NodeReader { + protected final ObjectMapper jsonMapper; + protected final ObjectMapper yamlMapper; + protected final JsonNodeFactoryFactory jsonNodeFactoryFactory; + + /** + * Constructor. + * + * @param jsonMapper the json mapper + * @param yamlMapper the yaml mapper + * @param jsonNodeFactoryFactory the json node factory factory + */ + protected DefaultNodeReader(ObjectMapper jsonMapper, ObjectMapper yamlMapper, + JsonNodeFactoryFactory jsonNodeFactoryFactory) { + this.jsonMapper = jsonMapper; + this.yamlMapper = yamlMapper; + this.jsonNodeFactoryFactory = jsonNodeFactoryFactory; + } + + @Override + public JsonNode readTree(String content, InputFormat inputFormat) throws IOException { + if (this.jsonNodeFactoryFactory == null) { + return getObjectMapper(inputFormat).readTree(content); + } else { + return JsonNodes.readTree(getObjectMapper(inputFormat), content, this.jsonNodeFactoryFactory); + } + } + + @Override + public JsonNode readTree(InputStream content, InputFormat inputFormat) throws IOException { + if (this.jsonNodeFactoryFactory == null) { + return getObjectMapper(inputFormat).readTree(content); + } else { + return JsonNodes.readTree(getObjectMapper(inputFormat), content, this.jsonNodeFactoryFactory); + } + } + + /** + * Gets the yaml mapper. + * + * @return the yaml mapper + */ + protected ObjectMapper getYamlMapper() { + return this.yamlMapper != null ? this.yamlMapper : YamlMapperFactory.getInstance(); + } + + /** + * Gets the json mapper. + * + * @return the json mapper + */ + protected ObjectMapper getJsonMapper() { + return this.jsonMapper != null ? this.jsonMapper : JsonMapperFactory.getInstance(); + } + + /** + * Gets the object mapper for the input format. + * + * @param inputFormat the input format + * @return the object mapper + */ + protected ObjectMapper getObjectMapper(InputFormat inputFormat) { + if (InputFormat.JSON.equals(inputFormat)) { + return getJsonMapper(); + } else if (InputFormat.YAML.equals(inputFormat)) { + return getYamlMapper(); + } + throw new IllegalArgumentException("Unsupported input format "+inputFormat); + } + + /** + * Gets the builder for {@link DefaultNodeReader}. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder support for {@link NodeReader}. + * + * @param the super type + */ + public static abstract class BuilderSupport { + protected ObjectMapper jsonMapper = null; + protected ObjectMapper yamlMapper = null; + protected JsonNodeFactoryFactory jsonNodeFactoryFactory = null; + + protected abstract T self(); + + /** + * Sets the json mapper. + * + * @param jsonMapper the json mapper + * @return the builder + */ + public T jsonMapper(ObjectMapper jsonMapper) { + this.jsonMapper = jsonMapper; + return self(); + } + + /** + * Sets the yaml mapper + * + * @param yamlMapper the yaml mapper + * @return the builder + */ + public T yamlMapper(ObjectMapper yamlMapper) { + this.yamlMapper = yamlMapper; + return self(); + } + + /** + * Configures the {@link JsonNodeFactoryFactory} to use. + *

+ * To get location information from {@link JsonNode} the + * {@link com.networknt.schema.serialization.node.LocationJsonNodeFactoryFactory} + * can be used. + * + * @param jsonNodeFactoryFactory the factory to create json node factories + * @return the builder + */ + public T jsonNodeFactoryFactory(JsonNodeFactoryFactory jsonNodeFactoryFactory) { + this.jsonNodeFactoryFactory = jsonNodeFactoryFactory; + return self(); + } + } + + /** + * Builder for {@link DefaultNodeReader}. + */ + public static class Builder extends BuilderSupport { + + @Override + protected Builder self() { + return this; + } + + /** + * Makes the nodes generated location aware. + * + * @return the builder + */ + public Builder locationAware() { + return jsonNodeFactoryFactory(LocationJsonNodeFactoryFactory.getInstance()); + } + + /** + * Builds the {@link NodeReader}. + * + * @return the object reader + */ + public NodeReader build() { + return new DefaultNodeReader(this.jsonMapper, this.yamlMapper, this.jsonNodeFactoryFactory); + } + } +} diff --git a/src/main/java/com/networknt/schema/serialization/JsonMapperFactory.java b/src/main/java/com/networknt/schema/serialization/JsonMapperFactory.java new file mode 100644 index 000000000..7a24e2939 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/JsonMapperFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +/** + * Json Mapper Factory. + */ +public class JsonMapperFactory { + + /** + * The holder defers the classloading until it is used. + */ + private static class Holder { + private static final ObjectMapper INSTANCE = JsonMapper.builder().build(); + } + + /** + * Gets the singleton instance of the JsonMapper. + * + * @return the JsonMapper + */ + public static ObjectMapper getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/NodeReader.java b/src/main/java/com/networknt/schema/serialization/NodeReader.java new file mode 100644 index 000000000..39cff35d1 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/NodeReader.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.InputFormat; + +/** + * Reader for reading content to {@link JsonNode}. + */ +public interface NodeReader { + + /** + * Deserialize content as a tree. + * + * @param content the content + * @param inputFormat the input format + * @return the node + * @throws IOException IOException + */ + JsonNode readTree(String content, InputFormat inputFormat) throws IOException; + + /** + * Deserialize content as a tree. + * + * @param content input stream + * @param inputFormat input format + * @return the node + * @throws IOException IOException + */ + JsonNode readTree(InputStream content, InputFormat inputFormat) throws IOException; + + /** + * Creates a builder for {@link NodeReader}. + * + * @return the builder + */ + static DefaultNodeReader.Builder builder() { + return DefaultNodeReader.builder(); + } +} diff --git a/src/main/java/com/networknt/schema/serialization/YamlMapperFactory.java b/src/main/java/com/networknt/schema/serialization/YamlMapperFactory.java new file mode 100644 index 000000000..ea0b7a2df --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/YamlMapperFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; + +/** + * YAML Mapper Factory. + */ +public class YamlMapperFactory { + + /** + * The holder defers the classloading until it is used. + */ + private static class Holder { + private static final ObjectMapper INSTANCE = YAMLMapper.builder().build(); + } + + /** + * Gets the singleton instance of the YAMLMapper. + * + * @return the YAMLMapper + */ + public static ObjectMapper getInstance() { + return Holder.INSTANCE; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/JsonNodeFactoryFactory.java b/src/main/java/com/networknt/schema/serialization/node/JsonNodeFactoryFactory.java new file mode 100644 index 000000000..cfaf6e5ee --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/JsonNodeFactoryFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +/** + * Factory that produces a {@link JsonNodeFactory}. + */ +public interface JsonNodeFactoryFactory { + /** + * Gets the JsonNodeFactory. + * + * @param jsonParser the json parser + * @return the json node factory + */ + JsonNodeFactory getJsonNodeFactory(JsonParser jsonParser); +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactory.java b/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactory.java new file mode 100644 index 000000000..87fa9ec75 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactory.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BinaryNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.NumericNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import com.fasterxml.jackson.databind.util.RawValue; + +/** + * {@link JsonNodeFactory} that creates {@link TokenStreamLocationAware} nodes. + *

+ * Note that this will adversely affect performance as nodes with the same value + * can no longer be cached and reused. + */ +public class LocationJsonNodeFactory extends JsonNodeFactory { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final JsonParser jsonParser; + + /** + * Constructor. + * + * @param jsonParser the json parser + */ + public LocationJsonNodeFactory(JsonParser jsonParser) { + this.jsonParser = jsonParser; + } + + @Override + public BooleanNode booleanNode(boolean v) { + return new TokenStreamLocationAwareBooleanNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public NullNode nullNode() { + return new TokenStreamLocationAwareNullNode(this.jsonParser.currentTokenLocation()); + } + + @Override + public JsonNode missingNode() { + return super.missingNode(); + } + + @Override + public NumericNode numberNode(byte v) { + return new TokenStreamLocationAwareIntNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Byte v) { + return (v == null) ? nullNode() : numberNode(v.intValue()); + } + + @Override + public NumericNode numberNode(short v) { + return new TokenStreamLocationAwareShortNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Short value) { + return (value == null) ? nullNode() : numberNode(value.shortValue()); + } + + @Override + public NumericNode numberNode(int v) { + return new TokenStreamLocationAwareIntNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Integer v) { + return (v == null) ? nullNode() : numberNode(v.intValue()); + } + + @Override + public NumericNode numberNode(long v) { + return new TokenStreamLocationAwareLongNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Long v) { + return (v == null) ? nullNode() : numberNode(v.longValue()); + } + + @Override + public ValueNode numberNode(BigInteger v) { + return (v == null) ? nullNode() : new TokenStreamLocationAwareBigIntegerNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public NumericNode numberNode(float v) { + return new TokenStreamLocationAwareFloatNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Float v) { + return (v == null) ? nullNode() : numberNode(v.floatValue()); + } + + @Override + public NumericNode numberNode(double v) { + return new TokenStreamLocationAwareDoubleNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode numberNode(Double v) { + return (v == null) ? nullNode() : numberNode(v.doubleValue()); + } + + @Override + public ValueNode numberNode(BigDecimal v) { + return (v == null) ? nullNode() : new TokenStreamLocationAwareDecimalNode(v, this.jsonParser.currentTokenLocation()); + } + + @Override + public TextNode textNode(String text) { + return new TokenStreamLocationAwareTextNode(text, this.jsonParser.currentTokenLocation()); + } + + @Override + public BinaryNode binaryNode(byte[] data) { + return new TokenStreamLocationAwareBinaryNode(data, this.jsonParser.currentTokenLocation()); + } + + @Override + public BinaryNode binaryNode(byte[] data, int offset, int length) { + return new TokenStreamLocationAwareBinaryNode(data, offset, length, this.jsonParser.currentTokenLocation()); + } + + @Override + public ArrayNode arrayNode() { + return new TokenStreamLocationAwareArrayNode(this, this.jsonParser.currentTokenLocation()); + } + + @Override + public ArrayNode arrayNode(int capacity) { + return new TokenStreamLocationAwareArrayNode(this, capacity, this.jsonParser.currentTokenLocation()); + } + + @Override + public ObjectNode objectNode() { + return new TokenStreamLocationAwareObjectNode(this, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode pojoNode(Object pojo) { + return new TokenStreamLocationAwarePOJONode(pojo, this.jsonParser.currentTokenLocation()); + } + + @Override + public ValueNode rawValueNode(RawValue value) { + return new TokenStreamLocationAwarePOJONode(value, this.jsonParser.currentTokenLocation()); + } + +} diff --git a/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactoryFactory.java b/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactoryFactory.java new file mode 100644 index 000000000..2adee508a --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/LocationJsonNodeFactoryFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +/** + * {@link JsonNodeFactoryFactory} that produces {@link LocationJsonNodeFactory}. + *

+ * Note that this will adversely affect performance as nodes with the same value + * can no longer be cached and reused. + */ +public class LocationJsonNodeFactoryFactory implements JsonNodeFactoryFactory { + + private static final LocationJsonNodeFactoryFactory INSTANCE = new LocationJsonNodeFactoryFactory(); + + public static LocationJsonNodeFactoryFactory getInstance() { + return INSTANCE; + } + + @Override + public JsonNodeFactory getJsonNodeFactory(JsonParser jsonParser) { + return new LocationJsonNodeFactory(jsonParser); + } +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAware.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAware.java new file mode 100644 index 000000000..073970292 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAware.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; + +/** + * JsonNodes that are aware of the token location will implement this interface. + */ +public interface TokenStreamLocationAware { + /** + * Gets the token location. + * + * @return the token location + */ + JsonLocation tokenStreamLocation(); +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareArrayNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareArrayNode.java new file mode 100644 index 000000000..cfa6569d2 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareArrayNode.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import java.util.List; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +/** + * {@link ArrayNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareArrayNode extends ArrayNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareArrayNode(JsonNodeFactory nf, int capacity, JsonLocation tokenStreamLocation) { + super(nf, capacity); + this.tokenStreamLocation = tokenStreamLocation; + } + + public TokenStreamLocationAwareArrayNode(JsonNodeFactory nf, List children, JsonLocation tokenStreamLocation) { + super(nf, children); + this.tokenStreamLocation = tokenStreamLocation; + } + + public TokenStreamLocationAwareArrayNode(JsonNodeFactory nf, JsonLocation tokenStreamLocation) { + super(nf); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBigIntegerNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBigIntegerNode.java new file mode 100644 index 000000000..c3b621a98 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBigIntegerNode.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import java.math.BigInteger; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.BigIntegerNode; + +/** + * {@link BigIntegerNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareBigIntegerNode extends BigIntegerNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareBigIntegerNode(BigInteger v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBinaryNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBinaryNode.java new file mode 100644 index 000000000..dc0e46019 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBinaryNode.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.BinaryNode; + +/** + * {@link BinaryNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareBinaryNode extends BinaryNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareBinaryNode(byte[] data, JsonLocation tokenStreamLocation) { + super(data); + this.tokenStreamLocation = tokenStreamLocation; + } + + public TokenStreamLocationAwareBinaryNode(byte[] data, int offset, int length, JsonLocation tokenStreamLocation) { + super(data, offset, length); + this.tokenStreamLocation = tokenStreamLocation; + } + + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBooleanNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBooleanNode.java new file mode 100644 index 000000000..867d38445 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareBooleanNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.BooleanNode; + +/** + * {@link BooleanNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareBooleanNode extends BooleanNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareBooleanNode(boolean v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDecimalNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDecimalNode.java new file mode 100644 index 000000000..5da546b1c --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDecimalNode.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.DecimalNode; + +/** + * {@link DecimalNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareDecimalNode extends DecimalNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareDecimalNode(BigDecimal v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDoubleNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDoubleNode.java new file mode 100644 index 000000000..5c07ef536 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareDoubleNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.DoubleNode; + +/** + * {@link DoubleNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareDoubleNode extends DoubleNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareDoubleNode(double v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareFloatNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareFloatNode.java new file mode 100644 index 000000000..654d5d0bb --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareFloatNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.FloatNode; + +/** + * {@link FloatNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareFloatNode extends FloatNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareFloatNode(float v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareIntNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareIntNode.java new file mode 100644 index 000000000..5c5e29b9d --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareIntNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.IntNode; + +/** + * {@link IntNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareIntNode extends IntNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareIntNode(int v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareLongNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareLongNode.java new file mode 100644 index 000000000..5d107717f --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareLongNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.LongNode; + +/** + * {@link LongNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareLongNode extends LongNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareLongNode(long v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareNullNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareNullNode.java new file mode 100644 index 000000000..329ebab3c --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareNullNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.NullNode; + +/** + * {@link NullNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareNullNode extends NullNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareNullNode(JsonLocation tokenStreamLocation) { + super(); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareObjectNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareObjectNode.java new file mode 100644 index 000000000..b33c04741 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareObjectNode.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import java.util.Map; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * {@link ObjectNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareObjectNode extends ObjectNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareObjectNode(JsonNodeFactory nc, Map children, JsonLocation tokenStreamLocation) { + super(nc, children); + this.tokenStreamLocation = tokenStreamLocation; + } + + public TokenStreamLocationAwareObjectNode(JsonNodeFactory nc, JsonLocation tokenStreamLocation) { + super(nc); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwarePOJONode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwarePOJONode.java new file mode 100644 index 000000000..5d4c89cdb --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwarePOJONode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.POJONode; + +/** + * {@link POJONode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwarePOJONode extends POJONode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwarePOJONode(Object v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareShortNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareShortNode.java new file mode 100644 index 000000000..64c7ad886 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareShortNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.ShortNode; + +/** + * {@link ShortNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareShortNode extends ShortNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareShortNode(short v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareTextNode.java b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareTextNode.java new file mode 100644 index 000000000..32ebea8c1 --- /dev/null +++ b/src/main/java/com/networknt/schema/serialization/node/TokenStreamLocationAwareTextNode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization.node; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * {@link TextNode} that is {@link TokenStreamLocationAware}. + */ +public class TokenStreamLocationAwareTextNode extends TextNode implements TokenStreamLocationAware { + /** + * + */ + private static final long serialVersionUID = 1L; + private final JsonLocation tokenStreamLocation; + + public TokenStreamLocationAwareTextNode(String v, JsonLocation tokenStreamLocation) { + super(v); + this.tokenStreamLocation = tokenStreamLocation; + } + + @Override + public JsonLocation tokenStreamLocation() { + return this.tokenStreamLocation; + } +} diff --git a/src/main/java/com/networknt/schema/uri/ClasspathURLFactory.java b/src/main/java/com/networknt/schema/uri/ClasspathURLFactory.java deleted file mode 100644 index 47e2fc862..000000000 --- a/src/main/java/com/networknt/schema/uri/ClasspathURLFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.net.*; -import java.util.Collections; -import java.util.Set; - -/** - * A URIFactory that uses URL for creating {@link URI}s. - */ -public final class ClasspathURLFactory implements URIFactory { - static final URLStreamHandler STREAM_HANDLER = new ClasspathURLStreamHandler(); - - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet( - ClasspathURLStreamHandler.SUPPORTED_SCHEMES); - - public static URL convert(final URI uri) throws MalformedURLException { - return new URL(null, uri.toString(), STREAM_HANDLER); - } - - /** - * {@inheritDoc} - */ - @Override - public URI create(final String uri) { - try { - return new URL(null, uri, STREAM_HANDLER).toURI(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public URI create(final URI baseURI, final String segment) { - try { - return new URL(convert(baseURI), segment, STREAM_HANDLER).toURI(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } - } -} diff --git a/src/main/java/com/networknt/schema/uri/ClasspathURLFetcher.java b/src/main/java/com/networknt/schema/uri/ClasspathURLFetcher.java deleted file mode 100644 index b93fe95e9..000000000 --- a/src/main/java/com/networknt/schema/uri/ClasspathURLFetcher.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.util.Collections; -import java.util.Set; - -/** - * A URIfetcher that uses {@link URL#openStream()} for fetching and assumes given {@link URI}s - * are actualy {@link URL}s. - */ -public final class ClasspathURLFetcher implements URIFetcher { - // This fetcher handles the {@link URL}s created with the {@link ClasspathURIFactory}. - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(ClasspathURLFactory.SUPPORTED_SCHEMES); - - /** - * {@inheritDoc} - */ - @Override - public InputStream fetch(final URI uri) throws IOException { - return ClasspathURLFactory.convert(uri).openStream(); - } -} diff --git a/src/main/java/com/networknt/schema/uri/ClasspathURLStreamHandler.java b/src/main/java/com/networknt/schema/uri/ClasspathURLStreamHandler.java deleted file mode 100644 index dbe64975f..000000000 --- a/src/main/java/com/networknt/schema/uri/ClasspathURLStreamHandler.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * An {@link URLStreamHandler} capable of loading resources from the classpath. - * - * @author Kenneth Waldenstrom - */ -class ClasspathURLStreamHandler extends URLStreamHandler { - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet( - Arrays.asList("classpath", "resource"))); - - @Override - protected URLConnection openConnection(final URL pURL) throws IOException { - return new ClassPathURLConnection(pURL); - } - - class ClassPathURLConnection extends URLConnection { - - private Class mHost = null; - - protected ClassPathURLConnection(URL pURL) { - super(pURL); - } - - @Override - public final void connect() throws IOException { - String className = url.getHost(); - try { - if (className != null && className.length() > 0) { - mHost = Class.forName(className); - } - connected = true; - } catch (ClassNotFoundException e) { - throw new IOException("Class not found: " + e.toString()); - } - } - - @Override - public final InputStream getInputStream() throws IOException { - if (!connected) { - connect(); - } - return getResourceAsStream(url); - } - - private InputStream getResourceAsStream(URL pURL) throws IOException { - String path = pURL.getPath(); - - if (path.startsWith("/")) { - path = path.substring(1); - } - - InputStream stream = null; - if (mHost != null) { - stream = mHost.getClassLoader().getResourceAsStream(path); - } else { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - if (contextClassLoader != null) { - stream = contextClassLoader.getResourceAsStream(path); - } - if (stream == null) { - stream = getClass().getClassLoader().getResourceAsStream(path); - } - if (stream == null) { - stream = ClassLoader.getSystemResourceAsStream(path); - } - } - if (stream == null) { - throw new IOException("Resource " + path + " not found in classpath."); - } - return stream; - } - } - - -} diff --git a/src/main/java/com/networknt/schema/uri/URIFactory.java b/src/main/java/com/networknt/schema/uri/URIFactory.java deleted file mode 100644 index ff5e35bcd..000000000 --- a/src/main/java/com/networknt/schema/uri/URIFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.net.URI; - -/** - * The URIFactory interface defines how {@link URI}s are able to be combined and created. - */ -public interface URIFactory { - /** - * @param uri Some uri string. - * @return The converted {@link URI}. - * @throws IllegalArgumentException if there was a problem creating the {@link URI} with the given data. - */ - URI create(String uri); - - /** - * @param baseURI The base {@link URI}. - * @param segment The segment to add to the base {@link URI}. - * @return The combined {@link URI}. - * @throws IllegalArgumentException if there was a problem creating the {@link URI} with the given data. - */ - URI create(URI baseURI, String segment); -} diff --git a/src/main/java/com/networknt/schema/uri/URIFetcher.java b/src/main/java/com/networknt/schema/uri/URIFetcher.java deleted file mode 100644 index 942117cd3..000000000 --- a/src/main/java/com/networknt/schema/uri/URIFetcher.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - -/** - * The URIFetcher interface defines how file streams are able to be fetched given a {@link URI}. - */ -public interface URIFetcher { - InputStream fetch(URI uri) throws IOException; -} diff --git a/src/main/java/com/networknt/schema/uri/URISchemeFactory.java b/src/main/java/com/networknt/schema/uri/URISchemeFactory.java deleted file mode 100644 index 5eef9792f..000000000 --- a/src/main/java/com/networknt/schema/uri/URISchemeFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.net.URI; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * The URISchemaFactory will proxy to other {@link URIFactory}s based on the scheme being used. - */ -public class URISchemeFactory implements URIFactory { - private static final Pattern URI_SCHEME_PATTERN = Pattern.compile("^([a-z][a-z0-9+\\.\\-\\\\]*):"); - - private final Map uriFactories; - - public URISchemeFactory(final Map uriFactories) { - if (uriFactories == null) { - throw new IllegalArgumentException("URIFactory map must not be null"); - } - this.uriFactories = uriFactories; - } - - public Map getURIFactories() { - return this.uriFactories; - } - - private String getScheme(final String uri) { - final Matcher matcher = URI_SCHEME_PATTERN.matcher(uri); - if (matcher.find()) { - return matcher.group(1); - } else { - return null; - } - } - - private URIFactory getFactory(final String scheme) { - final URIFactory uriFactory = this.uriFactories.get(scheme); - if (uriFactory == null) { - throw new IllegalArgumentException(String.format("Unsupported URI scheme encountered: %s", scheme)); - } - return uriFactory; - } - - /** - * @param uri String - * @return URI - */ - public URI create(final String uri) { - final String scheme = this.getScheme(uri); - if (scheme == null) { - throw new IllegalArgumentException(String.format("Couldn't find URI scheme: %s", uri)); - } - - final URIFactory uriFactory = this.getFactory(scheme); - return uriFactory.create(uri); - } - - /** - * @param baseURI base URI - * @param segment URI segment - * @return URI - */ - public URI create(final URI baseURI, final String segment) { - if (baseURI == null) { - return this.create(segment); - } else { - // We first attempt to get the scheme in case the segment is an absolute URI path. - String scheme = this.getScheme(segment); - if (scheme == null) { - // In this case, the segment is relative to the baseURI. - scheme = baseURI.getScheme(); - final URIFactory uriFactory = this.getFactory(scheme); - return uriFactory.create(baseURI, segment); - } else { - // In this case, the segment is an absolute URI path. - final URIFactory uriFactory = this.getFactory(scheme); - return uriFactory.create(segment); - } - } - } -} diff --git a/src/main/java/com/networknt/schema/uri/URISchemeFetcher.java b/src/main/java/com/networknt/schema/uri/URISchemeFetcher.java deleted file mode 100644 index 9e313d7d0..000000000 --- a/src/main/java/com/networknt/schema/uri/URISchemeFetcher.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.Map; - -/** - * The URISchemeFetcher will proxy to other {@link URIFetcher}s based on the scheme being used. - */ -public class URISchemeFetcher implements URIFetcher { - private final Map uriFetchers; - - public URISchemeFetcher(final Map uriFetchers) { - if (uriFetchers == null) { - throw new IllegalArgumentException("URIFetcher map must not be null"); - } - this.uriFetchers = uriFetchers; - } - - public Map getURIFetchers() { - return this.uriFetchers; - } - - /** - * @param uri URI - * @return InputStream - */ - public InputStream fetch(final URI uri) throws IOException { - final URIFetcher uriFetcher = this.uriFetchers.get(uri.getScheme()); - if (uriFetcher == null) { - throw new IllegalArgumentException(String.format("Unsupported URI scheme encountered: %s", uri.getScheme())); - } - return uriFetcher.fetch(uri); - } -} diff --git a/src/main/java/com/networknt/schema/uri/URLFactory.java b/src/main/java/com/networknt/schema/uri/URLFactory.java deleted file mode 100644 index 544707c66..000000000 --- a/src/main/java/com/networknt/schema/uri/URLFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * A URIFactory that uses URL for creating {@link URI}s. - */ -public final class URLFactory implements URIFactory { - // These supported schemes are defined in {@link #URL(String, String, int, String)}. - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet( - Arrays.asList("http", "https", "ftp", "file", "jar"))); - - /** - * @param uri String - * @return URI - */ - @Override - public URI create(final String uri) { - try { - return new URL(uri).toURI(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } - } - - /** - * @param baseURI URI - * @param segment String - * @return URI - */ - @Override - public URI create(final URI baseURI, final String segment) { - try { - return new URL(baseURI.toURL(), segment).toURI(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Unable to create URI.", e); - } - } -} diff --git a/src/main/java/com/networknt/schema/uri/URLFetcher.java b/src/main/java/com/networknt/schema/uri/URLFetcher.java deleted file mode 100644 index 14899cf8f..000000000 --- a/src/main/java/com/networknt/schema/uri/URLFetcher.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema.uri; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; -import java.util.Collections; -import java.util.Set; - -/** - * A URIfetcher that uses {@link URL#openStream()} for fetching and assumes given {@link URI}s are actualy {@link URL}s. - */ -public final class URLFetcher implements URIFetcher { - - // These supported schemes are defined in {@link #URL(String, String, int, String)}. - // This fetcher also supports the {@link URL}s created with the {@link ClasspathURIFactory}. - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(URLFactory.SUPPORTED_SCHEMES); - - /** - * {@inheritDoc} - */ - @Override - public InputStream fetch(final URI uri) throws IOException { - URLConnection conn = uri.toURL().openConnection(); - return this.openConnectionCheckRedirects(conn); - } - - // https://www.cs.mun.ca/java-api-1.5/guide/deployment/deployment-guide/upgrade-guide/article-17.html - private InputStream openConnectionCheckRedirects(URLConnection c) throws IOException { - boolean redir; - int redirects = 0; - InputStream in = null; - do { - if (c instanceof HttpURLConnection) { - ((HttpURLConnection) c).setInstanceFollowRedirects(false); - } - // We want to open the input stream before getting headers - // because getHeaderField() et al swallow IOExceptions. - in = c.getInputStream(); - redir = false; - if (c instanceof HttpURLConnection) { - HttpURLConnection http = (HttpURLConnection) c; - int stat = http.getResponseCode(); - if (stat >= 300 && stat <= 307 && stat != 306 - && stat != HttpURLConnection.HTTP_NOT_MODIFIED) { - URL base = http.getURL(); - String loc = http.getHeaderField("Location"); - URL target = null; - if (loc != null) { - target = new URL(base, loc); - } - http.disconnect(); - // Redirection should be allowed only for HTTP and HTTPS - // and should be limited to 5 redirections at most. - if (target == null - || !(target.getProtocol().equals("http") - || target.getProtocol().equals("https")) - || redirects >= 5) { - throw new SecurityException("illegal URL redirect"); - } - redir = true; - c = target.openConnection(); - redirects++; - } - } - } - while (redir); - return in; - } -} diff --git a/src/main/java/com/networknt/schema/urn/URNFactory.java b/src/main/java/com/networknt/schema/urn/URNFactory.java deleted file mode 100644 index 5f5456ac6..000000000 --- a/src/main/java/com/networknt/schema/urn/URNFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.networknt.schema.urn; - -import java.net.URI; - -public interface URNFactory -{ - /** - * @param urn Some urn string. - * @return The converted {@link URI}. - * @throws IllegalArgumentException if there was a problem creating the {@link URI} with the given data. - */ - URI create(String urn); -} diff --git a/src/main/java/com/networknt/schema/utils/AbsoluteIris.java b/src/main/java/com/networknt/schema/utils/AbsoluteIris.java new file mode 100644 index 000000000..1c06afcbf --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/AbsoluteIris.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import java.io.UnsupportedEncodingException; +import java.net.IDN; +import java.net.URI; +import java.net.URLEncoder; + +import com.networknt.schema.AbsoluteIri; + +/** + * Utility functions for AbsoluteIri. + */ +public class AbsoluteIris { + /** + * Converts an IRI to a URI. + * + * @param iri the IRI to convert + * @return the URI string + */ + public static String toUri(AbsoluteIri iri) { + String iriString = iri.toString(); + boolean ascii = isAscii(iriString); + if (ascii) { + int index = iriString.indexOf('?'); + if (index == -1) { + return iriString; + } + String rest = iriString.substring(0, index + 1); + String query = iriString.substring(index + 1); + StringBuilder result = new StringBuilder(rest); + handleQuery(result, query); + return result.toString(); + } + String[] parts = iriString.split(":"); // scheme + rest + if (parts.length == 2) { + StringBuilder result = new StringBuilder(parts[0]); + result.append(":"); + + String rest = parts[1]; + if (rest.startsWith("//")) { + rest = rest.substring(2); + result.append("//"); + } else if (rest.startsWith("/")) { + rest = rest.substring(1); + result.append("/"); + } + String[] query = rest.split("\\?"); // rest ? query + String[] restParts = query[0].split("/"); + for (int x = 0; x < restParts.length; x++) { + String p = restParts[x]; + if (x == 0) { + // Domain + if (isAscii(p)) { + result.append(p); + } else { + result.append(unicodeToASCII(p)); + } + } else { + result.append(p); + } + if (x != restParts.length - 1) { + result.append("/"); + } + } + if (query[0].endsWith("/")) { + result.append("/"); + } + if (query.length == 2) { + // handle query string + result.append("?"); + handleQuery(result, query[1]); + } + + return URI.create(result.toString()).toASCIIString(); + } + return iriString; + } + + /** + * Determine if a string is US ASCII. + * + * @param value to test + * @return true if ASCII + */ + static boolean isAscii(String value) { + return value.codePoints().allMatch(ch -> ch < 0x7F); + } + + /** + * Ensures that the query parameters are properly URL encoded. + * + * @param result the string builder to add to + * @param query the query string + */ + static void handleQuery(StringBuilder result, String query) { + String[] queryParts = query.split("&"); + for (int y = 0; y < queryParts.length; y++) { + String queryPart = queryParts[y]; + + String[] nameValue = queryPart.split("="); + try { + result.append(URLEncoder.encode(nameValue[0], "UTF-8")); + if (nameValue.length == 2) { + result.append("="); + result.append(URLEncoder.encode(nameValue[1], "UTF-8")); + } + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + if (y != queryParts.length - 1) { + result.append("&"); + } + } + } + + // The following routines are from apache commons validator routines + // DomainValidator + static String unicodeToASCII(final String input) { + try { + final String ascii = IDN.toASCII(input); + if (IDNBUGHOLDER.IDN_TOASCII_PRESERVES_TRAILING_DOTS) { + return ascii; + } + final int length = input.length(); + if (length == 0) { // check there is a last character + return input; + } + // RFC3490 3.1. 1) + // Whenever dots are used as label separators, the following + // characters MUST be recognized as dots: U+002E (full stop), U+3002 + // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 + // (halfwidth ideographic full stop). + final char lastChar = input.charAt(length - 1);// fetch original last char + switch (lastChar) { + case '\u002E': // "." full stop + case '\u3002': // ideographic full stop + case '\uFF0E': // fullwidth full stop + case '\uFF61': // halfwidth ideographic full stop + return ascii + "."; // restore the missing stop + default: + return ascii; + } + } catch (final IllegalArgumentException e) { // input is not valid + return input; + } + } + + private static class IDNBUGHOLDER { + private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot(); + + private static boolean keepsTrailingDot() { + final String input = "a."; // must be a valid name + return input.equals(IDN.toASCII(input)); + } + } + +} diff --git a/src/main/java/com/networknt/schema/utils/CachingSupplier.java b/src/main/java/com/networknt/schema/utils/CachingSupplier.java new file mode 100644 index 000000000..bd64d3b97 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/CachingSupplier.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.utils; + +import java.util.function.Supplier; + +/** + * {@link Supplier} that caches the value. + *

+ * This is not threadsafe. + * + * @param the type of results cached by this supplier + */ +public class CachingSupplier implements Supplier { + private Supplier delegate; + private T cache = null; + + public CachingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Override + public T get() { + if (this.cache == null && this.delegate != null) { + this.cache = this.delegate.get(); + this.delegate = null; + } + return this.cache; + } +} diff --git a/src/main/java/com/networknt/schema/utils/Classes.java b/src/main/java/com/networknt/schema/utils/Classes.java new file mode 100644 index 000000000..e752a5c0e --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/Classes.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +/** + * Utility methods for classes. + */ +public class Classes { + /** + * Determines if a class is present. + * + * @param name the name of the class + * @param classLoader the class loader + * @return true if present + */ + public static boolean isPresent(String name, ClassLoader classLoader) { + try { + Class.forName(name, false, classLoader); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java new file mode 100644 index 000000000..9062aa8e1 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java @@ -0,0 +1,122 @@ +package com.networknt.schema.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaRegistryConfig; +import com.networknt.schema.SpecificationVersion; + +public class JsonNodeTypes { + private static final long V6_VALUE = SpecificationVersion.DRAFT_6.getOrder(); + + private static final String TYPE = "type"; + private static final String ENUM = "enum"; + private static final String REF = "$ref"; + private static final String NULLABLE = "nullable"; + + public static boolean isNodeNullable(JsonNode schema){ + JsonNode nullable = schema.get(NULLABLE); + return nullable != null && nullable.asBoolean(); + } + + public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, Schema parentSchema, SchemaContext schemaContext, ExecutionContext executionContext) { + SchemaRegistryConfig config = schemaContext.getSchemaRegistryConfig(); + JsonType nodeType = TypeFactory.getValueNodeType(node, config); + // in the case that node type is not the same as schema type, try to convert node to the + // same type of schema. In REST API, query parameters, path parameters and headers are all + // string type and we must convert, otherwise, all schema validations will fail. + if (nodeType != schemaType) { + if (schemaType == JsonType.ANY) { + return true; + } + + if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) { + return true; + } + if (schemaType == JsonType.INTEGER && nodeType == JsonType.NUMBER && node.canConvertToExactIntegral() && V6_VALUE <= detectVersion(schemaContext)) { + return true; + } + + if (nodeType == JsonType.NULL) { + if (parentSchema != null && schemaContext.isNullableKeywordEnabled()) { + Schema grandParentSchema = parentSchema.getParentSchema(); + if (grandParentSchema != null && JsonNodeTypes.isNodeNullable(grandParentSchema.getSchemaNode()) + || JsonNodeTypes.isNodeNullable(parentSchema.getSchemaNode())) { + return true; + } + } + } + + // Skip the type validation when the schema is an enum object schema. Since the current type + // of node itself can be used for type validation. + if (!config.isStrict("type", Boolean.TRUE) && isEnumObjectSchema(parentSchema, executionContext)) { + return true; + } + if (config.isTypeLoose()) { + // if typeLoose is true, everything can be a size 1 array + if (schemaType == JsonType.ARRAY) { + return true; + } + if (nodeType == JsonType.STRING) { + if (schemaType == JsonType.INTEGER) { + return Strings.isInteger(node.textValue()); + } else if (schemaType == JsonType.BOOLEAN) { + return Strings.isBoolean(node.textValue()); + } else if (schemaType == JsonType.NUMBER) { + return Strings.isNumeric(node.textValue()); + } + } + } + + return false; + } + return true; + } + + private static long detectVersion(SchemaContext schemaContext) { + return schemaContext.getDialect().getSpecificationVersion().getOrder(); + } + + /** + * Check if the type of the JsonNode's value is number based on the + * status of typeLoose flag. + * + * @param node the JsonNode to check + * @param config the SchemaValidatorsConfig to depend on + * @return boolean to indicate if it is a number + */ + public static boolean isNumber(JsonNode node, SchemaRegistryConfig config) { + if (node.isNumber()) { + return true; + } else if (config.isTypeLoose()) { + if (TypeFactory.getValueNodeType(node, config) == JsonType.STRING) { + return Strings.isNumeric(node.textValue()); + } + } + return false; + } + + private static boolean isEnumObjectSchema(Schema jsonSchema, ExecutionContext executionContext) { + + // There are three conditions for enum object schema + // 1. The current schema contains key "type", and the value is object + // 2. The current schema contains key "enum", and the value is an array + // 3. The parent schema if refer from components, which means the corresponding enum object class would be generated + JsonNode typeNode = null; + JsonNode enumNode = null; + boolean refNode = false; + + if (jsonSchema != null) { + if (jsonSchema.getSchemaNode() != null) { + typeNode = jsonSchema.getSchemaNode().get(TYPE); + enumNode = jsonSchema.getSchemaNode().get(ENUM); + } + refNode = REF.equals(executionContext.getEvaluationPath().getParent().getElement(-1)); + } + if (typeNode != null && enumNode != null && refNode) { + return TypeFactory.getSchemaNodeType(typeNode) == JsonType.OBJECT && enumNode.isArray(); + } + return false; + } +} diff --git a/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java b/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java deleted file mode 100644 index f0b26e7b8..000000000 --- a/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.networknt.schema.utils; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.networknt.schema.*; - -import java.util.Iterator; - -public class JsonNodeUtil { - private static final String TYPE = "type"; - private static final String ENUM = "enum"; - private static final String REF = "$ref"; - private static final String NULLABLE = "nullable"; - - public static boolean isNodeNullable(JsonNode schema){ - JsonNode nullable = schema.get(NULLABLE); - if (nullable != null && nullable.asBoolean()) { - return true; - } - return false; - } - - //Check to see if a JsonNode is nullable with checking the isHandleNullableField - public static boolean isNodeNullable(JsonNode schema, SchemaValidatorsConfig config){ - // check if the parent schema declares the fields as nullable - if (config.isHandleNullableField()) { - return isNodeNullable(schema); - } - return false; - } - - //Check to see if any child node for the OneOf SchemaNode is nullable - public static boolean isChildNodeNullable(ArrayNode oneOfSchemaNode,SchemaValidatorsConfig config){ - Iterator iterator = oneOfSchemaNode.elements(); - while(iterator.hasNext()){ - //If one of the child Node for oneOf is nullable, it means the whole oneOf is nullable - if (isNodeNullable((JsonNode)iterator.next(),config)) return true; - } - return false; - } - - public static boolean matchOneOfTypeNode(JsonNode oneOfSchemaNode, JsonType nodeType ){ - Iterator iterator = oneOfSchemaNode.elements(); - while (iterator.hasNext()){ - JsonNode oneOfTypeNode = (JsonNode) iterator.next(); - JsonNode typeTextNode = oneOfTypeNode.get(TYPE); - if(typeTextNode != null && typeTextNode.asText().equals(nodeType.toString())) //If the nodeType is oneOf the type defined in the oneOf , return true - return true; - } - return false; - } - - public static boolean equalsToSchemaType(JsonNode node, JsonSchema schema, SchemaValidatorsConfig config) { - // in the case that node type is not the same as schema type, try to convert node to the - // same type of schema. In REST API, query parameters, path parameters and headers are all - // string type and we must convert, otherwise, all schema validations will fail. - JsonType schemaType = getSchemaJsonType(schema); - return equalsToSchemaType(node,schemaType,schema.getParentSchema(),config); - - } - - public static JsonType getSchemaJsonType(JsonSchema schema){ - JsonNode typeNode = schema.getSchemaNode().get(TYPE); - if(typeNode!= null) return JsonType.valueOf(typeNode.asText().toUpperCase()); - return JsonType.UNKNOWN; - } - - - - public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, JsonSchema parentSchema, SchemaValidatorsConfig config) { - // in the case that node type is not the same as schema type, try to convert node to the - // same type of schema. In REST API, query parameters, path parameters and headers are all - // string type and we must convert, otherwise, all schema validations will fail. - - JsonType nodeType = TypeFactory.getValueNodeType(node, config); - - if (nodeType != schemaType) { - if (schemaType == JsonType.ANY) { - return true; - } - - if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) { - return true; - } - - ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); - if(JsonType.NULL.equals(nodeType)) { - if(state.isComplexValidator() && parentSchema != null) { - if( parentSchema.getParentSchema() != null && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), config) || JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode()) ) { - return true; - } - } - } - - // Skip the type validation when the schema is an enum object schema. Since the current type - // of node itself can be used for type validation. - if (isEnumObjectSchema(parentSchema)) { - return true; - } - if (config != null && config.isTypeLoose()) { - // if typeLoose is true, everything can be a size 1 array - if (schemaType == JsonType.ARRAY) { - return true; - } - if (nodeType == JsonType.STRING) { - if (schemaType == JsonType.INTEGER) { - if (isInteger(node.textValue())) { - return true; - } - } else if (schemaType == JsonType.BOOLEAN) { - if (isBoolean(node.textValue())) { - return true; - } - } else if (schemaType == JsonType.NUMBER) { - if (isNumeric(node.textValue())) { - return true; - } - } - } - } - - return false; - } - return true; - } - public static boolean isInteger(String str) { - if (str == null || str.equals("")) { - return false; - } - - // all code below could be replaced with - //return str.matrch("[-+]?(?:0|[1-9]\\d*)") - int i = 0; - if (str.charAt(0) == '-' || str.charAt(0) == '+') { - if (str.length() == 1) { - return false; - } - i = 1; - } - for (; i < str.length(); i++) { - char c = str.charAt(i); - if (c < '0' || c > '9') { - return false; - } - } - return true; - } - - public static boolean isBoolean(String s) { - return "true".equals(s) || "false".equals(s); - } - - public static boolean isNumeric(String str) { - if (str == null || str.equals("")) { - return false; - } - - // all code below could be replaced with - //return str.matrch("[-+]?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?") - int i = 0; - int len = str.length(); - - if (str.charAt(i) == MINUS || str.charAt(i) == PLUS) { - if (str.length() == 1) { - return false; - } - i = 1; - } - - char c = str.charAt(i++); - - if (c == CHAR_0) { - // TODO: if leading zeros are supported (counter to JSON spec) handle it here - if (i < len) { - c = str.charAt(i++); - if (c != DOT && c != CHAR_E && c != CHAR_e) { - return false; - } - } - } else if (CHAR_1 <= c && c <= CHAR_9) { - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } else { - return false; - } - - if (c == DOT) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } - - if (c == CHAR_E || c == CHAR_e) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - if (c == PLUS || c == MINUS) { - if (i >= len) { - return false; - } - c = str.charAt(i++); - } - while (i < len && CHAR_0 <= c && c <= CHAR_9) { - c = str.charAt(i++); - } - } - - return i >= len && (CHAR_0 <= c && c <= CHAR_9); - } - - private static final char CHAR_0 = '0'; - private static final char CHAR_1 = '1'; - private static final char CHAR_9 = '9'; - private static final char MINUS = '-'; - private static final char PLUS = '+'; - private static final char DOT = '.'; - private static final char CHAR_E = 'E'; - private static final char CHAR_e = 'e'; - - /** - * Check if the type of the JsonNode's value is number based on the - * status of typeLoose flag. - * - * @param node the JsonNode to check - * @param config the SchemaValidatorsConfig to depend on - * @return boolean to indicate if it is a number - */ - public static boolean isNumber(JsonNode node, SchemaValidatorsConfig config) { - if (node.isNumber()) { - return true; - } else if (config.isTypeLoose()) { - if (TypeFactory.getValueNodeType(node, config) == JsonType.STRING) { - return isNumeric(node.textValue()); - } - } - return false; - } - - private static boolean isEnumObjectSchema(JsonSchema jsonSchema) { - // There are three conditions for enum object schema - // 1. The current schema contains key "type", and the value is object - // 2. The current schema contains key "enum", and the value is an array - // 3. The parent schema if refer from components, which means the corresponding enum object class would be generated - JsonNode typeNode = null; - JsonNode enumNode = null; - JsonNode refNode = null; - - if (jsonSchema != null) { - if (jsonSchema.getSchemaNode() != null) { - typeNode = jsonSchema.getSchemaNode().get(TYPE); - enumNode = jsonSchema.getSchemaNode().get(ENUM); - } - if (jsonSchema.getParentSchema() != null && jsonSchema.getParentSchema().getSchemaNode() != null) { - refNode = jsonSchema.getParentSchema().getSchemaNode().get(REF); - } - } - if (typeNode != null && enumNode != null && refNode != null) { - return TypeFactory.getSchemaNodeType(typeNode) == JsonType.OBJECT && enumNode.isArray(); - } - return false; - } -} diff --git a/src/main/java/com/networknt/schema/utils/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java new file mode 100644 index 000000000..e0e76d17c --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.serialization.node.TokenStreamLocationAware; +import com.networknt.schema.serialization.node.JsonNodeFactoryFactory; + +/** + * Utility methods for JsonNode. + */ +public class JsonNodes { + /** + * Gets the node found at the path. + * + * @param the type of the node + * @param node the node + * @param path the path + * @return the node found at the path or null + */ + @SuppressWarnings("unchecked") + public static T get(JsonNode node, NodePath path) { + int nameCount = path.getNameCount(); + JsonNode current = node; + for (int x = 0; x < nameCount; x++) { + Object segment = path.getElement(x); + JsonNode result = get(current, segment); + if (result == null) { + return null; + } + current = result; + } + return (T) current; + } + + /** + * Gets the node given the property or index. + * + * @param the type of the node + * @param node the node + * @param propertyOrIndex the property or index + * @return the node given the property or index + */ + @SuppressWarnings("unchecked") + public static T get(JsonNode node, Object propertyOrIndex) { + JsonNode value = null; + if (propertyOrIndex instanceof Number && node.isArray()) { + value = node.get(((Number) propertyOrIndex).intValue()); + } else { + value = node.get(propertyOrIndex.toString()); + } + return (T) value; + } + + /** + * Read a {@link JsonNode} from {@link String} content. + * + * @param objectMapper the object mapper + * @param content the string content + * @param jsonNodeFactoryFactory the factory + * @return the json node + */ + public static JsonNode readTree(ObjectMapper objectMapper, String content, + JsonNodeFactoryFactory jsonNodeFactoryFactory) { + JsonFactory factory = objectMapper.getFactory(); + try (JsonParser parser = factory.createParser(content)) { + JsonNodeFactory nodeFactory = jsonNodeFactoryFactory.getJsonNodeFactory(parser); + ObjectReader reader = objectMapper.reader(nodeFactory); + JsonNode result = reader.readTree(parser); + return (result != null) ? result : nodeFactory.missingNode(); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid input", e); + } + } + + /** + * Read a {@link JsonNode} from an {@link InputStream}. + * + * @param objectMapper the object mapper + * @param inputStream the string content + * @param jsonNodeFactoryFactory the factory + * @return the json node + */ + public static JsonNode readTree(ObjectMapper objectMapper, InputStream inputStream, + JsonNodeFactoryFactory jsonNodeFactoryFactory) { + JsonFactory factory = objectMapper.getFactory(); + try (JsonParser parser = factory.createParser(inputStream)) { + JsonNodeFactory nodeFactory = jsonNodeFactoryFactory.getJsonNodeFactory(parser); + ObjectReader reader = objectMapper.reader(nodeFactory); + JsonNode result = reader.readTree(parser); + return (result != null) ? result : nodeFactory.missingNode(); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid input", e); + } + } + + /** + * Gets the token location of the {@link JsonNode} that implements {@link TokenStreamLocationAware}. + * + * @param jsonNode the node + * @return the JsonLocation + */ + public static JsonLocation tokenStreamLocationOf(JsonNode jsonNode) { + if (jsonNode instanceof TokenStreamLocationAware) { + return ((TokenStreamLocationAware) jsonNode).tokenStreamLocation(); + } + throw new IllegalArgumentException("JsonNode does not contain the location information."); + } +} diff --git a/src/main/java/com/networknt/schema/utils/JsonType.java b/src/main/java/com/networknt/schema/utils/JsonType.java new file mode 100644 index 000000000..709001a3b --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/JsonType.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.utils; + +/** + * Indicates the type. + */ +public enum JsonType { + OBJECT("object"), + ARRAY("array"), + STRING("string"), + NUMBER("number"), + INTEGER("integer"), + BOOLEAN("boolean"), + NULL("null"), + ANY("any"), + + UNKNOWN("unknown"), + UNION("union"); + + private final String type; + + /** + * Constructor. + * + * @param typeStr the type value + */ + JsonType(String typeStr) { + this.type = typeStr; + } + + /** + * Gets the type value. + */ + @Override + public String toString() { + return this.type; + } + +} diff --git a/src/main/java/com/networknt/schema/utils/RFC5892.java b/src/main/java/com/networknt/schema/utils/RFC5892.java new file mode 100644 index 000000000..a21931cae --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/RFC5892.java @@ -0,0 +1,413 @@ +package com.networknt.schema.utils; + +import java.net.IDN; +import java.text.Normalizer; +import java.text.ParseException; +import java.util.BitSet; +import java.util.function.BiPredicate; + +import static com.networknt.schema.utils.UnicodeDatabase.*; +import static java.lang.Character.*; + +/** + * Encapsulates the rules determining whether a label conforms to the RFC 5892 specification. + *

+ * In the context of RFC 5892. a label is a subcomponent of a DNS entry. For example, + * schema.networknt.com has three sub-components or labels: com, networknt and schema. + *

+ * Each component (or label) must satisfy the constraints identified in RFC 5892. + */ +public class RFC5892 { + + private static final String ACE_PREFIX = "xn--"; + private static final int ACE_PREFIX_LENGTH = ACE_PREFIX.length(); + + private static final int GREEK_LOWER_NUMERAL_SIGN = 0x0375; + private static final int HEBREW_GERESH = 0x05F3; + private static final int HEBREW_GERSHAYIM = 0x05F4; + private static final int KATAKANA_MIDDLE_DOT = 0x30FB; + private static final int MIDDLE_DOT = 0x00B7; + private static final int VIRAMA = 0x94D; + private static final int ZERO_WIDTH_JOINER = 0x200D; + private static final int ZERO_WIDTH_NON_JOINER = 0x200C; + + private static final BitSet CONTEXTJ = new BitSet(0x110000); + private static final BitSet CONTEXTO = new BitSet(0x110000); + private static final BitSet DISALLOWED = new BitSet(0x110000); + private static final BitSet UNASSIGNED = new BitSet(0x110000); + + private static final BiPredicate RULE_ARABIC_INDIC_DIGITS_RULE = RFC5892::testArabicIndicDigit; + private static final BiPredicate RULE_EXTENDED_ARABIC_INDIC_DIGITS_RULE = RFC5892::testExtendedArabicIndicDigit; + private static final BiPredicate RULE_GREEK_LOWER_NUMERAL_SIGN = RFC5892::testGreekLowerNumeralSign; + private static final BiPredicate RULE_HEBREW_GERESH_GERSHAYIM = RFC5892::testHebrewPuncuation; + private static final BiPredicate RULE_KATAKANA_MIDDLE_DOT = RFC5892::testKatakanaMiddleDot; + private static final BiPredicate RULE_MIDDLE_DOT = RFC5892::testeMiddleDotRule; + private static final BiPredicate RULE_ZERO_WIDTH_JOINER = RFC5892::testZeroWidthJoiner; + private static final BiPredicate RULE_ZERO_WIDTH_NON_JOINER = RFC5892::testZeroWidthNonJoiner; + + private static final BiPredicate ALLOWED_CHARACTER = RFC5892::testAllowedCharacter; + + private static final BiPredicate LTR = RFC5892::testLTR; + private static final BiPredicate RTL = RFC5892::testRTL; + + private static final BiPredicate IDNA_RULES = + ALLOWED_CHARACTER + .and(RULE_ARABIC_INDIC_DIGITS_RULE) + .and(RULE_EXTENDED_ARABIC_INDIC_DIGITS_RULE) + .and(RULE_GREEK_LOWER_NUMERAL_SIGN) + .and(RULE_HEBREW_GERESH_GERSHAYIM) + .and(RULE_KATAKANA_MIDDLE_DOT) + .and(RULE_MIDDLE_DOT) + .and(RULE_ZERO_WIDTH_JOINER) + .and(RULE_ZERO_WIDTH_NON_JOINER) + ; + + private static boolean isContextJ(int codepoint) { + if (CONTEXTJ.isEmpty()) loadDerivedProperties(); + return CONTEXTJ.get(codepoint); + } + + private static boolean isContextO(int codepoint) { + if (CONTEXTO.isEmpty()) loadDerivedProperties(); + return CONTEXTO.get(codepoint); + } + + private static boolean isDisallowed(int codepoint) { + if (DISALLOWED.isEmpty()) loadDerivedProperties(); + return DISALLOWED.get(codepoint); + } + + private static boolean isUnassigned(int codepoint) { + if (UNASSIGNED.isEmpty()) loadDerivedProperties(); + return UNASSIGNED.get(codepoint); + } + + private static boolean testAllowedCharacter(String s, int i) { + int c = s.codePointAt(i); + return !isDisallowed(c) && !isUnassigned(c) // RFC 5891 4.2.2. Rejection of Characters That Are Not Permitted + && !isContextJ(c) && !isContextO(c); // RFC 5891 4.2.3.3. Contextual Rules + } + + /** + * Whenever dots are used as label separators, the following characters MUST be + * recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), + * U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full stop) + */ + private static String LABEL_SEPARATOR_REGEX = "[\\.\\u3002\\uff0e\\uff61]"; + + public static boolean isValid(String value) { + if ("".equals(value)) { + return false; // empty string should fail + } + if (value.length() == 1 && value.matches(LABEL_SEPARATOR_REGEX)) { + return false; // single label separator should fail + } + // RFC 5892 calls each segment in a host name a label. They are separated by all the recognized label separators. + String[] labels = value.split(LABEL_SEPARATOR_REGEX); + for (String label : labels) { + if (label.isEmpty()) continue; // A DNS entry may contain a trailing '.'. + + String unicode = label; + if (isACE(label)) { + // IDN returns the original value when it encounters an issue converting to Unicode + unicode = IDN.toUnicode(label, IDN.USE_STD3_ASCII_RULES); + if (unicode.equalsIgnoreCase(label)) return false; + } + + int len = unicode.length(); + BiPredicate rules; + + // RFC 5891 5.4. Validation and Character List Testing + if (!Normalizer.isNormalized(unicode, Normalizer.Form.NFC)) return false; + + // RFC 5891 4.2.3.1. Hyphen Restrictions + if ('-' == unicode.charAt(0) || '-' == unicode.codePointBefore(len)) return false; + if (4 <= len && '-' == unicode.codePointAt(2) && '-' == unicode.codePointAt(3)) return false; + + // RFC 5891 4.2.3.2. Leading Combining Marks + if (isCombiningMark(unicode.codePointAt(0))) return false; + + // RFC 5893 2. The Bidi Rule + switch (getDirectionality(unicode.codePointAt(0))) { + case DIRECTIONALITY_LEFT_TO_RIGHT: + rules = IDNA_RULES.and(LTR); + break; + case DIRECTIONALITY_RIGHT_TO_LEFT: + case DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: + rules = IDNA_RULES.and(RTL); + break; + case DIRECTIONALITY_EUROPEAN_NUMBER: + case DIRECTIONALITY_OTHER_NEUTRALS: + rules = IDNA_RULES; + break; + default: return false; + } + + for (int i = 0; i < len; ++i) { + if (!rules.test(unicode, i)) return false; + } + + try { + String ace = IDN.toASCII(unicode, IDN.USE_STD3_ASCII_RULES); + if (63 < ace.length()) return false; // RFC 5891 4.2.4. Registration Validation Requirements + } catch (IllegalArgumentException e) { + Throwable t = e.getCause(); + if (t instanceof ParseException) { + String m = t.getMessage(); + // Ignore this. Java does not have the latest spec. + return m.startsWith("The input does not conform to the rules for BiDi code points"); + } + return false; + } + } + + return true; + } + + private static boolean isACE(String value) { + return ACE_PREFIX_LENGTH <= value.length() && + ACE_PREFIX.equalsIgnoreCase(value.substring(0, ACE_PREFIX_LENGTH)); + } + + private static boolean isCombiningMark(int codepoint) { + switch (getType(codepoint)) { + case NON_SPACING_MARK: + case ENCLOSING_MARK: + case COMBINING_SPACING_MARK: + return true; + default: + return false; + } + } + + /* RFC 5893 1.4 Terminology + * L - Left to right - most letters in LTR scripts + * R - Right to left - most letters in non-Arabic RTL scripts + * AL - Arabic letters - most letters in the Arabic script + * EN - European Number (0-9, and Extended Arabic-Indic numbers) + * ES - European Number Separator (+ and -) + * ET - European Number Terminator (currency symbols, the hash sign, the percent sign and so on) + * AN - Arabic Number; this encompasses the Arabic-Indic numbers, but not the Extended Arabic-Indic numbers + * CS - Common Number Separator (. , / : et al) + * NSM - Nonspacing Mark - most combining accents + * BN - Boundary Neutral - control characters (ZWNJ, ZWJ, and others) + * B - Paragraph Separator + * S - Segment Separator + * WS - Whitespace, including the SPACE character + * ON - Other Neutrals, including @, &, parentheses, MIDDLE DOT + * LRE, LRO, RLE, RLO, PDF - these are "directional control characters" and are not used in IDNA labels. + */ + + // RFC 5891 4.2.3.4. Labels Containing Characters Written Right to Left + private static boolean testLTR(String s, int i) { + int c = s.codePointAt(i); + switch (getDirectionality(c)) { + case DIRECTIONALITY_LEFT_TO_RIGHT: + case DIRECTIONALITY_EUROPEAN_NUMBER: + case DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR: + case DIRECTIONALITY_COMMON_NUMBER_SEPARATOR: + case DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR: + case DIRECTIONALITY_OTHER_NEUTRALS: + case DIRECTIONALITY_BOUNDARY_NEUTRAL: + case DIRECTIONALITY_NONSPACING_MARK: + return true; + default: return false; + } + } + + // RFC 5891 4.2.3.4. Labels Containing Characters Written Right to Left + private static boolean testRTL(String s, int i) { + int c = s.codePointAt(i); + switch (getDirectionality(c)) { + case DIRECTIONALITY_RIGHT_TO_LEFT: + case DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: + case DIRECTIONALITY_ARABIC_NUMBER: + case DIRECTIONALITY_EUROPEAN_NUMBER: + case DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR: + case DIRECTIONALITY_COMMON_NUMBER_SEPARATOR: + case DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR: + case DIRECTIONALITY_OTHER_NEUTRALS: + case DIRECTIONALITY_BOUNDARY_NEUTRAL: + case DIRECTIONALITY_NONSPACING_MARK: + return true; + default: return false; + } + } + + /** + * Determines whether the GREEK LOWER NUMERAL SIGN (KERAIA) conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the KERAIA within the source label + * @return {@code true} if the KERAIA rule is valid at the given location + * or the character at the given position is not the KERAIA character. + */ + private static boolean testGreekLowerNumeralSign(String s, int i) { + int c = s.codePointAt(i); + if (GREEK_LOWER_NUMERAL_SIGN == c) { + // There must be a Greek character after this symbol + if (s.length() == 1 + i) return false; + int following = s.codePointAt(i + 1); + return isGreek(following); + } + return true; + } + + /** + * Determines whether the HEBREW PUNCTUATION (GERESH or GERSHAYIM) conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the character within the source label + * @return {@code true} if the rule is valid at the given location + * or the character at the given position is not a GERESH or GERSHAYIM character. + */ + private static boolean testHebrewPuncuation(String s, int i) { + int c = s.codePointAt(i); + if (HEBREW_GERESH == c || HEBREW_GERSHAYIM == c) { + // There must be a Hebrew character before this symbol + if (0 == i) return false; + int preceding = s.codePointAt(i - 1); + return isHebrew(preceding); + } + return true; + } + + /** + * Determines whether the KATAKANA MIDDLE DOT conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the character within the source label + * @return {@code true} if the rule is valid at the given location + * or the character at the given position is not a KATAKANA MIDDLE DOT character. + */ + private static boolean testKatakanaMiddleDot(String s, int i) { + int c = s.codePointAt(i); + if (KATAKANA_MIDDLE_DOT == c) { + // There must be a Katakana, Hiragana or Han character after this symbol + if (s.length() == 1 + i) return false; + int following = s.codePointAt(i + 1); + return isKatakana(following); + } + return true; + } + + /** + * Determines whether the MIDDLE DOT conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the MIDDLE DOT within the source label + * @return {@code true} if the MIDDLE DOT rule is valid at the given location + * or the character at the given position is not the MIDDLE DOT character. + */ + private static boolean testeMiddleDotRule(String s, int i) { + int c = s.codePointAt(i); + if (MIDDLE_DOT == c) { + // There must be a 'l' character before and after this symbol + if (0 == i) return false; + if (s.length() == 1 + i) return false; + int preceding = s.codePointAt(i - 1); + int following = s.codePointAt(i + 1); + return 'l' == preceding && 'l' == following; + } + return true; + } + + /** + * Determines whether the ZERO WIDTH JOINER conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the character within the source label + * @return {@code true} if the rule is valid at the given location + * or the character at the given position is not a ZERO WIDTH JOINER character. + */ + private static boolean testZeroWidthJoiner(String s, int i) { + int c = s.codePointAt(i); + if (ZERO_WIDTH_JOINER == c) { + // There must be a virama character before this symbol. + if (0 == i) return false; + int preceding = s.codePointAt(i - 1); + return VIRAMA == preceding; + } + return true; + } + + /** + * Determines whether the ZERO WIDTH NON-OINER conforms to the RFC 5892 specification. + * + * @param s Must be a simple Unicode string; i.e., not ACE encoded + * @param i the location of the character within the source label + * @return {@code true} if the rule is valid at the given location + * or the character at the given position is not a ZERO WIDTH NON-JOINER character. + */ + private static boolean testZeroWidthNonJoiner(String s, int i) { + int c = s.codePointAt(i); + if (ZERO_WIDTH_NON_JOINER == c) { + // There must be a virama character before this symbol or + // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then True; + + if (0 == i) return false; + int preceding = s.codePointBefore(i); + if (VIRAMA == preceding) return true; + + int j = i; + while (0 < j && isJoinTypeTransparent(s.codePointBefore(j))) --j; + if (0 == j) return false; + + preceding = s.codePointBefore(j); + if (!isJoinTypeLeft(preceding) && !isJoinTypeDual(preceding)) return false; + + j = i + 1; + int len = s.length(); + if (len == j) return false; + + while (j < len && isJoinTypeTransparent(s.codePointAt(j))) ++j; + if (len == j) return false; + + int following = s.codePointAt(j); + return isJoinTypeRight(following) || isJoinTypeDual(following); + } + return true; + } + + private static boolean testArabicIndicDigit(String s, int i) { + int c = s.codePointAt(i); + if (isArabicIndicDigit(c)) { + return !s.codePoints().anyMatch(UnicodeDatabase::isExtendedArabicIndicDigit); + } + return true; + } + + private static boolean testExtendedArabicIndicDigit(String s, int i) { + int c = s.codePointAt(i); + if (isExtendedArabicIndicDigit(c)) { + return !s.codePoints().anyMatch(UnicodeDatabase::isArabicIndicDigit); + } + return true; + } + + private static synchronized void loadDerivedProperties() { + if (DISALLOWED.isEmpty()) { + UCDLoader.loadMapping("/ucd/RFC5892-appendix-B.txt", v -> { + switch (v) { + case "CONTEXTJ": return CONTEXTJ; + case "CONTEXTO": return CONTEXTO; + case "DISALLOWED": return DISALLOWED; + case "UNASSIGNED": return UNASSIGNED; + default: return null; + } + }); + + // We have IDNA rules for these. + CONTEXTJ.clear(ZERO_WIDTH_JOINER); + CONTEXTJ.clear(ZERO_WIDTH_NON_JOINER); + CONTEXTO.clear(0x660, 0x066A); // ARABIC-INDIC DIGITS + CONTEXTO.clear(0x6F0, 0x06FA); // EXTENDED ARABIC-INDIC DIGITS + CONTEXTO.clear(GREEK_LOWER_NUMERAL_SIGN); + CONTEXTO.clear(HEBREW_GERESH); + CONTEXTO.clear(HEBREW_GERSHAYIM); + CONTEXTO.clear(KATAKANA_MIDDLE_DOT); + CONTEXTO.clear(MIDDLE_DOT); + } + } + +} diff --git a/src/main/java/com/networknt/schema/utils/SchemaRefs.java b/src/main/java/com/networknt/schema/utils/SchemaRefs.java new file mode 100644 index 000000000..e6782f55e --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/SchemaRefs.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.keyword.DynamicRefValidator; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.keyword.RecursiveRefValidator; +import com.networknt.schema.keyword.RefValidator; + +/** + * Utility methods for SchemaRef. + */ +public class SchemaRefs { + + /** + * Gets the ref. + * + * @param schema the schema + * @return the ref + */ + public static SchemaRef from(Schema schema, ExecutionContext executionContext) { + for (KeywordValidator validator : schema.getValidators()) { + if (validator instanceof RefValidator) { + return ((RefValidator) validator).getSchemaRef(); + } else if (validator instanceof DynamicRefValidator) { + return ((DynamicRefValidator) validator).getSchemaRef(executionContext); + } else if (validator instanceof RecursiveRefValidator) { + return ((RecursiveRefValidator) validator).getSchemaRef(executionContext); + } + } + return null; + } + +} diff --git a/src/main/java/com/networknt/schema/utils/Strings.java b/src/main/java/com/networknt/schema/utils/Strings.java new file mode 100644 index 000000000..d16f7202e --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/Strings.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for working with Strings. + */ +public final class Strings { + private static final char CHAR_0 = '0'; + private static final char CHAR_1 = '1'; + private static final char CHAR_9 = '9'; + private static final char MINUS = '-'; + private static final char PLUS = '+'; + private static final char DOT = '.'; + private static final char CHAR_E = 'E'; + private static final char CHAR_e = 'e'; + + private Strings() { + } + + public static boolean isInteger(String string) { + if (string == null || string.isEmpty()) { + return false; + } + + // all code below could be replaced with + //return str.matrch("[-+]?(?:0|[1-9]\\d*)") + int i = 0; + if (string.charAt(0) == '-' || string.charAt(0) == '+') { + if (string.length() == 1) { + return false; + } + i = 1; + } + for (; i < string.length(); i++) { + char c = string.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } + + public static boolean isBoolean(String string) { + return "true".equals(string) || "false".equals(string); + } + + public static boolean isNumeric(String string) { + if (string == null || string.isEmpty()) { + return false; + } + + // all code below could be replaced with + //return str.matrch("[-+]?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?") + int i = 0; + int len = string.length(); + + if (string.charAt(i) == MINUS || string.charAt(i) == PLUS) { + if (string.length() == 1) { + return false; + } + i = 1; + } + + char character = string.charAt(i++); + + if (character == CHAR_0) { + // TODO: if leading zeros are supported (counter to JSON spec) handle it here + if (i < len) { + character = string.charAt(i++); + if (character != DOT && character != CHAR_E && character != CHAR_e) { + return false; + } + } + } else if (CHAR_1 <= character && character <= CHAR_9) { + while (i < len && CHAR_0 <= character && character <= CHAR_9) { + character = string.charAt(i++); + } + } else { + return false; + } + + if (character == DOT) { + if (i >= len) { + return false; + } + character = string.charAt(i++); + while (i < len && CHAR_0 <= character && character <= CHAR_9) { + character = string.charAt(i++); + } + } + + if (character == CHAR_E || character == CHAR_e) { + if (i >= len) { + return false; + } + character = string.charAt(i++); + if (character == PLUS || character == MINUS) { + if (i >= len) { + return false; + } + character = string.charAt(i++); + } + while (i < len && CHAR_0 <= character && character <= CHAR_9) { + character = string.charAt(i++); + } + } + + return i >= len && (CHAR_0 <= character && character <= CHAR_9); + } + + public static boolean isBlank(String string) { + return null == string || string.trim().isEmpty(); + } + + /** + * Split text. Unlike the JDK String split using regex trailing delimiters are + * preserved. + * + * @param text the text to split + * @param delimiter the delimiter + * @return the fragments + */ + public static String[] split(String text, char delimiter) { + if (text == null) { + return new String[0]; + } + if (text.isEmpty()) { + return new String[]{""}; + } + + List segments = new ArrayList<>(); + int start = 0; + int end; + + while (start <= text.length()) { + end = text.indexOf(delimiter, start); + + if (end == -1) { + end = text.length(); + } + + String segment = text.substring(start, end); + segments.add(segment); + + if (end == text.length()) { + break; + } + + start = end + 1; + } + + return segments.toArray(new String[segments.size()]); + } +} diff --git a/src/main/java/com/networknt/schema/utils/ThreadSafeCachingSupplier.java b/src/main/java/com/networknt/schema/utils/ThreadSafeCachingSupplier.java new file mode 100644 index 000000000..d4a9c1f63 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/ThreadSafeCachingSupplier.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import java.util.function.Supplier; + +/** + * {@link Supplier} that caches the value. + * + * @param the type of results cached by this supplier + */ +public class ThreadSafeCachingSupplier implements Supplier { + private volatile Supplier delegate; + private volatile T cache = null; + + public ThreadSafeCachingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Override + public T get() { + if (this.delegate == null) { + return this.cache; + } + + synchronized(this) { + if (this.delegate != null) { + this.cache = this.delegate.get(); + this.delegate = null; + } + } + return this.cache; + } +} diff --git a/src/main/java/com/networknt/schema/utils/TypeFactory.java b/src/main/java/com/networknt/schema/utils/TypeFactory.java new file mode 100644 index 000000000..ffb1c4f35 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/TypeFactory.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.networknt.schema.SchemaRegistryConfig; + +/** + * Type factory. + */ +public class TypeFactory { + /** + * Gets the {@link JsonType} indicated by the schema node. + * + * @param node the schema node + * @return the json type + */ + public static JsonType getSchemaNodeType(JsonNode node) { + //Single Type Definition + if (node.isTextual()) { + String type = node.textValue(); + if ("object".equals(type)) { + return JsonType.OBJECT; + } + if ("array".equals(type)) { + return JsonType.ARRAY; + } + if ("string".equals(type)) { + return JsonType.STRING; + } + if ("number".equals(type)) { + return JsonType.NUMBER; + } + if ("integer".equals(type)) { + return JsonType.INTEGER; + } + if ("boolean".equals(type)) { + return JsonType.BOOLEAN; + } + if ("any".equals(type)) { + return JsonType.ANY; + } + if ("null".equals(type)) { + return JsonType.NULL; + } + } + + //Union Type Definition + if (node.isArray()) { + return JsonType.UNION; + } + + return JsonType.UNKNOWN; + } + + /** + * Gets the {@link JsonType} of the value node. + * + * @param node the value node + * @param config the config + * @return the json type + */ + public static JsonType getValueNodeType(JsonNode node, SchemaRegistryConfig config) { + if (node == null) { + // This returns JsonType.UNKNOWN to be consistent with the behavior when + // JsonNodeType.MISSING + return JsonType.UNKNOWN; + } + JsonNodeType type = node.getNodeType(); + switch (type) { + case OBJECT: + return JsonType.OBJECT; + case ARRAY: + return JsonType.ARRAY; + case STRING: + case BINARY: + return JsonType.STRING; + case NUMBER: + if (node.isIntegralNumber()) { + return JsonType.INTEGER; + } else if (config != null && config.isLosslessNarrowing() && node.canConvertToExactIntegral()) { + return JsonType.INTEGER; + } else { + return JsonType.NUMBER; + } + case BOOLEAN: + return JsonType.BOOLEAN; + case NULL: + return JsonType.NULL; + default: + return JsonType.UNKNOWN; + } + } +} diff --git a/src/main/java/com/networknt/schema/utils/UCDLoader.java b/src/main/java/com/networknt/schema/utils/UCDLoader.java new file mode 100644 index 000000000..c64afcb75 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/UCDLoader.java @@ -0,0 +1,43 @@ +package com.networknt.schema.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.util.BitSet; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.networknt.schema.format.IdnHostnameFormat; + +public class UCDLoader { + private static final Logger logger = LoggerFactory.getLogger(UCDLoader.class); + + static void loadMapping(String filename, Function selector) { + try ( + InputStream is = IdnHostnameFormat.class.getResourceAsStream(filename); + LineNumberReader rd = new LineNumberReader(new InputStreamReader(is)) + ) { + rd.lines().forEach(line -> { + if (!line.isEmpty() && '#' != line.charAt(0)) { + String[] s = line.split("\\s*[;#]\\s*", 3); + + BitSet bs = selector.apply(s[1]); + if (null != bs) { + String[] n = s[0].split("\\.\\."); + switch (n.length) { + case 2: bs.set(Integer.parseUnsignedInt(n[0], 16), 1 + Integer.parseUnsignedInt(n[1], 16)); break; + case 1: bs.set(Integer.parseUnsignedInt(n[0], 16)); break; + default: throw new IllegalStateException("Unable to parse integer range on line " + rd.getLineNumber()); + } + } + } + }); + } catch (IllegalStateException | IOException e) { + logger.error("unable to load Unicode data from file '{}': {}", filename, e.getMessage()); + } + } + +} diff --git a/src/main/java/com/networknt/schema/utils/UnicodeDatabase.java b/src/main/java/com/networknt/schema/utils/UnicodeDatabase.java new file mode 100644 index 000000000..70812ccfb --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/UnicodeDatabase.java @@ -0,0 +1,104 @@ +package com.networknt.schema.utils; + +import java.util.BitSet; + +public class UnicodeDatabase { + private static final BitSet ARABIC_INDIC_DIGITS = new BitSet(0x11000); + private static final BitSet EXTENDED_ARABIC_INDIC_DIGITS = new BitSet(0x11000); + private static final BitSet GREEK_CHARACTERS = new BitSet(0x2000); + private static final BitSet HEBREW_CHARACTERS = new BitSet(0x0600); + private static final BitSet KATAKANA_CHARACTERS = new BitSet(0x33000); + + private static final BitSet JOIN_TYPE_CAUSING = new BitSet(0x110000); + private static final BitSet JOIN_TYPE_DUAL = new BitSet(0x110000); + private static final BitSet JOIN_TYPE_LEFT = new BitSet(0x110000); + private static final BitSet JOIN_TYPE_RIGHT = new BitSet(0x110000); + private static final BitSet JOIN_TYPE_TRANSPARENT = new BitSet(0x110000); + + static { + // TODO: Should we initialize this lazily? + ARABIC_INDIC_DIGITS.set(0x0660, 0x066A); + EXTENDED_ARABIC_INDIC_DIGITS.set(0x06F0, 0x6FA); + GREEK_CHARACTERS.set(0x0370, 0x0400); + GREEK_CHARACTERS.set(0x1F00, 0x2000); + HEBREW_CHARACTERS.set(0x0590, 0x0600); + KATAKANA_CHARACTERS.set(0x2E80, 0x2F00); // The CJK Radicals Supplement code block + KATAKANA_CHARACTERS.set(0x2F00, 0x2FE0); // The Kangxi Radicals code block + KATAKANA_CHARACTERS.set(0x3000, 0x3040); // The CJK Symbols and Punctuation code block + KATAKANA_CHARACTERS.set(0x3040, 0x30A0); // The Hiragana code block. + KATAKANA_CHARACTERS.set(0x30A0, 0x3100); // The Katakana code block. + KATAKANA_CHARACTERS.set(0x3400, 0x4DC0); // The CJK Unified Ideographs Extension A code block + KATAKANA_CHARACTERS.set(0x4E00, 0xA000); // The CJK Unified Ideographs code block + KATAKANA_CHARACTERS.set(0xF900, 0xFB00); // The CJK Compatibility Ideographs code block + KATAKANA_CHARACTERS.set(0x16FE0, 0x17000); // The Ideographic Symbols and Punctuation code block + KATAKANA_CHARACTERS.set(0x20000, 0x2A6E0); // The CJK Unified Ideographs Extension B code block + KATAKANA_CHARACTERS.set(0x2A700, 0x2B740); // The CJK Unified Ideographs Extension C code block + KATAKANA_CHARACTERS.set(0x2B740, 0x2B820); // The CJK Unified Ideographs Extension D code block + KATAKANA_CHARACTERS.set(0x2B820, 0x2CEB0); // The CJK Unified Ideographs Extension E code block + KATAKANA_CHARACTERS.set(0x2CEB0, 0x2EBF0); // The CJK Unified Ideographs Extension F code block + KATAKANA_CHARACTERS.set(0x2F800, 0x2FA20); // The CJK Compatibility Ideographs Supplement code block + KATAKANA_CHARACTERS.set(0x30000, 0x31350); // The CJK Unified Ideographs Extension G code block + KATAKANA_CHARACTERS.set(0x31350, 0x323B0); // The CJK Unified Ideographs Extension H code block + } + + public static boolean isArabicIndicDigit(int codepoint) { + return ARABIC_INDIC_DIGITS.get(codepoint); + } + + public static boolean isExtendedArabicIndicDigit(int codepoint) { + return EXTENDED_ARABIC_INDIC_DIGITS.get(codepoint); + } + + public static boolean isGreek(int codepoint) { + return GREEK_CHARACTERS.get(codepoint); + } + + public static boolean isHebrew(int codepoint) { + return HEBREW_CHARACTERS.get(codepoint); + } + + public static boolean isKatakana(int codepoint) { + return KATAKANA_CHARACTERS.get(codepoint); + } + + public static boolean isJoinTypeCausing(int codepoint) { + if (JOIN_TYPE_CAUSING.isEmpty()) loadJoiningTypes(); + return JOIN_TYPE_CAUSING.get(codepoint); + } + + public static boolean isJoinTypeDual(int codepoint) { + if (JOIN_TYPE_DUAL.isEmpty()) loadJoiningTypes(); + return JOIN_TYPE_DUAL.get(codepoint); + } + + public static boolean isJoinTypeLeft(int codepoint) { + if (JOIN_TYPE_LEFT.isEmpty()) loadJoiningTypes(); + return JOIN_TYPE_LEFT.get(codepoint); + } + + public static boolean isJoinTypeRight(int codepoint) { + if (JOIN_TYPE_RIGHT.isEmpty()) loadJoiningTypes(); + return JOIN_TYPE_RIGHT.get(codepoint); + } + + public static boolean isJoinTypeTransparent(int codepoint) { + if (JOIN_TYPE_TRANSPARENT.isEmpty()) loadJoiningTypes(); + return JOIN_TYPE_TRANSPARENT.get(codepoint); + } + + private static synchronized void loadJoiningTypes() { + if (JOIN_TYPE_DUAL.isEmpty()) { + UCDLoader.loadMapping("/ucd/extracted/DerivedJoiningType.txt", v -> { + switch (v) { + case "C": return JOIN_TYPE_CAUSING; + case "D": return JOIN_TYPE_DUAL; + case "L": return JOIN_TYPE_LEFT; + case "R": return JOIN_TYPE_RIGHT; + case "T": return JOIN_TYPE_TRANSPARENT; + default: return null; + } + }); + } + } + +} diff --git a/src/main/java/com/networknt/schema/vocabulary/Vocabularies.java b/src/main/java/com/networknt/schema/vocabulary/Vocabularies.java new file mode 100644 index 000000000..f54d1e2c4 --- /dev/null +++ b/src/main/java/com/networknt/schema/vocabulary/Vocabularies.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.vocabulary; + +import java.util.HashMap; +import java.util.Map; + +/** + * Vocabularies. + */ +public class Vocabularies { + private static final Map VALUES; + + static { + Map mapping = new HashMap<>(); + mapping.put(Vocabulary.DRAFT_2019_09_CORE.getId(), Vocabulary.DRAFT_2019_09_CORE); + mapping.put(Vocabulary.DRAFT_2019_09_APPLICATOR.getId(), Vocabulary.DRAFT_2019_09_APPLICATOR); + mapping.put(Vocabulary.DRAFT_2019_09_VALIDATION.getId(), Vocabulary.DRAFT_2019_09_VALIDATION); + mapping.put(Vocabulary.DRAFT_2019_09_META_DATA.getId(), Vocabulary.DRAFT_2019_09_META_DATA); + mapping.put(Vocabulary.DRAFT_2019_09_FORMAT.getId(), Vocabulary.DRAFT_2019_09_FORMAT); + mapping.put(Vocabulary.DRAFT_2019_09_CONTENT.getId(), Vocabulary.DRAFT_2019_09_CONTENT); + + mapping.put(Vocabulary.DRAFT_2020_12_CORE.getId(), Vocabulary.DRAFT_2020_12_CORE); + mapping.put(Vocabulary.DRAFT_2020_12_APPLICATOR.getId(), Vocabulary.DRAFT_2020_12_APPLICATOR); + mapping.put(Vocabulary.DRAFT_2020_12_UNEVALUATED.getId(), Vocabulary.DRAFT_2020_12_UNEVALUATED); + mapping.put(Vocabulary.DRAFT_2020_12_VALIDATION.getId(), Vocabulary.DRAFT_2020_12_VALIDATION); + mapping.put(Vocabulary.DRAFT_2020_12_META_DATA.getId(), Vocabulary.DRAFT_2020_12_META_DATA); + mapping.put(Vocabulary.DRAFT_2020_12_FORMAT_ANNOTATION.getId(), Vocabulary.DRAFT_2020_12_FORMAT_ANNOTATION); + mapping.put(Vocabulary.DRAFT_2020_12_FORMAT_ASSERTION.getId(), Vocabulary.DRAFT_2020_12_FORMAT_ASSERTION); + mapping.put(Vocabulary.DRAFT_2020_12_CONTENT.getId(), Vocabulary.DRAFT_2020_12_CONTENT); + + mapping.put(Vocabulary.OPENAPI_3_1_BASE.getId(), Vocabulary.OPENAPI_3_1_BASE); + + VALUES = mapping; + } + + /** + * Gets the vocabulary given its uri. + * + * @param uri the vocabulary + * @return the vocabulary + */ + public static Vocabulary getVocabulary(String uri) { + return VALUES.get(uri); + } +} diff --git a/src/main/java/com/networknt/schema/vocabulary/Vocabulary.java b/src/main/java/com/networknt/schema/vocabulary/Vocabulary.java new file mode 100644 index 000000000..1505b9016 --- /dev/null +++ b/src/main/java/com/networknt/schema/vocabulary/Vocabulary.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.vocabulary; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.NonValidationKeyword; +import com.networknt.schema.keyword.KeywordType; + +/** + * Represents a vocabulary in meta-schema. + *

+ * This contains the id and the keywords in the vocabulary. + */ +public class Vocabulary { + + // 2019-09 + public static final Vocabulary DRAFT_2019_09_CORE = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/core", + new NonValidationKeyword("$id"), new NonValidationKeyword("$schema"), new NonValidationKeyword("$anchor"), + KeywordType.REF, KeywordType.RECURSIVE_REF, new NonValidationKeyword("$recursiveAnchor"), + new NonValidationKeyword("$vocabulary"), new NonValidationKeyword("$comment"), + new NonValidationKeyword("$defs")); + public static final Vocabulary DRAFT_2019_09_APPLICATOR = new Vocabulary( + "https://json-schema.org/draft/2019-09/vocab/applicator", new NonValidationKeyword("additionalItems"), + KeywordType.UNEVALUATED_ITEMS, KeywordType.ITEMS_LEGACY, KeywordType.CONTAINS, + KeywordType.ADDITIONAL_PROPERTIES, KeywordType.UNEVALUATED_PROPERTIES, + KeywordType.PROPERTIES, KeywordType.PATTERN_PROPERTIES, KeywordType.DEPENDENT_SCHEMAS, + KeywordType.PROPERTY_NAMES, KeywordType.IF_THEN_ELSE, new NonValidationKeyword("then"), + new NonValidationKeyword("else"), KeywordType.ALL_OF, KeywordType.ANY_OF, + KeywordType.ONE_OF, KeywordType.NOT); + public static final Vocabulary DRAFT_2019_09_VALIDATION = new Vocabulary( + "https://json-schema.org/draft/2019-09/vocab/validation", KeywordType.MULTIPLE_OF, + KeywordType.MAXIMUM, KeywordType.EXCLUSIVE_MAXIMUM, KeywordType.MINIMUM, + KeywordType.EXCLUSIVE_MINIMUM, KeywordType.MAX_LENGTH, KeywordType.MIN_LENGTH, + KeywordType.PATTERN, KeywordType.MAX_ITEMS, KeywordType.MIN_ITEMS, + KeywordType.UNIQUE_ITEMS, KeywordType.MAX_CONTAINS, KeywordType.MIN_CONTAINS, + KeywordType.MAX_PROPERTIES, KeywordType.MIN_PROPERTIES, KeywordType.REQUIRED, + KeywordType.DEPENDENT_REQUIRED, KeywordType.CONST, KeywordType.ENUM, + KeywordType.TYPE); + public static final Vocabulary DRAFT_2019_09_META_DATA = new Vocabulary( + "https://json-schema.org/draft/2019-09/vocab/meta-data", new AnnotationKeyword("title"), + new AnnotationKeyword("description"), new AnnotationKeyword("default"), new AnnotationKeyword("deprecated"), + KeywordType.READ_ONLY, KeywordType.WRITE_ONLY, new AnnotationKeyword("examples")); + public static final Vocabulary DRAFT_2019_09_FORMAT = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/format", + KeywordType.FORMAT); + public static final Vocabulary DRAFT_2019_09_CONTENT = new Vocabulary( + "https://json-schema.org/draft/2019-09/vocab/content", new AnnotationKeyword("contentMediaType"), + new AnnotationKeyword("contentEncoding"), new AnnotationKeyword("contentSchema")); + + // 2020-12 + public static final Vocabulary DRAFT_2020_12_CORE = new Vocabulary("https://json-schema.org/draft/2020-12/vocab/core", + new NonValidationKeyword("$id"), new NonValidationKeyword("$schema"), KeywordType.REF, + new NonValidationKeyword("$anchor"), KeywordType.DYNAMIC_REF, + new NonValidationKeyword("$dynamicAnchor"), new NonValidationKeyword("$vocabulary"), + new NonValidationKeyword("$comment"), new NonValidationKeyword("$defs")); + public static final Vocabulary DRAFT_2020_12_APPLICATOR = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/applicator", KeywordType.PREFIX_ITEMS, + KeywordType.ITEMS, KeywordType.CONTAINS, KeywordType.ADDITIONAL_PROPERTIES, + KeywordType.PROPERTIES, KeywordType.PATTERN_PROPERTIES, KeywordType.DEPENDENT_SCHEMAS, + KeywordType.PROPERTY_NAMES, KeywordType.IF_THEN_ELSE, new NonValidationKeyword("then"), + new NonValidationKeyword("else"), KeywordType.ALL_OF, KeywordType.ANY_OF, + KeywordType.ONE_OF, KeywordType.NOT); + public static final Vocabulary DRAFT_2020_12_UNEVALUATED = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/unevaluated", KeywordType.UNEVALUATED_ITEMS, + KeywordType.UNEVALUATED_PROPERTIES); + public static final Vocabulary DRAFT_2020_12_VALIDATION = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/validation", KeywordType.TYPE, KeywordType.CONST, + KeywordType.ENUM, KeywordType.MULTIPLE_OF, KeywordType.MAXIMUM, + KeywordType.EXCLUSIVE_MAXIMUM, KeywordType.MINIMUM, KeywordType.EXCLUSIVE_MINIMUM, + KeywordType.MAX_LENGTH, KeywordType.MIN_LENGTH, KeywordType.PATTERN, + KeywordType.MAX_ITEMS, KeywordType.MIN_ITEMS, KeywordType.UNIQUE_ITEMS, + KeywordType.MAX_CONTAINS, KeywordType.MIN_CONTAINS, KeywordType.MAX_PROPERTIES, + KeywordType.MIN_PROPERTIES, KeywordType.REQUIRED, KeywordType.DEPENDENT_REQUIRED); + public static final Vocabulary DRAFT_2020_12_META_DATA = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/meta-data", new AnnotationKeyword("title"), + new AnnotationKeyword("description"), new AnnotationKeyword("default"), new AnnotationKeyword("deprecated"), + KeywordType.READ_ONLY, KeywordType.WRITE_ONLY, new AnnotationKeyword("examples")); + public static final Vocabulary DRAFT_2020_12_FORMAT_ANNOTATION = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/format-annotation", KeywordType.FORMAT); + public static final Vocabulary DRAFT_2020_12_FORMAT_ASSERTION = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/format-assertion", KeywordType.FORMAT); + public static final Vocabulary DRAFT_2020_12_CONTENT = new Vocabulary( + "https://json-schema.org/draft/2020-12/vocab/content", new AnnotationKeyword("contentEncoding"), + new AnnotationKeyword("contentMediaType"), new AnnotationKeyword("contentSchema")); + + // OpenAPI 3.1 + public static final Vocabulary OPENAPI_3_1_BASE = new Vocabulary("https://spec.openapis.org/oas/3.1/vocab/base", + new AnnotationKeyword("example"), KeywordType.DISCRIMINATOR, new AnnotationKeyword("externalDocs"), + new AnnotationKeyword("xml")); + + private final String id; + private final Set keywords; + + /** + * Constructor. + * + * @param id the id + * @param keywords the keywords + */ + public Vocabulary(String id, Keyword... keywords) { + this.id = id; + this.keywords = new LinkedHashSet<>(); + this.keywords.addAll(Arrays.asList(keywords)); + } + + /** + * The id of the vocabulary. + * + * @return the id + */ + public String getId() { + return id; + } + + /** + * The keywords in the vocabulary. + * + * @return the keywords + */ + public Set getKeywords() { + return keywords; + } + + @Override + public int hashCode() { + return Objects.hash(id, keywords); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vocabulary other = (Vocabulary) obj; + return Objects.equals(id, other.id) && Objects.equals(keywords, other.keywords); + } + + @Override + public String toString() { + return "Vocabulary [id=" + id + ", keywords=" + keywords + "]"; + } + +} diff --git a/src/main/java/com/networknt/schema/vocabulary/VocabularyRegistry.java b/src/main/java/com/networknt/schema/vocabulary/VocabularyRegistry.java new file mode 100644 index 000000000..e712c652f --- /dev/null +++ b/src/main/java/com/networknt/schema/vocabulary/VocabularyRegistry.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.vocabulary; + +/** + * Registry for {@link Vocabulary}. + */ +@FunctionalInterface +public interface VocabularyRegistry { + /** + * Gets the vocabulary given the vocabulary id. + * + * @param id the vocabulary id which is an iri + * @return the vocabulary + */ + Vocabulary getVocabulary(String id); +} diff --git a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java index 9d0a94337..b7ef458f2 100644 --- a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java @@ -1,32 +1,27 @@ package com.networknt.schema.walk; import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.ValidationContext; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.Error; import java.util.List; -import java.util.Set; public abstract class AbstractWalkListenerRunner implements WalkListenerRunner { - protected String getKeywordName(String keyWordPath) { - return keyWordPath.substring(keyWordPath.lastIndexOf('/') + 1); + protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator) { + return WalkEvent.builder().executionContext(executionContext).instanceLocation(instanceLocation) + .keyword(keyword).instanceNode(instanceNode) + .rootNode(rootNode).schema(schema).validator(validator).build(); } - protected WalkEvent constructWalkEvent(String keyWordName, JsonNode node, JsonNode rootNode, String at, - String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory) { - return WalkEvent.builder().at(at).keyWordName(keyWordName).node(node).parentSchema(parentSchema) - .rootNode(rootNode).schemaNode(schemaNode).schemaPath(schemaPath) - .currentJsonSchemaFactory(currentJsonSchemaFactory).validationContext(validationContext).build(); - } - - protected boolean runPreWalkListeners(List walkListeners, WalkEvent walkEvent) { + protected boolean runPreWalkListeners(List walkListeners, WalkEvent walkEvent) { boolean continueToWalkMethod = true; if (walkListeners != null) { - for (JsonSchemaWalkListener walkListener : walkListeners) { + for (WalkListener walkListener : walkListeners) { WalkFlow walkFlow = walkListener.onWalkStart(walkEvent); if (WalkFlow.SKIP.equals(walkFlow) || WalkFlow.ABORT.equals(walkFlow)) { continueToWalkMethod = false; @@ -39,11 +34,11 @@ protected boolean runPreWalkListeners(List walkListeners return continueToWalkMethod; } - protected void runPostWalkListeners(List walkListeners, WalkEvent walkEvent, - Set validationMessages) { + protected void runPostWalkListeners(List walkListeners, WalkEvent walkEvent, + List errors) { if (walkListeners != null) { - for (JsonSchemaWalkListener walkListener : walkListeners) { - walkListener.onWalkEnd(walkEvent, validationMessages); + for (WalkListener walkListener : walkListeners) { + walkListener.onWalkEnd(walkEvent, errors); } } } diff --git a/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java b/src/main/java/com/networknt/schema/walk/ApplyDefaultsStrategy.java similarity index 91% rename from src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java rename to src/main/java/com/networknt/schema/walk/ApplyDefaultsStrategy.java index 391a160a0..bbd80f0bd 100644 --- a/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java +++ b/src/main/java/com/networknt/schema/walk/ApplyDefaultsStrategy.java @@ -1,7 +1,7 @@ -package com.networknt.schema; +package com.networknt.schema.walk; public class ApplyDefaultsStrategy { - static final ApplyDefaultsStrategy EMPTY_APPLY_DEFAULTS_STRATEGY = new ApplyDefaultsStrategy(false, false, false); + public static final ApplyDefaultsStrategy EMPTY_APPLY_DEFAULTS_STRATEGY = new ApplyDefaultsStrategy(false, false, false); private final boolean applyPropertyDefaults; private final boolean applyPropertyDefaultsIfNull; diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java deleted file mode 100644 index 76b33ef26..000000000 --- a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.networknt.schema.walk; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.ValidationContext; -import com.networknt.schema.ValidationMessage; - -import java.util.List; -import java.util.Set; - -public class DefaultItemWalkListenerRunner extends AbstractWalkListenerRunner { - - private List itemWalkListeners; - - public DefaultItemWalkListenerRunner(List itemWalkListeners) { - this.itemWalkListeners = itemWalkListeners; - } - - @Override - public boolean runPreWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, - String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory) { - WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, validationContext, - currentJsonSchemaFactory); - return runPreWalkListeners(itemWalkListeners, walkEvent); - } - - @Override - public void runPostWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, String schemaPath, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, - Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, - validationContext, currentJsonSchemaFactory); - runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages); - } - -} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java deleted file mode 100644 index 79e7c8896..000000000 --- a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.networknt.schema.walk; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.*; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class DefaultKeywordWalkListenerRunner extends AbstractWalkListenerRunner { - - private Map> keywordWalkListenersMap; - - public DefaultKeywordWalkListenerRunner(Map> keywordWalkListenersMap) { - this.keywordWalkListenersMap = keywordWalkListenersMap; - } - - @Override - public boolean runPreWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, - String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory) { - String keyword = getKeywordName(keyWordPath); - boolean continueRunningListenersAndWalk = true; - WalkEvent keywordWalkEvent = constructWalkEvent(keyword, node, rootNode, at, schemaPath, schemaNode, - parentSchema, validationContext, currentJsonSchemaFactory); - // Run Listeners that are setup only for this keyword. - List currentKeywordListeners = keywordWalkListenersMap.get(keyword); - continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent); - if (continueRunningListenersAndWalk) { - // Run Listeners that are setup for all keywords. - List allKeywordListeners = keywordWalkListenersMap - .get(SchemaValidatorsConfig.ALL_KEYWORD_WALK_LISTENER_KEY); - runPreWalkListeners(allKeywordListeners, keywordWalkEvent); - } - return continueRunningListenersAndWalk; - } - - @Override - public void runPostWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, String schemaPath, - JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory, - Set validationMessages) { - String keyword = getKeywordName(keyWordPath); - WalkEvent keywordWalkEvent = constructWalkEvent(keyword, node, rootNode, at, schemaPath, schemaNode, - parentSchema, validationContext, currentJsonSchemaFactory); - // Run Listeners that are setup only for this keyword. - List currentKeywordListeners = keywordWalkListenersMap.get(keyword); - runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages); - // Run Listeners that are setup for all keywords. - List allKeywordListeners = keywordWalkListenersMap - .get(SchemaValidatorsConfig.ALL_KEYWORD_WALK_LISTENER_KEY); - runPostWalkListeners(allKeywordListeners, keywordWalkEvent, validationMessages); - } - -} diff --git a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java deleted file mode 100644 index 58b78cb3f..000000000 --- a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.networknt.schema.walk; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.ValidationContext; -import com.networknt.schema.ValidationMessage; - -import java.util.List; -import java.util.Set; - -public class DefaultPropertyWalkListenerRunner extends AbstractWalkListenerRunner { - - private List propertyWalkListeners; - - public DefaultPropertyWalkListenerRunner(List propertyWalkListeners) { - this.propertyWalkListeners = propertyWalkListeners; - } - - @Override - public boolean runPreWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, - String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory) { - WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, - validationContext, currentJsonSchemaFactory); - return runPreWalkListeners(propertyWalkListeners, walkEvent); - } - - @Override - public void runPostWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, String schemaPath, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, - Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, - validationContext, currentJsonSchemaFactory); - runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages); - - } - -} diff --git a/src/main/java/com/networknt/schema/walk/ItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/ItemWalkListenerRunner.java new file mode 100644 index 000000000..62522e20d --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/ItemWalkListenerRunner.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.walk; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.Error; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A {@link WalkListenerRunner} for walking items. + */ +public class ItemWalkListenerRunner extends AbstractWalkListenerRunner { + + private final List itemWalkListeners; + + public ItemWalkListenerRunner(List itemWalkListeners) { + this.itemWalkListeners = itemWalkListeners; + } + + @Override + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + schema, validator); + return runPreWalkListeners(itemWalkListeners, walkEvent); + } + + @Override + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator, List errors) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + schema, validator); + runPostWalkListeners(itemWalkListeners, walkEvent, errors); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List itemWalkListeners = new ArrayList<>(); + + public Builder itemWalkListener(WalkListener itemWalkListener) { + this.itemWalkListeners.add(itemWalkListener); + return this; + } + + public Builder itemWalkListeners(Consumer> itemWalkListeners) { + itemWalkListeners.accept(this.itemWalkListeners); + return this; + } + + public ItemWalkListenerRunner build() { + return new ItemWalkListenerRunner(itemWalkListeners); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java b/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java deleted file mode 100644 index 187387926..000000000 --- a/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.networknt.schema.walk; - -import com.networknt.schema.ValidationMessage; - -import java.util.Set; - -/** - * - * Listener class that captures walkStart and walkEnd events. - * - */ -public interface JsonSchemaWalkListener { - - public WalkFlow onWalkStart(WalkEvent walkEvent); - - public void onWalkEnd(WalkEvent walkEvent, Set validationMessages); -} diff --git a/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java b/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java deleted file mode 100644 index bb940b222..000000000 --- a/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.networknt.schema.walk; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.BaseJsonValidator; -import com.networknt.schema.ValidationMessage; - -import java.util.Set; - -public interface JsonSchemaWalker { - /** - * - * This method gives the capability to walk through the given JsonNode, allowing - * functionality beyond validation like collecting information,handling cross - * cutting concerns like logging or instrumentation. This method also performs - * the validation if {@code shouldValidateSchema} is set to true.
- *
- * {@link BaseJsonValidator#walk(JsonNode, JsonNode, String, boolean)} provides - * a default implementation of this method. However validators that parse - * sub-schemas should override this method to call walk method on those - * sub-schemas. - * - * @param node JsonNode - * @param rootNode JsonNode - * @param at String - * @param shouldValidateSchema boolean - * @return a set of validation messages if shouldValidateSchema is true. - */ - Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema); -} diff --git a/src/main/java/com/networknt/schema/walk/KeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/KeywordWalkListenerRunner.java new file mode 100644 index 000000000..9a22e2340 --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/KeywordWalkListenerRunner.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.walk; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; + +/** + * A {@link WalkListenerRunner} for walking keywords. + */ +public class KeywordWalkListenerRunner extends AbstractWalkListenerRunner { + private final List allKeywordWalkListeners; + private final Map> keywordWalkListenersMap; + + public KeywordWalkListenerRunner(List allKeywordWalkListeners, + Map> keywordWalkListenersMap) { + this.allKeywordWalkListeners = allKeywordWalkListeners; + this.keywordWalkListenersMap = keywordWalkListenersMap; + } + + @Override + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator) { + boolean continueRunningListenersAndWalk = true; + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, + instanceLocation, schema, validator); + // Run Listeners that are setup only for this keyword. + List currentKeywordListeners = keywordWalkListenersMap.get(keyword); + continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent); + if (continueRunningListenersAndWalk) { + // Run Listeners that are setup for all keywords. + runPreWalkListeners(allKeywordWalkListeners, keywordWalkEvent); + } + return continueRunningListenersAndWalk; + } + + @Override + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator, + List errors) { + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, + instanceLocation, schema, validator); + // Run Listeners that are setup only for this keyword. + List currentKeywordListeners = keywordWalkListenersMap.get(keyword); + runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, errors); + // Run Listeners that are setup for all keywords. + runPostWalkListeners(allKeywordWalkListeners, keywordWalkEvent, errors); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Map> keywordWalkListeners = new HashMap<>(); + private List allKeywordWalkListeners = new ArrayList<>(); + + public Builder keywordWalkListener(String keyword, WalkListener keywordWalkListener) { + this.keywordWalkListeners.computeIfAbsent(keyword, key -> new ArrayList<>()).add(keywordWalkListener); + return this; + } + + public Builder keywordWalkListener(Keyword keyword, WalkListener keywordWalkListener) { + return keywordWalkListener(keyword.getValue(), keywordWalkListener); + } + + public Builder keywordWalkListener(WalkListener keywordWalkListener) { + allKeywordWalkListeners.add(keywordWalkListener); + return this; + } + + public Builder keywordWalkListeners(Consumer>> keywordWalkListeners) { + keywordWalkListeners.accept(this.keywordWalkListeners); + return this; + } + + public KeywordWalkListenerRunner build() { + return new KeywordWalkListenerRunner(allKeywordWalkListeners, keywordWalkListeners); + } + } +} diff --git a/src/main/java/com/networknt/schema/walk/PropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/PropertyWalkListenerRunner.java new file mode 100644 index 000000000..d10fb10bb --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/PropertyWalkListenerRunner.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.walk; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.Error; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A {@link WalkListenerRunner} for walking properties. + */ +public class PropertyWalkListenerRunner extends AbstractWalkListenerRunner { + + private final List propertyWalkListeners; + + public PropertyWalkListenerRunner(List propertyWalkListeners) { + this.propertyWalkListeners = propertyWalkListeners; + } + + @Override + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + schema, validator); + return runPreWalkListeners(propertyWalkListeners, walkEvent); + } + + @Override + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator, + List errors) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + schema, validator); + runPostWalkListeners(propertyWalkListeners, walkEvent, errors); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List propertyWalkListeners = new ArrayList<>(); + + public Builder propertyWalkListener(WalkListener propertyWalkListener) { + this.propertyWalkListeners.add(propertyWalkListener); + return this; + } + + public Builder propertyWalkListeners(Consumer> propertyWalkListeners) { + propertyWalkListeners.accept(this.propertyWalkListeners); + return this; + } + + public PropertyWalkListenerRunner build() { + return new PropertyWalkListenerRunner(propertyWalkListeners); + } + } +} diff --git a/src/main/java/com/networknt/schema/walk/WalkConfig.java b/src/main/java/com/networknt/schema/walk/WalkConfig.java new file mode 100644 index 000000000..d008abad9 --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/WalkConfig.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.walk; + +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Error; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; + +/** + * Configuration used when walking a schema. + */ +public class WalkConfig { + private static class Holder { + private static final WalkConfig INSTANCE = WalkConfig.builder().build(); + } + + public static WalkConfig getInstance() { + return Holder.INSTANCE; + } + + /** + * {@link WalkListenerRunner} that performs no operations but indicates that it + * should walk. + */ + public static class NoOpWalkListenerRunner implements WalkListenerRunner { + private static class Holder { + private static final NoOpWalkListenerRunner INSTANCE = new NoOpWalkListenerRunner(); + } + + public static NoOpWalkListenerRunner getInstance() { + return Holder.INSTANCE; + } + + @Override + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator) { + // Always walk + return true; + } + + @Override + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator, + List errors) { + } + } + + /** + * The strategy the walker uses to sets nodes that are missing or NullNode to + * the default value, if any, and mutate the input json. + */ + private final ApplyDefaultsStrategy applyDefaultsStrategy; + + private final WalkListenerRunner itemWalkListenerRunner; + + private final WalkListenerRunner keywordWalkListenerRunner; + + private final WalkListenerRunner propertyWalkListenerRunner; + + WalkConfig(ApplyDefaultsStrategy applyDefaultsStrategy, WalkListenerRunner itemWalkListenerRunner, + WalkListenerRunner keywordWalkListenerRunner, WalkListenerRunner propertyWalkListenerRunner) { + super(); + this.applyDefaultsStrategy = applyDefaultsStrategy; + this.itemWalkListenerRunner = itemWalkListenerRunner; + this.keywordWalkListenerRunner = keywordWalkListenerRunner; + this.propertyWalkListenerRunner = propertyWalkListenerRunner; + } + + /** + * Gets the strategy for applying defaults. + * + * @return the strategy for applying defaults + */ + public ApplyDefaultsStrategy getApplyDefaultsStrategy() { + return this.applyDefaultsStrategy; + } + + /** + * Gets the property walk listener runner. + * + * @return the property walk listener runner + */ + + public WalkListenerRunner getPropertyWalkListenerRunner() { + return this.propertyWalkListenerRunner; + } + + /** + * Gets the item walk listener runner. + * + * @return the item walk listener runner + */ + public WalkListenerRunner getItemWalkListenerRunner() { + return this.itemWalkListenerRunner; + } + + /** + * Gets the keyword walk listener runner. + * + * @return the keyword walk listener runner + */ + public WalkListenerRunner getKeywordWalkListenerRunner() { + return this.keywordWalkListenerRunner; + } + + /** + * Creates a new builder for {@link WalkConfig}. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new builder for {@link WalkConfig} and copies the configuration. + * + * @param config the configuration to copy + * @return the builder + */ + public static Builder builder(WalkConfig config) { + Builder builder = new Builder(); + builder.applyDefaultsStrategy = config.applyDefaultsStrategy; + builder.itemWalkListenerRunner = config.itemWalkListenerRunner; + builder.keywordWalkListenerRunner = config.keywordWalkListenerRunner; + builder.propertyWalkListenerRunner = config.propertyWalkListenerRunner; + return builder; + } + + /** + * Builder for {@link WalkConfig}. + */ + public static class Builder { + private ApplyDefaultsStrategy applyDefaultsStrategy = null; + private WalkListenerRunner itemWalkListenerRunner = null; + private WalkListenerRunner keywordWalkListenerRunner = null; + private WalkListenerRunner propertyWalkListenerRunner = null; + + /** + * Sets the strategy the walker uses to sets nodes to the default value. + *

+ * Defaults to {@link ApplyDefaultsStrategy#EMPTY_APPLY_DEFAULTS_STRATEGY}. + * + * @param applyDefaultsStrategy the strategy + * @return the builder + */ + public Builder applyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) { + this.applyDefaultsStrategy = applyDefaultsStrategy; + return this; + } + + public Builder itemWalkListenerRunner(WalkListenerRunner itemWalkListenerRunner) { + this.itemWalkListenerRunner = itemWalkListenerRunner; + return this; + } + + public Builder keywordWalkListenerRunner(WalkListenerRunner keywordWalkListenerRunner) { + this.keywordWalkListenerRunner = keywordWalkListenerRunner; + return this; + } + + public Builder propertyWalkListenerRunner(WalkListenerRunner propertyWalkListenerRunner) { + this.propertyWalkListenerRunner = propertyWalkListenerRunner; + return this; + } + + public WalkConfig build() { + return new WalkConfig( + applyDefaultsStrategy != null ? applyDefaultsStrategy + : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY, + itemWalkListenerRunner != null ? itemWalkListenerRunner : NoOpWalkListenerRunner.getInstance(), + keywordWalkListenerRunner != null ? keywordWalkListenerRunner + : NoOpWalkListenerRunner.getInstance(), + propertyWalkListenerRunner != null ? propertyWalkListenerRunner + : NoOpWalkListenerRunner.getInstance()); + } + } +} diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index 6543e1591..cdce63b28 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -1,102 +1,132 @@ package com.networknt.schema.walk; import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SchemaValidatorsConfig; -import com.networknt.schema.ValidationContext; - -import java.net.URI; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; /** - * Encapsulation of Walk data that is passed into the {@link JsonSchemaWalkListener}. + * Encapsulation of Walk data that is passed into the {@link WalkListener}. */ public class WalkEvent { - private String schemaPath; - private JsonNode schemaNode; - private JsonSchema parentSchema; - private String keyWordName; - private JsonNode node; + private ExecutionContext executionContext; + private Schema schema; + private String keyword; private JsonNode rootNode; - private String at; - private JsonSchemaFactory currentJsonSchemaFactory; - private ValidationContext validationContext; - - public String getSchemaPath() { - return schemaPath; - } - - public JsonNode getSchemaNode() { - return schemaNode; + private JsonNode instanceNode; + private NodePath instanceLocation; + private KeywordValidator validator; + private NodePath evaluationPath; + + public NodePath getEvaluationPath() { + return this.evaluationPath; } - public JsonSchema getParentSchema() { - return parentSchema; + /** + * Gets the execution context. + *

+ * As the listeners should be state-less, this allows listeners to store data in + * the collector context. + * + * @return the execution context + */ + public ExecutionContext getExecutionContext() { + return executionContext; } - public String getKeyWordName() { - return keyWordName; + /** + * Gets the schema that will be used to evaluate the instance node. + *

+ * For the keyword listener, this will allow getting the validator for the given keyword. + * + * @return the schema + */ + public Schema getSchema() { + return schema; } - public JsonNode getNode() { - return node; + /** + * Gets the keyword. + * + * @return the keyword + */ + public String getKeyword() { + return keyword; } + /** + * Gets the root instance node. + *

+ * This makes it possible to get the parent node, for instance by getting the + * instance location parent and using the root node. + * + * @return the root node + */ public JsonNode getRootNode() { return rootNode; } - public String getAt() { - return at; + /** + * Gets the instance node. + * + * @return the instance node + */ + public JsonNode getInstanceNode() { + return instanceNode; } - public JsonSchema getRefSchema(URI schemaUri) { - return currentJsonSchemaFactory.getSchema(schemaUri, validationContext.getConfig()); + /** + * Gets the instance location of the instance node. + * + * @return the instance location of the instance node + */ + public NodePath getInstanceLocation() { + return instanceLocation; } - public JsonSchema getRefSchema(URI schemaUri, SchemaValidatorsConfig schemaValidatorsConfig) { - if (schemaValidatorsConfig != null) { - return currentJsonSchemaFactory.getSchema(schemaUri, schemaValidatorsConfig); - } else { - return getRefSchema(schemaUri); - } + /** + * Gets the validator that corresponds with the keyword. + * @param the type of the validator + * @return the validator + */ + @SuppressWarnings("unchecked") + public T getValidator() { + return (T) this.validator; } - public JsonSchemaFactory getCurrentJsonSchemaFactory() { - return currentJsonSchemaFactory; + @Override + public String toString() { + return "WalkEvent [schemaLocation=" + + getSchema().getSchemaLocation() + ", instanceLocation=" + instanceLocation + "]"; } static class WalkEventBuilder { - private WalkEvent walkEvent; + private final WalkEvent walkEvent; WalkEventBuilder() { walkEvent = new WalkEvent(); } - public WalkEventBuilder schemaPath(String schemaPath) { - walkEvent.schemaPath = schemaPath; - return this; - } - - public WalkEventBuilder schemaNode(JsonNode schemaNode) { - walkEvent.schemaNode = schemaNode; + public WalkEventBuilder executionContext(ExecutionContext executionContext) { + walkEvent.executionContext = executionContext; return this; } - public WalkEventBuilder parentSchema(JsonSchema parentSchema) { - walkEvent.parentSchema = parentSchema; + public WalkEventBuilder schema(Schema schema) { + walkEvent.schema = schema; return this; } - public WalkEventBuilder keyWordName(String keyWordName) { - walkEvent.keyWordName = keyWordName; + public WalkEventBuilder keyword(String keyword) { + walkEvent.keyword = keyword; return this; } - public WalkEventBuilder node(JsonNode node) { - walkEvent.node = node; + public WalkEventBuilder instanceNode(JsonNode node) { + walkEvent.instanceNode = node; return this; } @@ -105,22 +135,20 @@ public WalkEventBuilder rootNode(JsonNode rootNode) { return this; } - public WalkEventBuilder at(String at) { - walkEvent.at = at; - return this; - } - - public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSchemaFactory) { - walkEvent.currentJsonSchemaFactory = currentJsonSchemaFactory; + public WalkEventBuilder instanceLocation(NodePath instanceLocation) { + walkEvent.instanceLocation = instanceLocation; return this; } - public WalkEventBuilder validationContext(ValidationContext validationContext) { - walkEvent.validationContext = validationContext; + public WalkEventBuilder validator(KeywordValidator validator) { + walkEvent.validator = validator; return this; } public WalkEvent build() { + if (walkEvent.executionContext != null) { + walkEvent.evaluationPath = walkEvent.executionContext.getEvaluationPath(); + } return walkEvent; } @@ -129,5 +157,4 @@ public WalkEvent build() { public static WalkEventBuilder builder() { return new WalkEventBuilder(); } - } diff --git a/src/main/java/com/networknt/schema/walk/WalkFlow.java b/src/main/java/com/networknt/schema/walk/WalkFlow.java index af2da7b5c..e4bc43fd6 100644 --- a/src/main/java/com/networknt/schema/walk/WalkFlow.java +++ b/src/main/java/com/networknt/schema/walk/WalkFlow.java @@ -8,9 +8,9 @@ public enum WalkFlow { CONTINUE("ContinueToWalk", "continue to invoke the walk method and other listeners"); - private String name; + private final String name; - private String description; + private final String description; WalkFlow(String name, String description) { this.name = name; diff --git a/src/main/java/com/networknt/schema/walk/WalkListener.java b/src/main/java/com/networknt/schema/walk/WalkListener.java new file mode 100644 index 000000000..e1ac73dc5 --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/WalkListener.java @@ -0,0 +1,17 @@ +package com.networknt.schema.walk; + +import com.networknt.schema.Error; + +import java.util.List; + +/** + * + * Listener class that captures walkStart and walkEnd events. + * + */ +public interface WalkListener { + + WalkFlow onWalkStart(WalkEvent walkEvent); + + void onWalkEnd(WalkEvent walkEvent, List errors); +} diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java index 5eb331a61..66f2a6ad3 100644 --- a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java @@ -1,20 +1,20 @@ package com.networknt.schema.walk; import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.ValidationContext; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.Error; -import java.util.Set; +import java.util.List; public interface WalkListenerRunner { - public boolean runPreWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, - String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory jsonSchemaFactory); + boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator); - public void runPostWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, String schemaPath, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory jsonSchemaFactory, - Set validationMessages); + void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, + JsonNode rootNode, NodePath instanceLocation, Schema schema, KeywordValidator validator, List errors); } diff --git a/src/main/java/com/networknt/schema/walk/Walker.java b/src/main/java/com/networknt/schema/walk/Walker.java new file mode 100644 index 000000000..bb250d426 --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/Walker.java @@ -0,0 +1,29 @@ +package com.networknt.schema.walk; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.Validator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.ExecutionContext; + +public interface Walker { + /** + * This method gives the capability to walk through the given JsonNode, allowing + * functionality beyond validation like collecting information,handling + * cross-cutting concerns like logging or instrumentation. This method also + * performs the validation if {@code shouldValidateSchema} is set to true.
+ *
+ * {@link Validator#walk(ExecutionContext, JsonNode, JsonNode, NodePath, boolean)} + * provides a default implementation of this method. However, validators that + * parse sub-schemas should override this method to call walk method on those + * sub-schemas. + * + * @param executionContext the execution context + * @param instanceNode the instance node being processed + * @param instance the instance document that the instance node + * belongs to + * @param instanceLocation the location of the instance node being processed + * @param shouldValidateSchema true to validate the schema while walking + */ + void walk(ExecutionContext executionContext, JsonNode instanceNode, JsonNode instance, + NodePath instanceLocation, boolean shouldValidateSchema); +} diff --git a/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/native-image.properties b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/native-image.properties new file mode 100644 index 000000000..1d69fb5ea --- /dev/null +++ b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/native-image.properties @@ -0,0 +1,2 @@ +Args = -H:ReflectionConfigurationResources=${.}/reflect-config.json \ + -H:ResourceConfigurationResources=${.}/resource-config.json diff --git a/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/reflect-config.json b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/reflect-config.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/reflect-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/resource-config.json b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/resource-config.json new file mode 100644 index 000000000..9e167b124 --- /dev/null +++ b/src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/resource-config.json @@ -0,0 +1,24 @@ +{ + "resources": { + "includes": [ + { + "pattern": "draft/.*" + }, + { + "pattern": "draft-04/.*" + }, + { + "pattern": "draft-06/.*" + }, + { + "pattern": "draft-07/.*" + }, + { + "pattern": "ucd/.*" + }, + { + "pattern": "jsv-messages.*properties" + } + ] + } +} diff --git a/src/main/resources/draft-04/schema b/src/main/resources/draft-04/schema new file mode 100644 index 000000000..bcbb84743 --- /dev/null +++ b/src/main/resources/draft-04/schema @@ -0,0 +1,149 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "$schema": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/src/main/resources/draft-06/schema b/src/main/resources/draft-06/schema new file mode 100644 index 000000000..bd3e763bc --- /dev/null +++ b/src/main/resources/draft-06/schema @@ -0,0 +1,155 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "examples": { + "type": "array", + "items": {} + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} diff --git a/src/main/resources/draft-07/schema b/src/main/resources/draft-07/schema new file mode 100644 index 000000000..fb92c7f75 --- /dev/null +++ b/src/main/resources/draft-07/schema @@ -0,0 +1,172 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": { "$ref": "#" }, + "then": { "$ref": "#" }, + "else": { "$ref": "#" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true +} diff --git a/src/main/resources/draft/2019-09/meta/applicator b/src/main/resources/draft/2019-09/meta/applicator new file mode 100644 index 000000000..24a1cc4f4 --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/applicator @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/applicator": true + }, + "$recursiveAnchor": true, + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "additionalItems": { "$recursiveRef": "#" }, + "unevaluatedItems": { "$recursiveRef": "#" }, + "items": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "#/$defs/schemaArray" } + ] + }, + "contains": { "$recursiveRef": "#" }, + "additionalProperties": { "$recursiveRef": "#" }, + "unevaluatedProperties": { "$recursiveRef": "#" }, + "properties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { + "$recursiveRef": "#" + } + }, + "propertyNames": { "$recursiveRef": "#" }, + "if": { "$recursiveRef": "#" }, + "then": { "$recursiveRef": "#" }, + "else": { "$recursiveRef": "#" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$recursiveRef": "#" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$recursiveRef": "#" } + } + } +} diff --git a/src/main/resources/draft/2019-09/meta/content b/src/main/resources/draft/2019-09/meta/content new file mode 100644 index 000000000..f6752a8ef --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/content @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "contentSchema": { "$recursiveRef": "#" } + } +} diff --git a/src/main/resources/draft/2019-09/meta/core b/src/main/resources/draft/2019-09/meta/core new file mode 100644 index 000000000..eb708a560 --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/core @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true + }, + "$recursiveAnchor": true, + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$anchor": { + "type": "string", + "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveRef": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveAnchor": { + "type": "boolean", + "default": false + }, + "$vocabulary": { + "type": "object", + "propertyNames": { + "type": "string", + "format": "uri" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + } + } +} diff --git a/src/main/resources/draft/2019-09/meta/format b/src/main/resources/draft/2019-09/meta/format new file mode 100644 index 000000000..09bbfdda9 --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/format @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/format", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/format": true + }, + "$recursiveAnchor": true, + + "title": "Format vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/src/main/resources/draft/2019-09/meta/meta-data b/src/main/resources/draft/2019-09/meta/meta-data new file mode 100644 index 000000000..da04cff6d --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/meta-data @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/meta-data": true + }, + "$recursiveAnchor": true, + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +} diff --git a/src/main/resources/draft/2019-09/meta/validation b/src/main/resources/draft/2019-09/meta/validation new file mode 100644 index 000000000..9f59677b3 --- /dev/null +++ b/src/main/resources/draft/2019-09/meta/validation @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/validation": true + }, + "$recursiveAnchor": true, + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/src/main/resources/draft/2019-09/schema b/src/main/resources/draft/2019-09/schema new file mode 100644 index 000000000..2248a0c80 --- /dev/null +++ b/src/main/resources/draft/2019-09/schema @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true, + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/meta-data": true, + "https://json-schema.org/draft/2019-09/vocab/format": false, + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "properties": { + "definitions": { + "$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.", + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + } + } + } +} diff --git a/src/main/resources/draft/2020-12/meta/applicator b/src/main/resources/draft/2020-12/meta/applicator new file mode 100644 index 000000000..ca6992309 --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/applicator @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/applicator": true + }, + "$dynamicAnchor": "meta", + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "prefixItems": { "$ref": "#/$defs/schemaArray" }, + "items": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "properties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$dynamicRef": "#meta" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$dynamicRef": "#meta" } + } + } +} diff --git a/src/main/resources/draft/2020-12/meta/content b/src/main/resources/draft/2020-12/meta/content new file mode 100644 index 000000000..2f6e056a9 --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/content @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$dynamicRef": "#meta" } + } +} diff --git a/src/main/resources/draft/2020-12/meta/core b/src/main/resources/draft/2020-12/meta/core new file mode 100644 index 000000000..dfc092d96 --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/core @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "$dynamicAnchor": "meta", + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "$ref": "#/$defs/uriReferenceString", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { "$ref": "#/$defs/uriString" }, + "$ref": { "$ref": "#/$defs/uriReferenceString" }, + "$anchor": { "$ref": "#/$defs/anchorString" }, + "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, + "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, + "$vocabulary": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/uriString" }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" } + } + }, + "$defs": { + "anchorString": { + "type": "string", + "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" + }, + "uriString": { + "type": "string", + "format": "uri" + }, + "uriReferenceString": { + "type": "string", + "format": "uri-reference" + } + } +} diff --git a/src/main/resources/draft/2020-12/meta/format-annotation b/src/main/resources/draft/2020-12/meta/format-annotation new file mode 100644 index 000000000..51ef7ea11 --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/format-annotation @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for annotation results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/src/main/resources/draft/2020-12/meta/format-assertion b/src/main/resources/draft/2020-12/meta/format-assertion new file mode 100644 index 000000000..1a4f106cf --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/format-assertion @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion", + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for assertion results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/src/main/resources/draft/2020-12/meta/meta-data b/src/main/resources/draft/2020-12/meta/meta-data new file mode 100644 index 000000000..05cbc22af --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/meta-data @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/meta-data": true + }, + "$dynamicAnchor": "meta", + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +} diff --git a/src/main/resources/draft/2020-12/meta/unevaluated b/src/main/resources/draft/2020-12/meta/unevaluated new file mode 100644 index 000000000..5f62a3ffa --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/unevaluated @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true + }, + "$dynamicAnchor": "meta", + + "title": "Unevaluated applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" } + } +} diff --git a/src/main/resources/draft/2020-12/meta/validation b/src/main/resources/draft/2020-12/meta/validation new file mode 100644 index 000000000..606b87ba2 --- /dev/null +++ b/src/main/resources/draft/2020-12/meta/validation @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/validation": true + }, + "$dynamicAnchor": "meta", + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/src/main/resources/draft/2020-12/schema b/src/main/resources/draft/2020-12/schema new file mode 100644 index 000000000..d5e2d31c3 --- /dev/null +++ b/src/main/resources/draft/2020-12/schema @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/unevaluated"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format-annotation"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } +} diff --git a/src/main/resources/draftv4.schema.json b/src/main/resources/draftv4.schema.json deleted file mode 100644 index 6d926f6fc..000000000 --- a/src/main/resources/draftv4.schema.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "id": "http://json-schema.org/draft-04/schema#", - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#" - } - }, - "positiveInteger": { - "type": "integer", - "minimum": 0 - }, - "positiveIntegerDefault0": { - "allOf": [ - { - "$ref": "#/definitions/positiveInteger" - }, - { - "default": 0 - } - ] - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uri" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": {}, - "multipleOf": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "boolean", - "default": false - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "boolean", - "default": false - }, - "maxLength": { - "$ref": "#/definitions/positiveInteger" - }, - "minLength": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#" - } - ], - "default": {} - }, - "items": { - "anyOf": [ - { - "$ref": "#" - }, - { - "$ref": "#/definitions/schemaArray" - } - ], - "default": {} - }, - "maxItems": { - "$ref": "#/definitions/positiveInteger" - }, - "minItems": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "maxProperties": { - "$ref": "#/definitions/positiveInteger" - }, - "minProperties": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "required": { - "$ref": "#/definitions/stringArray" - }, - "additionalProperties": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#" - } - ], - "default": {} - }, - "definitions": { - "type": "object", - "additionalProperties": { - "$ref": "#" - }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#" - }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#" - }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "$ref": "#" - }, - { - "$ref": "#/definitions/stringArray" - } - ] - } - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { - "$ref": "#/definitions/simpleTypes" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/simpleTypes" - }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "allOf": { - "$ref": "#/definitions/schemaArray" - }, - "anyOf": { - "$ref": "#/definitions/schemaArray" - }, - "oneOf": { - "$ref": "#/definitions/schemaArray" - }, - "not": { - "$ref": "#" - } - }, - "dependencies": { - "exclusiveMaximum": [ - "maximum" - ], - "exclusiveMinimum": [ - "minimum" - ] - }, - "default": {} -} diff --git a/src/main/resources/jsv-messages.properties b/src/main/resources/jsv-messages.properties index 69262d22d..773da5783 100644 --- a/src/main/resources/jsv-messages.properties +++ b/src/main/resources/jsv-messages.properties @@ -1,40 +1,73 @@ -additionalProperties = {0}.{1}: is not defined in the schema and the schema does not allow additional properties -allOf = {0}: should be valid to all the schemas {1} -anyOf = {0}: should be valid to any of the schemas {1} -crossEdits = {0}: has an error with 'cross edits' -dependencies = {0}: has an error with dependencies {1} -dependentRequired = {0}: has a missing property which is dependent required {1} -dependentSchemas = {0}: has an error with dependentSchemas {1} -edits = {0}: has an error with 'edits' -enum = {0}: does not have a value in the enumeration {1} -format = {0}: does not match the {1} pattern {2} -items = {0}[{1}]: no validator found at this index -maximum = {0}: must have a maximum value of {1} -maxItems = {0}: there must be a maximum of {1} items in the array -maxLength = {0}: may only be {1} characters long -maxProperties = {0}: may only have a maximum of {1} properties -minimum = {0}: must have a minimum value of {1} -minItems = {0}: there must be a minimum of {1} items in the array -minLength = {0}: must be at least {1} characters long -minProperties = {0}: should have a minimum of {1} properties -multipleOf = {0}: must be multiple of {1} -notAllowed = {0}.{1}: is not allowed but it is in the data -not = {0}: should not be valid to the schema {1} -oneOf = {0}: should be valid to one and only one of the schemas {1} -patternProperties = {0}: has some error with 'pattern properties' -pattern = {0}: does not match the regex pattern {1} -properties = {0}: has an error with 'properties' -readOnly = {0}: is a readonly field, it cannot be changed -$ref = {0}: has an error with 'refs' -required = {0}.{1}: is missing but it is required -type = {0}: {1} found, {2} expected -unionType = {0}: {1} found, but {2} is required -uniqueItems = {0}: the items in the array must be unique -uuid = {0}: {1} is an invalid {2} -id = {0}: {1} is an invalid segment for URI {2} -exclusiveMaximum = {0}: must have an exclusive maximum value of {1} -exclusiveMinimum = {0}: must have an exclusive minimum value of {1} -false = Boolean schema false is not valid -const = {0}: must be a constant value {1} -contains = {0}: does not contain an element that passes these validations: {1} -propertyNames = Property name {0} is not valid for validation: {1} +$ref = has an error with ''refs'' +additionalItems = index ''{0}'' is not defined in the schema and the schema does not allow additional items +additionalProperties = property ''{0}'' is not defined in the schema and the schema does not allow additional properties +allOf = must be valid to all the schemas {0} +anyOf = must be valid to any of the schemas {0} +const = must be the constant value ''{0}'' +contains = does not contain an element that passes these validations: {1} +contains.max = must contain at most {0} element(s) that passes these validations: {1} +contains.min = must contain at least {0} element(s) that passes these validations: {1} +dependencies = has an error with dependencies {0} +dependentRequired = has a missing property ''{0}'' which is dependent required because ''{1}'' is present +dependentSchemas = has an error with dependentSchemas {0} +enum = does not have a value in the enumeration {0} +exclusiveMaximum = must have an exclusive maximum value of {0} +exclusiveMinimum = must have an exclusive minimum value of {0} +false = schema for ''{0}'' is false +format = does not match the {0} pattern +format.date = does not match the {0} pattern must be a valid RFC 3339 full-date +format.date-time = does not match the {0} pattern must be a valid RFC 3339 date-time +format.duration = does not match the {0} pattern must be a valid ISO 8601 duration +format.email = does not match the {0} pattern must be a valid RFC 5321 Mailbox +format.ipv4 = does not match the {0} pattern must be a valid RFC 2673 IP address +format.ipv6 = does not match the {0} pattern must be a valid RFC 4291 IP address +format.idn-email = does not match the {0} pattern must be a valid RFC 6531 Mailbox +format.idn-hostname = does not match the {0} pattern must be a valid RFC 5890 internationalized hostname +format.iri = does not match the {0} pattern must be a valid RFC 3987 IRI +format.iri-reference = does not match the {0} pattern must be a valid RFC 3987 IRI-reference +format.uri = does not match the {0} pattern must be a valid RFC 3986 URI +format.uri-reference = does not match the {0} pattern must be a valid RFC 3986 URI-reference +format.uri-template = does not match the {0} pattern must be a valid RFC 6570 URI Template +format.uuid = does not match the {0} pattern must be a valid RFC 4122 UUID +format.regex = does not match the {0} pattern must be a valid ECMA-262 regular expression +format.time = does not match the {0} pattern must be a valid RFC 3339 time +format.hostname = does not match the {0} pattern must be a valid RFC 1123 host name +format.json-pointer = does not match the {0} pattern must be a valid RFC 6901 JSON Pointer +format.relative-json-pointer = does not match the {0} pattern must be a valid IETF Relative JSON Pointer +format.unknown = has an unknown format ''{0}'' +id = ''{0}'' is not a valid {1} +items = index ''{0}'' is not defined in the schema and the schema does not allow additional items +maxContains = must be a non-negative integer in {0} +maxItems = must have at most {0} items but found {1} +maxLength = must be at most {0} characters long +maxProperties = must have at most {0} properties +maximum = must have a maximum value of {0} +minContains = must be a non-negative integer in {0} +minContainsVsMaxContains = minContains must less than or equal to maxContains in {0} +minItems = must have at least {0} items but found {1} +minLength = must be at least {0} characters long +minProperties = must have at least {0} properties +minimum = must have a minimum value of {0} +multipleOf = must be multiple of {0} +not = must not be valid to the schema {0} +notAllowed = property ''{0}'' is not allowed but it is in the data +oneOf = must be valid to one and only one schema, but {0} are valid +oneOf.indexes = must be valid to one and only one schema, but {0} are valid with indexes ''{1}'' +pattern = does not match the regex pattern {0} +patternProperties = has some error with ''pattern properties'' +prefixItems = no validator found at this index +properties = has an error with ''properties'' +propertyNames = property ''{0}'' name is not valid: {1} +readOnly = is a readonly field, it cannot be changed +required = required property ''{0}'' not found +type = {0} found, {1} expected +unevaluatedItems = index ''{0}'' is not evaluated and the schema does not allow unevaluated items +unevaluatedProperties = property ''{0}'' is not evaluated and the schema does not allow unevaluated properties +unionType = {0} found, {1} expected +uniqueItems = must have only unique items in the array +writeOnly = is a write-only field, it cannot appear in the data +contentEncoding = does not match content encoding {0} +contentMediaType = is not a content media type +discriminator.missing_discriminating_value = required property ''{0}'' for discriminator not found +discriminator.anyOf.no_match_found = no matching schema found for discriminator value ''{0}'' +discriminator.oneOf.no_match_found = no matching schema found for discriminator value ''{0}'' \ No newline at end of file diff --git a/src/main/resources/jsv-messages_ar.properties b/src/main/resources/jsv-messages_ar.properties new file mode 100644 index 000000000..6016c7319 --- /dev/null +++ b/src/main/resources/jsv-messages_ar.properties @@ -0,0 +1,70 @@ +$ref = \u0628\u0647 \u062E\u0637\u0623 \u0641\u064A ''refs'' +additionalItems = \u0644\u0645 \u064A\u062A\u0645 \u062A\u0639\u0631\u064A\u0641 \u0627\u0644\u0641\u0647\u0631\u0633 ''{0}'' \u0641\u064A \u0627\u0644\u0645\u062E\u0637\u0637 \u0648\u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0644\u0645\u062E\u0637\u0637 \u0628\u0639\u0646\u0627\u0635\u0631 \u0625\u0636\u0627\u0641\u064A\u0629 +additionalProperties = \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{0}'' \u063A\u064A\u0631 \u0645\u062D\u062F\u062F\u0629 \u0641\u064A \u0627\u0644\u0645\u062E\u0637\u0637 \u0648\u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0644\u0645\u062E\u0637\u0637 \u0628\u062E\u0635\u0627\u0626\u0635 \u0625\u0636\u0627\u0641\u064A\u0629 +allOf = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u062C\u0645\u064A\u0639 \u0627\u0644\u0645\u062E\u0637\u0637\u0627\u062A {0} +anyOf = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0623\u064A \u0645\u0646 \u0627\u0644\u0645\u062E\u0637\u0637\u0627\u062A {0} +const = \u064A\u062C\u0628 \u0623\u0646 \u062A\u0643\u0648\u0646 \u0627\u0644\u0642\u064A\u0645\u0629 \u0627\u0644\u062B\u0627\u0628\u062A\u0629 ''{0}'' +contains = \u0644\u0627 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0639\u0646\u0635\u0631 \u064A\u0642\u0648\u0645 \u0628\u062A\u0645\u0631\u064A\u0631 \u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u062A\u062D\u0642\u0642 \u0647\u0630\u0647: {1} +contains.max = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0639\u0646\u0635\u0631 (\u0639\u0646\u0627\u0635\u0631) \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631 \u064A\u062C\u062A\u0627\u0632 \u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u062A\u062D\u0642\u0642 \u0647\u0630\u0647: {1} +contains.min = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0639\u0646\u0635\u0631 (\u0639\u0646\u0627\u0635\u0631) \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644 \u064A\u0642\u0648\u0645 \u0628\u062A\u0645\u0631\u064A\u0631 \u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u062A\u062D\u0642\u0642 \u0647\u0630\u0647: {1} +dependencies = \u064A\u0648\u062C\u062F \u062E\u0637\u0623 \u0641\u064A \u0627\u0644\u062A\u0628\u0639\u064A\u0627\u062A {0} +dependentRequired = \u0628\u0647 \u062E\u0627\u0635\u064A\u0629 \u0645\u0641\u0642\u0648\u062F\u0629 ''{0}'' \u0648\u0647\u064A \u062A\u0627\u0628\u0639\u0629 \u0645\u0637\u0644\u0648\u0628\u0629 \u0644\u0623\u0646 ''{1}'' \u0645\u0648\u062C\u0648\u062F\u0629 +dependentSchemas = \u0628\u0647 \u062E\u0637\u0623 \u0641\u064AdependentSchemas {0} +enum = \u0644\u0627 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0642\u064A\u0645\u0629 \u0641\u064A \u0627\u0644\u062A\u0639\u062F\u0627\u062F {0} +exclusiveMaximum = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0644\u0647 \u0642\u064A\u0645\u0629 \u0642\u0635\u0648\u0649 \u062D\u0635\u0631\u064A\u0629 \u062A\u0628\u0644\u063A {0} +exclusiveMinimum = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0642\u064A\u0645\u0629 \u062F\u0646\u064A\u0627 \u062D\u0635\u0631\u064A\u0629 \u062A\u0628\u0644\u063A {0} +false = \u0645\u062E\u0637\u0637 ''{0}'' \u063A\u064A\u0631 \u0635\u062D\u064A\u062D +format = \u0644\u0627 \u064A\u0637\u0627\u0628\u0642 \u0627\u0644\u0646\u0645\u0637 {0} +format.date = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u062A\u0627\u0631\u064A\u062E\u064B\u0627 \u0643\u0627\u0645\u0644\u0627\u064B \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 3339 +format.date-time = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u062A\u0627\u0631\u064A\u062E\u064B\u0627 \u0648\u0648\u0642\u062A\u064B\u0627 \u0635\u0627\u0644\u062D\u064B\u0627 \u0641\u064A RFC 3339 +format.duration = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u062A\u0643\u0648\u0646 \u0645\u062F\u0629 ISO 8601 \u0635\u0627\u0644\u062D\u0629 +format.email = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0646\u062F\u0648\u0642 \u0628\u0631\u064A\u062F RFC 5321 \u0635\u0627\u0644\u062D\u064B\u0627 +format.ipv4 = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0639\u0646\u0648\u0627\u0646 IP \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 2673 +format.ipv6 = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0639\u0646\u0648\u0627\u0646 IP \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 4291 +format.idn-email = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0646\u062F\u0648\u0642 \u0628\u0631\u064A\u062F RFC 6531 \u0635\u0627\u0644\u062D\u064B\u0627 +format.idn-hostname = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0633\u0645 \u0645\u0636\u064A\u0641 \u062F\u0648\u0644\u064A\u064B\u0627 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 5890 +format.iri = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 RFC 3987 IRI \u0635\u0627\u0644\u062D\u064B\u0627 +format.iri-reference = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0631\u062C\u0639 RFC 3987 IRI \u0635\u0627\u0644\u062D\u064B\u0627 +format.uri = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0639\u0646\u0648\u0627\u0646 URI \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 3986 +format.uri-reference = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0631\u062C\u0639 URI \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 RFC 3986 +format.uri-template = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0642\u0627\u0644\u0628 URI RFC 6570 \u0635\u0627\u0644\u062D\u064B\u0627 +format.uuid = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 RFC 4122 UUID \u0635\u0627\u0644\u062D\u064B\u0627 +format.regex = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u062A\u0639\u0628\u064A\u0631\u064B\u0627 \u0639\u0627\u062F\u064A\u064B\u0627 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0640 ECMA-262 +format.time = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0648\u0642\u062A RFC 3339 \u0635\u0627\u0644\u062D\u064B\u0627 +format.hostname = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0633\u0645 \u0645\u0636\u064A\u0641 RFC 1123 \u0635\u0627\u0644\u062D\u064B\u0627 +format.json-pointer = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0624\u0634\u0631 RFC 6901 JSON \u0635\u0627\u0644\u062D\u064B\u0627 +format.relative-json-pointer = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {0} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0624\u0634\u0631 IETF Relative JSON \u0635\u0627\u0644\u062D\u064B\u0627 +format.unknown = \u0644\u0647 \u062A\u0646\u0633\u064A\u0642 \u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641 ''{0}'' +id = ''{0}'' \u0644\u064A\u0633 {1} \u0635\u0627\u0644\u062D\u064B\u0627 +items = \u0644\u0645 \u064A\u062A\u0645 \u062A\u0639\u0631\u064A\u0641 \u0627\u0644\u0641\u0647\u0631\u0633 ''{0}'' \u0641\u064A \u0627\u0644\u0645\u062E\u0637\u0637 \u0648\u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0644\u0645\u062E\u0637\u0637 \u0628\u0639\u0646\u0627\u0635\u0631 \u0625\u0636\u0627\u0641\u064A\u0629 +maxContains = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0639\u062F\u062F\u064B\u0627 \u0635\u062D\u064A\u062D\u064B\u0627 \u063A\u064A\u0631 \u0633\u0627\u0644\u0628 \u0641\u064A {0} +maxItems = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0639\u0646\u0635\u0631 \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631 \u0648\u0644\u0643\u0646 \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {1} +maxLength = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0637\u0648\u0644\u0647 {0} \u062D\u0631\u0641\u064B\u0627 \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631 +maxProperties = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0645\u0646 \u0627\u0644\u062E\u0635\u0627\u0626\u0635 \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631 +maximum = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 \u0644\u0642\u064A\u0645\u0629 {0} +minContains = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0639\u062F\u062F\u064B\u0627 \u0635\u062D\u064A\u062D\u064B\u0627 \u063A\u064A\u0631 \u0633\u0627\u0644\u0628 \u0641\u064A {0} +minContainsVsMaxContains = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 minContains \u0623\u0642\u0644 \u0645\u0646 \u0623\u0648 \u064A\u0633\u0627\u0648\u064A maxContains \u0641\u064A {0} +minItems = \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0639\u0646\u0635\u0631 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644 \u0648\u0644\u0643\u0646 \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {1} +minLength = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0637\u0648\u0644\u0647 {0} \u062D\u0631\u0641\u064B\u0627 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644 +minProperties = \u064A\u062C\u0628 \u0623\u0646 \u062A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {0} \u0645\u0646 \u0627\u0644\u062E\u0635\u0627\u0626\u0635 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644 +minimum = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u062F\u0646\u0649 \u0644\u0642\u064A\u0645\u0629 {0} +multipleOf = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0646 \u0645\u0636\u0627\u0639\u0641\u0627\u062A {0} +not = \u064A\u062C\u0628 \u0623\u0646 \u0644\u0627 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0644\u0645\u062E\u0637\u0637 {0} +notAllowed = \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{0}'' \u063A\u064A\u0631 \u0645\u0633\u0645\u0648\u062D \u0628\u0647\u0627 \u0648\u0644\u0643\u0646\u0647\u0627 \u0645\u0648\u062C\u0648\u062F\u0629 \u0641\u064A \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A +oneOf = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0645\u062E\u0637\u0637 \u0648\u0627\u062D\u062F \u0641\u0642\u0637\u060C \u0648\u0644\u0643\u0646 {0} \u0635\u0627\u0644\u062D +oneOf.indexes = \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0645\u062E\u0637\u0637 \u0648\u0627\u062D\u062F \u0641\u0642\u0637\u060C \u0648\u0644\u0643\u0646 {0} \u0635\u0627\u0644\u062D \u0645\u0639 \u0627\u0644\u0641\u0647\u0627\u0631\u0633 ''{1}'' +pattern = \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0646\u0645\u0637 \u0627\u0644\u062A\u0639\u0628\u064A\u0631 \u0627\u0644\u0639\u0627\u062F\u064A {0} +patternProperties = \u0628\u0647 \u0628\u0639\u0636 \u0627\u0644\u0623\u062E\u0637\u0627\u0621 \u0641\u064A ''\u062E\u0635\u0627\u0626\u0635 \u0627\u0644\u0646\u0645\u0637'' +prefixItems = \u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 \u0623\u062F\u0627\u0629 \u0627\u0644\u062A\u062D\u0642\u0642 \u0641\u064A \u0647\u0630\u0627 \u0627\u0644\u0641\u0647\u0631\u0633 +properties = \u064A\u0648\u062C\u062F \u062E\u0637\u0623 \u0641\u064A ''\u0627\u0644\u062E\u0635\u0627\u0626\u0635'' +propertyNames = \u0627\u0633\u0645 \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{0}'' \u063A\u064A\u0631 \u0635\u0627\u0644\u062D: {1} +readOnly = \u0647\u0648 \u062D\u0642\u0644 \u0644\u0644\u0642\u0631\u0627\u0621\u0629 \u0641\u0642\u0637\u060C \u0648\u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u063A\u064A\u064A\u0631\u0647 +required = \u0627\u0644\u062E\u0627\u0635\u064A\u0629 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 ''{0}'' \u063A\u064A\u0631 \u0645\u0648\u062C\u0648\u062F\u0629 +type = \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {0}\u060C \u0648\u0627\u0644\u0645\u062A\u0648\u0642\u0639 \u0647\u0648 {1}. +unevaluatedItems = \u0644\u0645 \u064A\u062A\u0645 \u062A\u0642\u064A\u064A\u0645 \u0627\u0644\u0641\u0647\u0631\u0633 ''{0}'' \u0648\u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0644\u0645\u062E\u0637\u0637 \u0628\u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u063A\u064A\u0631 \u0627\u0644\u0645\u0642\u064A\u064E\u0651\u0645\u0629 +unevaluatedProperties = \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{0}'' \u0644\u0645 \u064A\u062A\u0645 \u062A\u0642\u064A\u064A\u0645\u0647\u0627 \u0648\u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0644\u0645\u062E\u0637\u0637 \u0628\u0627\u0644\u062E\u0635\u0627\u0626\u0635 \u063A\u064A\u0631 \u0627\u0644\u0645\u0642\u064A\u064E\u0651\u0645\u0629 +unionType = \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {0}\u060C \u0648\u0627\u0644\u0645\u062A\u0648\u0642\u0639 \u0647\u0648 {1}. +uniqueItems = \u064A\u062C\u0628 \u0623\u0646 \u062A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0639\u0646\u0627\u0635\u0631 \u0641\u0631\u064A\u062F\u0629 \u0641\u0642\u0637 \u0641\u064A \u0627\u0644\u0645\u0635\u0641\u0648\u0641\u0629 +writeOnly = \u0647\u0648 \u062D\u0642\u0644 \u0644\u0644\u0643\u062A\u0627\u0628\u0629 \u0641\u0642\u0637\u060C \u0648\u0644\u0627 \u064A\u0645\u0643\u0646 \u0623\u0646 \u064A\u0638\u0647\u0631 \u0641\u064A \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A +contentEncoding = \u0644\u0627 \u064A\u0637\u0627\u0628\u0642 \u062A\u0631\u0645\u064A\u0632 \u0627\u0644\u0645\u062D\u062A\u0648\u0649 {0} +contentMediaType = \u0644\u064A\u0633 \u0645\u062D\u062A\u0648\u0649 \u062E\u0627\u0635\u064B\u0627 \u0628\u064A diff --git a/src/main/resources/jsv-messages_cs.properties b/src/main/resources/jsv-messages_cs.properties new file mode 100644 index 000000000..d6b35668c --- /dev/null +++ b/src/main/resources/jsv-messages_cs.properties @@ -0,0 +1,70 @@ +$ref = obsahuje chybu s ''refs'' +additionalItems = index ''{0}'' nen ve schmatu definovn a schma nepovoluje dal\u0161 polo\u017Eky +additionalProperties = vlastnost ''{0}'' nen ve schmatu definovna a schma neumo\u017E\u0148uje dal\u0161 vlastnosti +allOf = mus bt platn pro v\u0161echna schmata {0} +anyOf = mus bt platn pro kterkoli ze schmat {0} +const = mus bt konstantn hodnota ''{0}'' +contains = neobsahuje prvek, kter pro\u0161el t\u011Bmito ov\u011B\u0159enmi: {1} +contains.max = mus obsahovat nejv\u0161e {0} prvk\u016F, kter projdou t\u011Bmito ov\u011B\u0159enmi: {1} +contains.min = mus obsahovat alespo\u0148 {0} prvk\u016F, kter projdou t\u011Bmito ov\u011B\u0159enmi: {1} +dependencies = obsahuje chybu se zvislostmi {0} +dependentRequired = m chyb\u011Bjc vlastnost ''{0}'', kter je zvisl povinn, proto\u017Ee ''{1}'' je p\u0159tomen +dependentSchemas = obsahuje chybu s dependentSchemas {0} +enum = nem hodnotu ve v\u010Dtu {0} +exclusiveMaximum = mus mt exkluzivn maximln hodnotu {0} +exclusiveMinimum = mus mt exkluzivn minimln hodnotu {0} +false = schma pro ''{0}'' je nepravdiv +format = neodpovd vzoru {0} +format.date = neodpovd vzoru {0} mus bt platn pln datum RFC 3339 +format.date-time = neodpovd vzoru {0} mus bt platn datum a \u010Das RFC 3339 +format.duration = neodpovd vzoru {0}, mus mt platnou dobu trvn ISO 8601 +format.email = neodpovd vzoru {0} mus bt platn po\u0161tovn schrnka RFC 5321 +format.ipv4 = neodpovd vzoru {0} mus bt platn IP adresa RFC 2673 +format.ipv6 = neodpovd vzoru {0} mus bt platn IP adresa RFC 4291 +format.idn-email = neodpovd vzoru {0} mus bt platn po\u0161tovn schrnka RFC 6531 +format.idn-hostname = neodpovd vzoru {0}, mus bt platnm internacionalizovanm nzvem hostitele RFC 5890 +format.iri = neodpovd vzoru {0} mus bt platn RFC 3987 IRI +format.iri-reference = neodpovd vzoru {0} mus bt platn RFC 3987 IRI-reference +format.uri = neodpovd vzoru {0} mus bt platn RFC 3986 URI +format.uri-reference = neodpovd vzoru {0} mus bt platn RFC 3986 URI odkaz +format.uri-template = neodpovd vzoru {0} mus bt platn \u0161ablona URI RFC 6570 +format.uuid = neodpovd vzoru {0} mus bt platn RFC 4122 UUID +format.regex = neodpovd vzoru {0} mus bt platn regulrn vraz ECMA-262 +format.time = neodpovd vzoru {0} mus bt platn \u010Das RFC 3339 +format.hostname = neodpovd vzoru {0} mus bt platn nzev hostitele RFC 1123 +format.json-pointer = neodpovd vzoru {0} mus bt platn RFC 6901 JSON ukazatel +format.relative-json-pointer = neodpovd vzoru {0} mus bt platn IETF relativn ukazatel JSON +format.unknown = m neznm formt ''{0}'' +id = ''{0}'' nen platn {1} +items = index ''{0}'' nen ve schmatu definovn a schma neumo\u017E\u0148uje dal\u0161 polo\u017Eky +maxContains = mus bt nezporn cel \u010Dslo v {0} +maxItems = mus mt maximln\u011B {0} polo\u017Eek, ale nalezeno {1} +maxLength = mus mt maximln\u011B {0} znak\u016F +maxProperties = mus mt maximln\u011B {0} vlastnost +maximum = mus mt maximln hodnotu {0} +minContains = mus bt nezporn cel \u010Dslo v {0} +minContainsVsMaxContains = minContains mus bt men\u0161 nebo roven maxContains v {0} +minItems = mus mt alespo\u0148 {0} polo\u017Eek, ale nalezeno {1} +minLength = mus mt alespo\u0148 {0} znak\u016F +minProperties = mus mt alespo\u0148 {0} vlastnost +minimum = mus mt minimln hodnotu {0} +multipleOf = mus bt nsobkem {0} +not = nesm bt platn pro schma {0} +notAllowed = vlastnost ''{0}'' nen povolena, ale je v datech +oneOf = mus bt platn pro jedno a pouze jedno schma, ale {0} jsou platn +oneOf.indexes = mus bt platn pro jedno a pouze jedno schma, ale {0} jsou platn s indexy ''{1}'' +pattern = neodpovd vzoru regulrnho vrazu {0} +patternProperties = obsahuje n\u011Bjakou chybu s ''vlastnostmi vzoru'' +prefixItems = v tomto indexu nebyl nalezen \u017Edn validtor +properties = obsahuje chybu s ''vlastnosti'' +propertyNames = nzev vlastnosti ''{0}'' nen platn: {1} +readOnly = je pole pouze pro \u010Dten, nelze jej zm\u011Bnit +required = po\u017Eadovan vlastnost ''{0}'' nebyla nalezena +type = nalezeno {0}, o\u010Dekvno {1} +unevaluatedItems = index ''{0}'' nen vyhodnocen a schma nepovoluje neohodnocen polo\u017Eky +unevaluatedProperties = vlastnost ''{0}'' nen vyhodnocena a schma nepovoluje neohodnocen vlastnosti +unionType = nalezeno {0}, o\u010Dekvno {1} +uniqueItems = mus mt v poli pouze jedine\u010Dn polo\u017Eky +writeOnly = je pole pouze pro zpis, nem\u016F\u017Ee se objevit v datech +contentEncoding = neodpovd kdovn obsahu {0} +contentMediaType = nen obsah j diff --git a/src/main/resources/jsv-messages_da.properties b/src/main/resources/jsv-messages_da.properties new file mode 100644 index 000000000..f1eaa1375 --- /dev/null +++ b/src/main/resources/jsv-messages_da.properties @@ -0,0 +1,70 @@ +$ref = har en fejl med ''refs'' +additionalItems = indeks ''{0}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere elementer +additionalProperties = egenskaben ''{0}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere egenskaber +allOf = skal vre gyldig for alle skemaerne {0} +anyOf = skal vre gyldig for et hvilket som helst af skemaerne {0} +const = skal vre den konstante vrdi ''{0}'' +contains = indeholder ikke et element, der bestr disse valideringer: {1} +contains.max = m hjst indeholde {0} element(er), der bestr disse valideringer: {1} +contains.min = skal indeholde mindst {0} element(er), der bestr disse valideringer: {1} +dependencies = har en fejl med afhngigheder {0} +dependentRequired = har en manglende egenskab ''{0}'', som er afhngig pkrvet, fordi ''{1}'' er til stede +dependentSchemas = har en fejl med dependentSchemas {0} +enum = har ikke en vrdi i opregningen {0} +exclusiveMaximum = skal have en eksklusiv maksimumvrdi p {0} +exclusiveMinimum = skal have en eksklusiv minimumsvrdi p {0} +false = skemaet for ''{0}'' er falsk +format = matcher ikke {0}-mnsteret +format.date = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3339 fuld-dato +format.date-time = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3339 dato-tid +format.duration = matcher ikke {0}-mnsteret skal vre en gyldig ISO 8601-varighed +format.email = matcher ikke {0}-mnsteret skal vre en gyldig RFC 5321-postkasse +format.ipv4 = matcher ikke {0}-mnsteret skal vre en gyldig RFC 2673 IP-adresse +format.ipv6 = matcher ikke {0}-mnsteret skal vre en gyldig RFC 4291 IP-adresse +format.idn-email = matcher ikke {0}-mnsteret skal vre en gyldig RFC 6531-postkasse +format.idn-hostname = matcher ikke {0}-mnsteret skal vre et gyldigt RFC 5890 internationaliseret vrtsnavn +format.iri = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3987 IRI +format.iri-reference = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3987 IRI-reference +format.uri = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3986 URI +format.uri-reference = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3986 URI-reference +format.uri-template = matcher ikke {0}-mnsteret skal vre en gyldig RFC 6570 URI-skabelon +format.uuid = matcher ikke {0}-mnsteret skal vre et gyldigt RFC 4122 UUID +format.regex = matcher ikke {0}-mnsteret skal vre et gyldigt ECMA-262 regulrt udtryk +format.time = matcher ikke {0}-mnsteret skal vre en gyldig RFC 3339-tid +format.hostname = matcher ikke {0}-mnsteret skal vre et gyldigt RFC 1123-vrtsnavn +format.json-pointer = matcher ikke {0}-mnsteret skal vre en gyldig RFC 6901 JSON-peger +format.relative-json-pointer = matcher ikke {0}-mnsteret skal vre en gyldig IETF Relativ JSON-peger +format.unknown = har et ukendt format ''{0}'' +id = ''{0}'' er ikke en gyldig {1} +items = indeks ''{0}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere elementer +maxContains = skal vre et ikke-negativt heltal i {0} +maxItems = m hjst have {0} varer, men fundet {1} +maxLength = m hjst vre p {0} tegn +maxProperties = m hjst have {0} egenskaber +maximum = skal have en maksimal vrdi p {0} +minContains = skal vre et ikke-negativt heltal i {0} +minContainsVsMaxContains = minContains skal vre mindre end eller lig med maxContains i {0} +minItems = skal have mindst {0} elementer, men fundet {1} +minLength = skal vre mindst {0} tegn lang +minProperties = skal have mindst {0} egenskaber +minimum = skal have en minimumsvrdi p {0} +multipleOf = skal vre multiplum af {0} +not = m ikke vre gyldig for skemaet {0} +notAllowed = egenskaben ''{0}'' er ikke tilladt, men den er i dataene +oneOf = skal vre gyldig for t og kun t skema, men {0} er gyldige +oneOf.indexes = skal vre gyldig for t og kun t skema, men {0} er gyldige med indekser ''{1}'' +pattern = matcher ikke regex-mnsteret {0} +patternProperties = har en fejl med ''mnsteregenskaber'' +prefixItems = ingen validator fundet i dette indeks +properties = har en fejl med ''egenskaber'' +propertyNames = Ejendommen ''{0}'' navn er ikke gyldigt: {1} +readOnly = er et skrivebeskyttet felt, det kan ikke ndres +required = pkrvet egenskab ''{0}'' blev ikke fundet +type = {0} fundet, {1} forventet +unevaluatedItems = indeks ''{0}'' evalueres ikke, og skemaet tillader ikke uevaluerede elementer +unevaluatedProperties = egenskaben ''{0}'' evalueres ikke, og skemaet tillader ikke uevaluerede egenskaber +unionType = {0} fundet, {1} forventet +uniqueItems = m kun have unikke elementer i arrayet +writeOnly = er et skrivebeskyttet felt, det kan ikke vises i dataene +contentEncoding = matcher ikke indholdskodning {0} +contentMediaType = er ikke et indholds mig diff --git a/src/main/resources/jsv-messages_de.properties b/src/main/resources/jsv-messages_de.properties index ecaaed7cb..7eae3b691 100644 --- a/src/main/resources/jsv-messages_de.properties +++ b/src/main/resources/jsv-messages_de.properties @@ -1,40 +1,70 @@ -additionalProperties = {0}.{1} ist nicht im Schema definiert und das Schema verbietet 'additionalProperties' -allOf = {0} muss gltig fr alle Schemata {1} sein -anyOf = {0} muss gltig fr mindestens ein Schema {1} sein -crossEdits = {0}: Ein Fehler mit 'cross edits' ist aufgetreten -dependencies = {0} hat einen Fehler mit Abhngigkeiten {1} -dependentRequired = {0} fehlt eine Eigenschaft, welche 'dependentRequired' {1} ist -dependentSchemas = {0}: Ein Fehler mit 'dependentSchemas' {1} ist aufgetreten -edits = {0}: Ein Fehler mit 'edits' ist aufgetreten -enum = {0}: Ein Wert in der Aufzhlung {1} fehlt -format = {0} muss dem Format {1} entsprechen {2} -items = {0}[{1}]: Kein Validator an diesem Index gefunden -maximum = {0} darf den Wert {1} nicht berschreiten -maxItems = {0} darf hchstens {1} Element(e) beinhalten -maxLength = {0} darf hchstens {1} Zeichen lang sein -maxProperties = {0} darf hchstens {1} Eigenschaft(en) haben -minimum = {0} muss mindestens den Wert {1} haben -minItems = {0} muss mindestens {1} Element(e) beinhalten -minLength = {0} muss mindestens {1} Zeichen lang sein -minProperties = {0} muss mindestens {1} Eigenschaft(en) haben -multipleOf = {0} muss ein Vielfaches von {1} sein -notAllowed = {0}.{1} ist nicht erlaubt und darf folglich nicht auftreten -not = {0} soll nicht gltig sein fr das Schema {1} -oneOf = {0} darf nur fr ein einziges Schema {1} gltig sein -patternProperties = {0} stimmt nicht berein mit dem Format definiert in 'pattern properties' -pattern = {0} stimmt nicht mit dem regulren Ausdruck {1} berein -properties = {0}: Ein Fehler mit 'properties' ist aufgetreten -readOnly = {0} ist ein schreibgeschtztes Feld und kann nicht verndert werden -$ref = {0}: Ein Fehler mit 'refs' ist aufgetreten -required = {0}.{1} ist ein Pflichtfeld aber fehlt -type = {0}: {1} wurde gefunden aber {2} erwartet -unionType = {0}: {1} wurde gefunden aber {2} wird verlangt -uniqueItems = {0}: Die Element(e) des Arrays mssen einmalig sein -uuid = {0}: {1} ist ein ungltiges {2} -id = {0}: {1} ist ein ungltiges Segment fr die URI {2} -exclusiveMaximum = {0} muss grer sein als {1} -exclusiveMinimum = {0} muss kleiner sein als {1} -false = Das boolsche Schema 'false' ist ungltig -const = {0} muss den konstanten Wert {1} annehmen -contains = {0} beinhaltet kein Element, das diese Validierung besteht: {1} -propertyNames = Eigenschaftsname {0} ist ungltig fr die Validierung: {1} +$ref = hat einen Fehler mit ''refs'' +additionalItems = Index \u201E{0}\u201C ist im Schema nicht definiert und das Schema lsst keine zustzlichen Elemente zu +additionalProperties = Eigenschaft ''{0}'' ist im Schema nicht definiert und das Schema lsst keine zustzlichen Eigenschaften zu +allOf = muss fr alle Schemas {0} gltig sein +anyOf = muss fr eines der Schemas {0} gltig sein +const = muss der konstante Wert ''{0}'' sein +contains = enthlt kein Element, das diese Validierungen besteht: {1} +contains.max = muss hchstens {0} Elemente enthalten, die diese Validierungen bestehen: {1} +contains.min = muss mindestens {0} Elemente enthalten, die diese Validierungen bestehen: {1} +dependencies = Es liegt ein Fehler mit den Abhngigkeiten {0} vor. +dependentRequired = Es fehlt eine Eigenschaft \u201E{0}\u201C, die abhngig ist, da \u201E{1}\u201C vorhanden ist +dependentSchemas = Es liegt ein Fehler mit dependenceSchemas {0} vor. +enum = hat keinen Wert in der Aufzhlung {0} +exclusiveMaximum = muss einen exklusiven Maximalwert von {0} haben +exclusiveMinimum = muss einen exklusiven Mindestwert von {0} haben +false = Schema fr ''{0}'' ist falsch +format = entspricht nicht dem Muster {0} +format.date = stimmt nicht mit dem {0}-Muster berein, muss ein gltiges RFC 3339-Volldatum sein +format.date-time = entspricht nicht dem {0}-Muster. Es muss sich um ein gltiges RFC 3339-Datum/Uhrzeit-Format handeln +format.duration = stimmt nicht mit dem {0}-Muster berein, muss eine gltige ISO 8601-Dauer sein +format.email = entspricht nicht dem {0}-Muster. Es muss sich um ein gltiges RFC 5321-Postfach handeln +format.ipv4 = entspricht nicht dem {0}-Muster. Es muss sich um eine gltige RFC 2673-IP-Adresse handeln +format.ipv6 = entspricht nicht dem {0}-Muster. Es muss sich um eine gltige RFC 4291-IP-Adresse handeln +format.idn-email = entspricht nicht dem {0}-Muster. Es muss sich um ein gltiges RFC 6531-Postfach handeln +format.idn-hostname = entspricht nicht dem {0}-Muster. Es muss sich um einen gltigen internationalisierten RFC 5890-Hostnamen handeln +format.iri = entspricht nicht dem {0}-Muster, muss ein gltiger RFC 3987 IRI sein +format.iri-reference = entspricht nicht dem {0}-Muster muss eine gltige RFC 3987 IRI-Referenz sein +format.uri = entspricht nicht dem {0}-Muster muss ein gltiger RFC 3986-URI sein +format.uri-reference = stimmt nicht mit dem {0}-Muster berein, muss eine gltige RFC 3986-URI-Referenz sein +format.uri-template = entspricht nicht dem {0}-Muster. Es muss sich um eine gltige RFC 6570-URI-Vorlage handeln +format.uuid = stimmt nicht mit dem {0}-Muster berein, muss eine gltige RFC 4122-UUID sein +format.regex = entspricht nicht dem Muster {0} muss ein gltiger regulrer ECMA-262-Ausdruck sein +format.time = entspricht nicht dem {0}-Muster muss eine gltige RFC 3339-Zeit sein +format.hostname = entspricht nicht dem {0}-Muster. Es muss sich um einen gltigen RFC 1123-Hostnamen handeln +format.json-pointer = stimmt nicht mit dem {0}-Muster berein, muss ein gltiger RFC 6901-JSON-Zeiger sein +format.relative-json-pointer = stimmt nicht mit dem {0}-Muster berein, muss ein gltiger relativer IETF-JSON-Zeiger sein +format.unknown = hat ein unbekanntes Format ''{0}'' +id = ''{0}'' ist kein gltiger {1} +items = Index ''{0}'' ist im Schema nicht definiert und das Schema lsst keine zustzlichen Elemente zu +maxContains = muss eine nicht negative Ganzzahl in {0} sein +maxItems = muss hchstens {0} Elemente haben, aber {1} gefunden +maxLength = darf hchstens {0} Zeichen lang sein +maxProperties = darf hchstens {0} Eigenschaften haben +maximum = muss einen Maximalwert von {0} haben +minContains = muss eine nicht negative Ganzzahl in {0} sein +minContainsVsMaxContains = minContains muss kleiner oder gleich maxContains in {0} sein +minItems = muss mindestens {0} Elemente haben, aber {1} gefunden +minLength = muss mindestens {0} Zeichen lang sein +minProperties = muss mindestens {0} Eigenschaften haben +minimum = muss einen Mindestwert von {0} haben +multipleOf = muss ein Vielfaches von {0} sein +not = darf fr das Schema {0} nicht gltig sein +notAllowed = Eigenschaft ''{0}'' ist nicht zulssig, befindet sich aber in den Daten +oneOf = muss fr ein und nur ein Schema gltig sein, aber {0} sind gltig +oneOf.indexes = muss fr ein und nur ein Schema gltig sein, aber {0} sind mit den Indizes \u201E{1}\u201C gltig. +pattern = stimmt nicht mit dem Regex-Muster {0} berein +patternProperties = Es liegt ein Fehler mit den \u201EMustereigenschaften\u201C vor. +prefixItems = Bei diesem Index wurde kein Validator gefunden +properties = Es liegt ein Fehler mit \u201EProperties\u201C vor. +propertyNames = Der Name der Eigenschaft \u201E{0}\u201C ist ungltig: {1} +readOnly = ist ein schreibgeschtztes Feld, es kann nicht gendert werden +required = erforderliche Eigenschaft ''{0}'' nicht gefunden +type = {0} gefunden, {1} erwartet +unevaluatedItems = Index ''{0}'' wird nicht ausgewertet und das Schema lsst keine nicht ausgewerteten Elemente zu +unevaluatedProperties = Eigenschaft ''{0}'' wird nicht ausgewertet und das Schema lsst keine nicht ausgewerteten Eigenschaften zu +unionType = {0} gefunden, {1} erwartet +uniqueItems = Das Array darf nur eindeutige Elemente enthalten +writeOnly = ist ein schreibgeschtztes Feld, es kann nicht in den Daten erscheinen +contentEncoding = stimmt nicht mit der Inhaltskodierung {0} berein +contentMediaType = ist kein Inhalt fr mich diff --git a/src/main/resources/jsv-messages_en.properties b/src/main/resources/jsv-messages_en.properties new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/resources/jsv-messages_es.properties b/src/main/resources/jsv-messages_es.properties new file mode 100644 index 000000000..18a08cf9f --- /dev/null +++ b/src/main/resources/jsv-messages_es.properties @@ -0,0 +1,70 @@ +$ref = tiene un error con las 'referencias' +additionalItems = el \u00edndice ''{0}'' no est\u00e1 definido en el esquema y el esquema no permite elementos adicionales +additionalProperties = la propiedad ''{0}'' no est\u00e1 definida en el esquema y el esquema no permite propiedades adicionales +allOf = debe ser v\u00e1lido para todos los esquemas {0} +anyOf = debe ser v\u00e1lido para cualquiera de los esquemas {0} +const = debe ser el valor constante ''{0}'' +contains = no contiene un elemento que cumpla estas validaciones: {1} +contains.max = debe contener como m\u00e1ximo {0} elemento(s) que cumpla(n) estas validaciones: {1} +contains.min = debe contener al menos {0} elemento(s) que cumpla(n) estas validaciones: {1} +dependencies = tiene un error con las dependencias {0} +dependentRequired = tiene una propiedad faltante ''{0}'' que es dependiente y requerida porque ''{1}'' est\u00e1 presente +dependentSchemas = tiene un error con dependentSchemas {0} +enum = no tiene un valor en la enumeraci\u00f3n {0} +exclusiveMaximum = debe tener un valor m\u00e1ximo exclusivo de {0} +exclusiveMinimum = debe tener un valor m\u00ednimo exclusivo de {0} +false = el esquema para ''{0}'' es falso +format = no coincide con el patr\u00f3n {0} +format.date = no coincide con el patr\u00f3n {0}; debe ser un RFC 3339 con fecha completa v\u00e1lido +format.date-time = no coincide con el patr\u00f3n {0}; debe ser un RFC 3339 con fecha y hora v\u00e1lido +format.duration = no coincide con el patr\u00f3n {0}; debe ser un ISO 8601 de duraci\u00f3n v\u00e1lido +format.email = no coincide con el patr\u00f3n {0}; debe ser un RFC 5321 de buz\u00f3n de correo v\u00e1lido +format.ipv4 = no coincide con el patr\u00f3n {0}; debe ser un RFC 2673 de direcci\u00f3n IP v\u00e1lido +format.ipv6 = no coincide con el patr\u00f3n {0}; debe ser un RFC 4291 de direcci\u00f3n IP v\u00e1lido +format.idn-email = no coincide con el patr\u00f3n {0}; debe ser un RFC 6531 de buz\u00f3n de correo v\u00e1lido +format.idn-hostname = no coincide con el patr\u00f3n {0}; debe ser un RFC 5890 de nombre de host internacionalizado v\u00e1lido +format.iri = no coincide con el patr\u00f3n {0}; debe ser un RFC 3987 de IRI v\u00e1lido +format.iri-reference = no coincide con el patr\u00f3n {0}; debe ser un RFC 3987 de referencia IRI v\u00e1lido +format.uri = no coincide con el patr\u00f3n {0}; debe ser un RFC 3986 de URI v\u00e1lido +format.uri-reference = no coincide con el patr\u00f3n {0}; debe ser un RFC 3986 de referencia URI v\u00e1lido +format.uri-template = no coincide con el patr\u00f3n {0}; debe ser un RFC 6570 de plantilla de URI v\u00e1lido +format.uuid = no coincide con el patr\u00f3n {0}; debe ser un RFC 4122 de UUID v\u00e1lido +format.regex = no coincide con el patr\u00f3n {0}; debe ser una expresi\u00f3n regular ECMA-262 v\u00e1lida +format.time = no coincide con el patr\u00f3n {0}; debe ser un RFC 3339 con hora v\u00e1lido +format.hostname = no coincide con el patr\u00f3n {0}; debe ser un RFC 1123 de nombre de host v\u00e1lido +format.json-pointer = no coincide con el patr\u00f3n {0}; debe ser un RFC 6901 de puntero JSON v\u00e1lido +format.relative-json-pointer = no coincide con el patr\u00f3n {0}; debe ser un IETF relativo de puntero JSON v\u00e1lido +format.unknown = tiene un formato desconocido ''{0}'' +id = ''{0}'' no es un v\u00e1lido {1} +items = el \u00edndice ''{0}'' no est\u00e1 definido en el esquema y el esquema no permite elementos adicionales +maxContains = debe ser un entero no negativo en {0} +maxItems = debe tener como m\u00e1ximo {0} elementos, pero se encontraron {1} +maxLength = debe tener como m\u00e1ximo {0} caracteres +maxProperties = debe tener como m\u00e1ximo {0} propiedades +maximum = debe tener un valor m\u00e1ximo de {0} +minContains = debe ser un entero no negativo en {0} +minContainsVsMaxContains = minContains debe ser menor o igual a maxContains en {0} +minItems = debe tener al menos {0} elementos, pero se encontraron {1} +minLength = debe tener al menos {0} caracteres +minProperties = debe tener al menos {0} propiedades +minimum = debe tener un valor m\u00ednimo de {0} +multipleOf = debe ser m\u00faltiplo de {0} +not = no debe ser v\u00e1lido para el esquema {0} +notAllowed = la propiedad ''{0}'' no est\u00e1 permitida, pero est\u00e1 en los datos +oneOf = debe ser v\u00e1lido para uno y solo un esquema, pero {0} son v\u00e1lidos +oneOf.indexes = debe ser v\u00e1lido para uno y solo un esquema, pero {0} son v\u00e1lidos con los \u00edndices ''{1}'' +pattern = no coincide con el patr\u00f3n regex {0} +patternProperties = tiene algunos errores con las 'propiedades del patr\u00f3n' +prefixItems = no se encuentra ning\u00fan elemento de validaci\u00f3n en este \u00edndice +properties = tiene un error con las 'propiedades' +propertyNames = el nombre ''{0}'' de la propiedad no es v\u00e1lido: {1} +readOnly = no puede cambiarse, ya que es un campo de solo lectura +required = no se encontr\u00f3 la propiedad requerida ''{0}'' +type = se encontraron {0}, se preve\u00edan {1} +unevaluatedItems = el \u00edndice ''{0}'' no est\u00e1 evaluado y el esquema no permite elementos que no hayan sido evaluados +unevaluatedProperties = la propiedad ''{0}'' no est\u00e1 evaluada y el esquema no permite propiedades que no hayan sido evaluadas +unionType = se encontraron {0}, se preve\u00edan {1} +uniqueItems = debe tener solo elementos \u00fanicos en la matriz +writeOnly = es un campo de solo lectura, no puede aparecer en los datos +contentEncoding = no coincide con la codificaci\u00f3n de contenido {0} +contentMediaType = no es un tipo de medios de contenido diff --git a/src/main/resources/jsv-messages_fa.properties b/src/main/resources/jsv-messages_fa.properties new file mode 100644 index 000000000..aa62ea930 --- /dev/null +++ b/src/main/resources/jsv-messages_fa.properties @@ -0,0 +1,70 @@ +$ref = \u062F\u0627\u0631\u0627\u06CC \u062E\u0637\u0627 \u0628\u0627 "refs" \u0627\u0633\u062A +additionalItems = \u0646\u0645\u0627\u06CC\u0647 ''{0}'' \u062F\u0631 \u0627\u06CC\u0646 \u0637\u0631\u062D \u062A\u0639\u0631\u06CC\u0641 \u0646\u0634\u062F\u0647 \u0627\u0633\u062A \u0648 \u0637\u0631\u062D \u0645\u0648\u0627\u0631\u062F \u0627\u0636\u0627\u0641\u06CC \u0631\u0627 \u0645\u062C\u0627\u0632 \u0646\u0645\u06CC\u200C\u062F\u0627\u0646\u062F +additionalProperties = \u062E\u0627\u0635\u06CC\u062A ''{0}'' \u062F\u0631 \u0637\u0631\u062D \u062A\u0639\u0631\u06CC\u0641 \u0646\u0634\u062F\u0647 \u0627\u0633\u062A \u0648 \u0627\u06CC\u0646 \u0637\u0631\u062D \u0648\u06CC\u0698\u06AF\u06CC \u0647\u0627\u06CC \u0627\u0636\u0627\u0641\u06CC \u0631\u0627 \u0627\u062C\u0627\u0632\u0647 \u0646\u0645\u06CC \u062F\u0647\u062F +allOf = \u0628\u0627\u06CC\u062F \u0628\u0631\u0627\u06CC \u0647\u0645\u0647 \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0647\u0627 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F {0} +anyOf = \u0628\u0627\u06CC\u062F \u0628\u0631\u0627\u06CC \u0647\u0631 \u06CC\u06A9 \u0627\u0632 \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0647\u0627 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F {0} +const = \u0628\u0627\u06CC\u062F \u0645\u0642\u062F\u0627\u0631 \u062B\u0627\u0628\u062A "{0}" \u0628\u0627\u0634\u062F +contains = \u062D\u0627\u0648\u06CC \u0639\u0646\u0635\u0631\u06CC \u0646\u06CC\u0633\u062A \u06A9\u0647 \u0627\u06CC\u0646 \u0627\u0639\u062A\u0628\u0627\u0631\u0633\u0646\u062C\u06CC \u0647\u0627 \u0631\u0627 \u067E\u0627\u0633 \u06A9\u0646\u062F: {1} +contains.max = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u062D\u0627\u0648\u06CC {0} \u0639\u0646\u0635\u0631 (\u0647\u0627) \u0628\u0627\u0634\u062F \u06A9\u0647 \u0627\u06CC\u0646 \u0627\u0639\u062A\u0628\u0627\u0631\u0633\u0646\u062C\u06CC \u0647\u0627 \u0631\u0627 \u067E\u0627\u0633 \u0645\u06CC \u06A9\u0646\u062F: {1} +contains.min = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0634\u0627\u0645\u0644 {0} \u0639\u0646\u0635\u0631 (\u0647\u0627) \u0628\u0627\u0634\u062F \u06A9\u0647 \u0627\u06CC\u0646 \u0627\u0639\u062A\u0628\u0627\u0631\u0633\u0646\u062C\u06CC \u0647\u0627 \u0631\u0627 \u0628\u06AF\u0630\u0631\u0627\u0646\u062F: {1} +dependencies = \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u062E\u0637\u0627 \u062F\u0631 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC {0} +dependentRequired = \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u0648\u06CC\u0698\u06AF\u06CC \u06AF\u0645 \u0634\u062F\u0647 "{0}" \u0627\u0633\u062A \u06A9\u0647 \u0628\u0647 \u062F\u0644\u06CC\u0644 \u0648\u062C\u0648\u062F "{1}" \u0648\u0627\u0628\u0633\u062A\u0647 \u0627\u0633\u062A +dependentSchemas = \u062F\u0627\u0631\u0627\u06CC \u062E\u0637\u0627 \u0628\u0627 dependentSchemas {0} +enum = \u0645\u0642\u062F\u0627\u0631\u06CC \u062F\u0631 \u0634\u0645\u0627\u0631\u0634 {0} \u0646\u062F\u0627\u0631\u062F +exclusiveMaximum = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u0645\u0642\u062F\u0627\u0631 \u0627\u0646\u062D\u0635\u0627\u0631\u06CC {0} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +exclusiveMinimum = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0645\u0642\u062F\u0627\u0631 \u0627\u0646\u062D\u0635\u0627\u0631\u06CC {0} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +false = \u0637\u0631\u062D\u0648\u0627\u0631\u0647 ''{0}'' \u0646\u0627\u062F\u0631\u0633\u062A \u0627\u0633\u062A +format = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F +format.date = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u062A\u0627\u0631\u06CC\u062E \u06A9\u0627\u0645\u0644 RFC 3339 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.date-time = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u062A\u0627\u0631\u06CC\u062E \u0648 \u0632\u0645\u0627\u0646 RFC 3339 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.duration = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u0645\u062F\u062A \u0632\u0645\u0627\u0646 ISO 8601 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.email = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0635\u0646\u062F\u0648\u0642 \u067E\u0633\u062A\u06CC RFC 5321 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.ipv4 = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0622\u062F\u0631\u0633 IP \u0645\u0639\u062A\u0628\u0631 RFC 2673 \u0628\u0627\u0634\u062F +format.ipv6 = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0622\u062F\u0631\u0633 IP \u0645\u0639\u062A\u0628\u0631 RFC 4291 \u0628\u0627\u0634\u062F +format.idn-email = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0635\u0646\u062F\u0648\u0642 \u067E\u0633\u062A\u06CC RFC 6531 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.idn-hostname = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0646\u0627\u0645 \u0645\u06CC\u0632\u0628\u0627\u0646 \u0628\u06CC\u0646 \u0627\u0644\u0645\u0644\u0644\u06CC \u0634\u062F\u0647 \u0645\u0639\u062A\u0628\u0631 RFC 5890 \u0628\u0627\u0634\u062F. +format.iri = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 RFC 3987 IRI \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.iri-reference = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u0645\u0631\u062C\u0639 \u0645\u0639\u062A\u0628\u0631 RFC 3987 IRI \u0628\u0627\u0634\u062F +format.uri = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 URI RFC 3986 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.uri-reference = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0645\u0631\u062C\u0639 URI RFC 3986 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.uri-template = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0627\u0644\u06AF\u0648\u06CC URI RFC 6570 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.uuid = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 UUID RFC 4122 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.regex = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0639\u0628\u0627\u0631\u062A \u0645\u0646\u0638\u0645 ECMA-262 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.time = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u0632\u0645\u0627\u0646 \u0645\u0639\u062A\u0628\u0631 RFC 3339 \u0628\u0627\u0634\u062F +format.hostname = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0646\u0627\u0645 \u0645\u06CC\u0632\u0628\u0627\u0646 \u0645\u0639\u062A\u0628\u0631 RFC 1123 \u0628\u0627\u0634\u062F +format.json-pointer = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F RFC 6901 JSON Pointer \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.relative-json-pointer = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {0} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0646\u0634\u0627\u0646\u06AF\u0631 JSON \u0646\u0633\u0628\u06CC IETF \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F +format.unknown = \u062F\u0627\u0631\u0627\u06CC \u0642\u0627\u0644\u0628 \u0646\u0627\u0634\u0646\u0627\u062E\u062A\u0647 ''{0}'' +id = "{0}" \u06CC\u06A9 {1} \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A +items = \u0646\u0645\u0627\u06CC\u0647 ''{0}'' \u062F\u0631 \u0637\u0631\u062D \u062A\u0639\u0631\u06CC\u0641 \u0646\u0634\u062F\u0647 \u0627\u0633\u062A \u0648 \u0637\u0631\u062D \u0645\u0648\u0627\u0631\u062F \u0627\u0636\u0627\u0641\u06CC \u0631\u0627 \u0645\u062C\u0627\u0632 \u0646\u0645\u06CC \u06A9\u0646\u062F +maxContains = \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0639\u062F\u062F \u0635\u062D\u06CC\u062D \u063A\u06CC\u0631 \u0645\u0646\u0641\u06CC \u062F\u0631 {0} \u0628\u0627\u0634\u062F +maxItems = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {0} \u0645\u0648\u0631\u062F \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F \u0627\u0645\u0627 {1} \u06CC\u0627\u0641\u062A \u0634\u0648\u062F +maxLength = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {0} \u06A9\u0627\u0631\u0627\u06A9\u062A\u0631 \u0628\u0627\u0634\u062F +maxProperties = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {0} \u0648\u06CC\u0698\u06AF\u06CC \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +maximum = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u0645\u0642\u062F\u0627\u0631 {0} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +minContains = \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0639\u062F\u062F \u0635\u062D\u06CC\u062D \u063A\u06CC\u0631 \u0645\u0646\u0641\u06CC \u062F\u0631 {0} \u0628\u0627\u0634\u062F +minContainsVsMaxContains = minContains \u0628\u0627\u06CC\u062F \u06A9\u0645\u062A\u0631 \u06CC\u0627 \u0645\u0633\u0627\u0648\u06CC maxContains \u062F\u0631 {0} +minItems = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {0} \u0645\u0648\u0631\u062F \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F \u0627\u0645\u0627 {1} \u067E\u06CC\u062F\u0627 \u0634\u062F\u0647 \u0628\u0627\u0634\u062F +minLength = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {0} \u06A9\u0627\u0631\u0627\u06A9\u062A\u0631 \u0628\u0627\u0634\u062F +minProperties = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {0} \u0648\u06CC\u0698\u06AF\u06CC \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +minimum = \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0645\u0642\u062F\u0627\u0631 {0} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +multipleOf = \u0628\u0627\u06CC\u062F \u0645\u0636\u0631\u0628 {0} \u0628\u0627\u0634\u062F +not = \u0646\u0628\u0627\u06CC\u062F \u0628\u0631\u0627\u06CC \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F {0} +notAllowed = \u0648\u06CC\u0698\u06AF\u06CC "{0}" \u0645\u062C\u0627\u0632 \u0646\u06CC\u0633\u062A \u0627\u0645\u0627 \u062F\u0631 \u062F\u0627\u062F\u0647 \u0647\u0627 \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F +oneOf = \u0628\u0627\u06CC\u062F \u0628\u0631\u0627\u06CC \u06CC\u06A9 \u0648 \u062A\u0646\u0647\u0627 \u06CC\u06A9 \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F\u060C \u0627\u0645\u0627 {0} \u0645\u0639\u062A\u0628\u0631 \u0647\u0633\u062A\u0646\u062F +oneOf.indexes = \u0628\u0627\u06CC\u062F \u0628\u0631\u0627\u06CC \u06CC\u06A9 \u0648 \u062A\u0646\u0647\u0627 \u06CC\u06A9 \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0645\u0639\u062A\u0628\u0631 \u0628\u0627\u0634\u062F\u060C \u0627\u0645\u0627 {0} \u0628\u0627 \u0646\u0645\u0627\u06CC\u0647 \u0647\u0627\u06CC ''{1}'' \u0645\u0639\u062A\u0628\u0631 \u0647\u0633\u062A\u0646\u062F +pattern = \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC regex \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F {0} +patternProperties = \u062F\u0627\u0631\u0627\u06CC \u0645\u0642\u062F\u0627\u0631\u06CC \u062E\u0637\u0627 \u0628\u0627 "\u062E\u0648\u0627\u0635 \u0627\u0644\u06AF\u0648" \u0627\u0633\u062A +prefixItems = \u0647\u06CC\u0686 \u0627\u0639\u062A\u0628\u0627\u0631\u0633\u0646\u062C\u06CC \u062F\u0631 \u0627\u06CC\u0646 \u0641\u0647\u0631\u0633\u062A \u06CC\u0627\u0641\u062A \u0646\u0634\u062F +properties = \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u062E\u0637\u0627 \u0628\u0627 "properties" \u0627\u0633\u062A +propertyNames = \u0646\u0627\u0645 \u0648\u06CC\u0698\u06AF\u06CC ''{0}'' \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A: {1} +readOnly = \u06CC\u06A9 \u0641\u06CC\u0644\u062F \u0641\u0642\u0637 \u062E\u0648\u0627\u0646\u062F\u0646\u06CC \u0627\u0633\u062A\u060C \u0646\u0645\u06CC \u062A\u0648\u0627\u0646 \u0622\u0646 \u0631\u0627 \u062A\u063A\u06CC\u06CC\u0631 \u062F\u0627\u062F +required = \u0648\u06CC\u0698\u06AF\u06CC \u0645\u0648\u0631\u062F \u0646\u06CC\u0627\u0632 ''{0}'' \u06CC\u0627\u0641\u062A \u0646\u0634\u062F +type = {0} \u06CC\u0627\u0641\u062A \u0634\u062F\u060C {1} \u0645\u0648\u0631\u062F \u0627\u0646\u062A\u0638\u0627\u0631 \u0628\u0648\u062F +unevaluatedItems = \u0646\u0645\u0627\u06CC\u0647 ''{0}'' \u0627\u0631\u0632\u06CC\u0627\u0628\u06CC \u0646\u0645\u06CC \u0634\u0648\u062F \u0648 \u0637\u0631\u062D\u0648\u0627\u0631\u0647 \u0645\u0648\u0627\u0631\u062F \u0627\u0631\u0632\u06CC\u0627\u0628\u06CC \u0646\u0634\u062F\u0647 \u0631\u0627 \u0645\u062C\u0627\u0632 \u0646\u0645\u06CC \u062F\u0627\u0646\u062F +unevaluatedProperties = \u0648\u06CC\u0698\u06AF\u06CC ''{0}'' \u0627\u0631\u0632\u06CC\u0627\u0628\u06CC \u0646\u0645\u06CC \u0634\u0648\u062F \u0648 \u0627\u06CC\u0646 \u0637\u0631\u062D \u0648\u06CC\u0698\u06AF\u06CC \u0647\u0627\u06CC \u0627\u0631\u0632\u06CC\u0627\u0628\u06CC \u0646\u0634\u062F\u0647 \u0631\u0627 \u0627\u062C\u0627\u0632\u0647 \u0646\u0645\u06CC \u062F\u0647\u062F +unionType = {0} \u06CC\u0627\u0641\u062A \u0634\u062F\u060C {1} \u0645\u0648\u0631\u062F \u0627\u0646\u062A\u0638\u0627\u0631 \u0628\u0648\u062F +uniqueItems = \u0628\u0627\u06CC\u062F \u0641\u0642\u0637 \u0645\u0648\u0627\u0631\u062F \u0645\u0646\u062D\u0635\u0631 \u0628\u0647 \u0641\u0631\u062F \u062F\u0631 \u0622\u0631\u0627\u06CC\u0647 \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F +writeOnly = \u06CC\u06A9 \u0641\u06CC\u0644\u062F \u0641\u0642\u0637 \u0646\u0648\u0634\u062A\u0646\u06CC \u0627\u0633\u062A\u060C \u0646\u0645\u06CC \u062A\u0648\u0627\u0646\u062F \u062F\u0631 \u062F\u0627\u062F\u0647 \u0647\u0627 \u0638\u0627\u0647\u0631 \u0634\u0648\u062F +contentEncoding = \u0628\u0627 \u06A9\u062F\u06AF\u0630\u0627\u0631\u06CC \u0645\u062D\u062A\u0648\u0627 \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F {0} +contentMediaType = \u06CC\u06A9 \u0645\u062D\u062A\u0648\u0627\u06CC \u0645\u0646 \u0646\u06CC\u0633\u062A diff --git a/src/main/resources/jsv-messages_fi.properties b/src/main/resources/jsv-messages_fi.properties new file mode 100644 index 000000000..7c21d02c0 --- /dev/null +++ b/src/main/resources/jsv-messages_fi.properties @@ -0,0 +1,70 @@ +$ref = siin on virhe "viittauksilla" +additionalItems = hakemistoa ''{0}'' ei ole mritetty skeemassa, eik skeema salli liskohteita +additionalProperties = ominaisuutta ''{0}'' ei ole mritetty skeemassa, eik skeema salli lisominaisuuksia +allOf = tytyy olla voimassa kaikissa malleissa {0} +anyOf = tytyy olla kelvollinen mille tahansa skeemalle {0} +const = on oltava vakioarvo ''{0}'' +contains = ei sisll elementti, joka lpisee seuraavat tarkistukset: {1} +contains.max = saa sislt enintn {0} elementti, jotka lpisevt nm tarkistukset: {1} +contains.min = sislt vhintn {0} elementti, jotka lpisevt nm tarkistukset: {1} +dependencies = siin on virhe riippuvuuksien {0} kanssa +dependentRequired = puuttuu ominaisuus "{0}", joka on riippuvainen, koska "{1}" on lsn +dependentSchemas = sislt virheen dependentSchemasissa {0} +enum = sill ei ole arvoa luettelossa {0} +exclusiveMaximum = eksklusiivisen enimmisarvon on oltava {0} +exclusiveMinimum = on oltava yksinomainen vhimmisarvo {0} +false = kaava kohteelle ''{0}'' on eptosi +format = ei vastaa mallia {0} +format.date = ei vastaa mallia {0}, ja sen on oltava kelvollinen RFC 3339:n tysi pivmr +format.date-time = ei vastaa mallia {0}, ja sen on oltava kelvollinen RFC 3339 pivmr-aika +format.duration = ei vastaa mallia {0}. Sen on oltava kelvollinen ISO 8601 -kesto +format.email = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 5321 -postilaatikko +format.ipv4 = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 2673 IP-osoite +format.ipv6 = ei vastaa mallia {0}, ja sen on oltava kelvollinen RFC 4291 IP-osoite +format.idn-email = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 6531 -postilaatikko +format.idn-hostname = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 5890:n kansainvlistetty isntnimi +format.iri = ei vastaa mallia {0}, ja sen on oltava kelvollinen RFC 3987 IRI +format.iri-reference = ei vastaa {0}-mallia, on oltava kelvollinen RFC 3987 IRI-viite +format.uri = ei vastaa mallia {0}, ja sen on oltava kelvollinen RFC 3986 URI +format.uri-reference = ei vastaa {0}-mallia, tytyy olla kelvollinen RFC 3986 URI-viite +format.uri-template = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 6570 URI-malli +format.uuid = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 4122 UUID +format.regex = ei vastaa mallia {0}. Sen on oltava kelvollinen ECMA-262-snnllinen lauseke +format.time = ei vastaa mallia {0}, sen on oltava kelvollinen RFC 3339 -aika +format.hostname = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 1123 -isntnimi +format.json-pointer = ei vastaa mallia {0}. Sen on oltava kelvollinen RFC 6901 JSON-osoitin +format.relative-json-pointer = ei vastaa mallia {0}. Sen on oltava kelvollinen IETF:n suhteellinen JSON-osoitin +format.unknown = sill on tuntematon muoto ''{0}'' +id = ''{0}'' ei ole kelvollinen {1} +items = hakemistoa ''{0}'' ei ole mritetty skeemassa, eik skeema salli liskohteita +maxContains = tytyy olla ei-negatiivinen kokonaisluku ryhmss {0} +maxItems = saa olla enintn {0} kohdetta, mutta lytyi {1} +maxLength = saa olla enintn {0} merkki pitk +maxProperties = saa olla enintn {0} ominaisuutta +maximum = maksimiarvon on oltava {0} +minContains = tytyy olla ei-negatiivinen kokonaisluku ryhmss {0} +minContainsVsMaxContains = minContains on pienempi tai yht suuri kuin maxContains kohteessa {0} +minItems = on oltava vhintn {0} kohdetta, mutta lydetty {1} +minLength = on oltava vhintn {0} merkki pitk +minProperties = on oltava vhintn {0} ominaisuutta +minimum = vhimmisarvon on oltava {0} +multipleOf = tytyy olla {0}:n kerrannainen +not = ei saa olla kelvollinen kaavalle {0} +notAllowed = ominaisuus ''{0}'' ei ole sallittu, mutta se on tiedoissa +oneOf = tytyy olla voimassa vain yhdelle skeemalle, mutta {0} ovat kelvollisia +oneOf.indexes = tytyy olla voimassa vain yhdelle skeemalle, mutta {0} ovat kelvollisia indeksien ''{1}'' kanssa +pattern = ei vastaa snnllisen lausekkeen mallia {0} +patternProperties = siin on virhe komennolla "pattern properties" +prefixItems = tst hakemistosta ei lytynyt vahvistusta +properties = sislt virheen ominaisuuksissa +propertyNames = ominaisuuden ''{0}'' nimi ei kelpaa: {1} +readOnly = on vain luku -kentt, sit ei voi muuttaa +required = vaadittua ominaisuutta ''{0}'' ei lydy +type = {0} lydetty, {1} odotettu +unevaluatedItems = hakemistoa ''{0}'' ei arvioida, eik skeema salli arvioimattomia kohteita +unevaluatedProperties = ominaisuutta ''{0}'' ei arvioida ja skeema ei salli arvioimattomia ominaisuuksia +unionType = {0} lydetty, {1} odotettu +uniqueItems = taulukossa saa olla vain yksilllisi kohteita +writeOnly = on vain kirjoituskentt, se ei voi nky tiedoissa +contentEncoding = ei vastaa sislln koodausta {0} +contentMediaType = ei ole sisltni diff --git a/src/main/resources/jsv-messages_fr.properties b/src/main/resources/jsv-messages_fr.properties new file mode 100644 index 000000000..e3cb7668b --- /dev/null +++ b/src/main/resources/jsv-messages_fr.properties @@ -0,0 +1,70 @@ +$ref = il y a une erreur avec ''refs'' +additionalItems = l''index ''{0}'' n''est pas dfini dans le schma et le schma n''autorise pas les lments supplmentaires +additionalProperties = la proprit ''{0}'' n''est pas dfinie dans le schma et le schma n''autorise pas de proprits supplmentaires +allOf = doit tre valide pour tous les schmas {0} +anyOf = doit tre valide pour l''un des schmas {0} +const = doit tre la valeur constante ''{0}'' +contains = ne contient aucun lment qui russit ces validations : {1} +contains.max = doit contenir au plus {0} lment(s) qui russissent ces validations : {1} +contains.min = doit contenir au moins {0} lment(s) qui russissent ces validations : {1} +dependencies = il y a une erreur avec les dpendances {0} +dependentRequired = a une proprit manquante {0} qui est dpendante requise car {1} est prsente +dependentSchemas = il y a une erreur avecdependentSchemas {0} +enum = n''a pas de valeur dans l''numration {0} +exclusiveMaximum = doit avoir une valeur maximale exclusive de {0} +exclusiveMinimum = doit avoir une valeur minimale exclusive de {0} +false = le schma de ''{0}'' est faux +format = ne correspond pas au modle {0} +format.date = ne correspond pas au modle {0} doit tre une date complte RFC 3339 valide +format.date-time = ne correspond pas au modle {0} doit tre une date-heure RFC 3339 valide +format.duration = ne correspond pas au modle {0} doit tre une dure ISO 8601 valide +format.email = ne correspond pas au modle {0} doit tre une bote aux lettres RFC 5321 valide +format.ipv4 = ne correspond pas au modle {0} doit tre une adresse IP RFC 2673 valide +format.ipv6 = ne correspond pas au modle {0} doit tre une adresse IP RFC 4291 valide +format.idn-email = ne correspond pas au modle {0} doit tre une bote aux lettres RFC 6531 valide +format.idn-hostname = ne correspond pas au modle {0} doit tre un nom d''hte internationalis RFC 5890 valide +format.iri = ne correspond pas au modle {0} doit tre un IRI RFC 3987 valide +format.iri-reference = ne correspond pas au modle {0} doit tre une rfrence IRI RFC 3987 valide +format.uri = ne correspond pas au modle {0} doit tre un URI RFC 3986 valide +format.uri-reference = ne correspond pas au modle {0} doit tre une rfrence URI RFC 3986 valide +format.uri-template = ne correspond pas au modle {0} doit tre un modle d''URI RFC 6570 valide +format.uuid = ne correspond pas au modle {0} doit tre un UUID RFC 4122 valide +format.regex = ne correspond pas au modle {0} doit tre une expression rgulire ECMA-262 valide +format.time = ne correspond pas au modle {0} doit tre une heure RFC 3339 valide +format.hostname = ne correspond pas au modle {0} doit tre un nom d''hte RFC 1123 valide +format.json-pointer = ne correspond pas au modle {0} doit tre un pointeur JSON RFC 6901 valide +format.relative-json-pointer = ne correspond pas au modle {0} doit tre un pointeur JSON relatif IETF valide +format.unknown = a un format inconnu ''{0}'' +id = ''{0}'' n''est pas un {1} valide +items = l''index ''{0}'' n''est pas dfini dans le schma et le schma n''autorise pas d''lments supplmentaires +maxContains = doit tre un entier non ngatif dans {0} +maxItems = doit contenir au plus {0} lments mais trouv {1} +maxLength = doit contenir au plus {0} caractres +maxProperties = doit avoir au plus {0} proprits +maximum = doit avoir une valeur maximale de {0} +minContains = doit tre un entier non ngatif dans {0} +minContainsVsMaxContains = minContains doit tre infrieur ou gal maxContains dans {0} +minItems = doit avoir au moins {0} lments mais trouv {1} +minLength = doit contenir au moins {0} caractres +minProperties = doit avoir au moins {0} proprits +minimum = doit avoir une valeur minimale de {0} +multipleOf = doit tre un multiple de {0} +not = ne doit pas tre valide pour le schma {0} +notAllowed = la proprit ''{0}'' n''est pas autorise mais elle est dans les donnes +oneOf = doit tre valide pour un et un seul schma, mais {0} sont valides +oneOf.indexes = doit tre valide pour un et un seul schma, mais {0} sont valides avec les index ''{1}'' +pattern = ne correspond pas au modle d''expression rgulire {0} +patternProperties = il y a une erreur avec les proprits du modle +prefixItems = aucun validateur trouv cet index +properties = il y a une erreur avec proprits +propertyNames = le nom de la proprit ''{0}'' n''est pas valide : {1} +readOnly = est un champ en lecture seule, il ne peut pas tre modifi +required = proprit requise ''{0}'' introuvable +type = {0} trouv, {1} attendu +unevaluatedItems = l''index ''{0}'' n''est pas valu et le schma n''autorise pas les lments non valus +unevaluatedProperties = la proprit ''{0}'' n''est pas value et le schma n''autorise pas les proprits non values +unionType = {0} trouv, {1} attendu +uniqueItems = ne doit avoir que des lments uniques dans le tableau +writeOnly = est un champ en criture seule, il ne peut pas apparatre dans les donnes +contentEncoding = ne correspond pas l''encodage du contenu {0} +contentMediaType = n''est pas un contenu moi diff --git a/src/main/resources/jsv-messages_he.properties b/src/main/resources/jsv-messages_he.properties new file mode 100644 index 000000000..4626eced6 --- /dev/null +++ b/src/main/resources/jsv-messages_he.properties @@ -0,0 +1,70 @@ +$ref = \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD ''refs'' +additionalItems = \u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05D2\u05D3\u05E8 \u05D1\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D5\u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05DE\u05D0\u05E4\u05E9\u05E8\u05EA \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05E0\u05D5\u05E1\u05E4\u05D9\u05DD +additionalProperties = \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05D2\u05D3\u05E8 \u05D1\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D5\u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05DE\u05D0\u05E4\u05E9\u05E8\u05EA \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD \u05E0\u05D5\u05E1\u05E4\u05D9\u05DD +allOf = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05DB\u05DC \u05D4\u05E1\u05DB\u05DE\u05D5\u05EA {0} +anyOf = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05DB\u05DC \u05D0\u05D7\u05EA \u05DE\u05D4\u05E1\u05DB\u05D9\u05DE\u05D5\u05EA {0} +const = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D4\u05E2\u05E8\u05DA \u05D4\u05E7\u05D1\u05D5\u05E2 ''{0}'' +contains = \u05D0\u05D9\u05E0\u05D5 \u05DE\u05DB\u05D9\u05DC \u05E8\u05DB\u05D9\u05D1 \u05E9\u05E2\u05D5\u05D1\u05E8 \u05D0\u05EA \u05D4\u05D0\u05D9\u05DE\u05D5\u05EA\u05D9\u05DD \u05D4\u05D1\u05D0\u05D9\u05DD: {1} +contains.max = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05DB\u05D9\u05DC \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {0} \u05E8\u05DB\u05D9\u05D1\u05D9\u05DD \u05E9\u05E2\u05D5\u05D1\u05E8\u05D9\u05DD \u05D0\u05D9\u05DE\u05D5\u05EA\u05D9\u05DD \u05D0\u05DC\u05D4: {1} +contains.min = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05DB\u05D9\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {0} \u05E8\u05DB\u05D9\u05D1\u05D9\u05DD \u05E9\u05E2\u05D5\u05D1\u05E8\u05D9\u05DD \u05D0\u05D9\u05DE\u05D5\u05EA\u05D9\u05DD \u05D0\u05DC\u05D4: {1} +dependencies = \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD \u05EA\u05DC\u05D5\u05D9\u05D5\u05EA {0} +dependentRequired = \u05D7\u05E1\u05E8 \u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{0}'' \u05D0\u05E9\u05E8 \u05D3\u05E8\u05D5\u05E9 \u05EA\u05DC\u05D5\u05D9 \u05DB\u05D9 ''{1}'' \u05E7\u05D9\u05D9\u05DD +dependentSchemas = \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD dependentSchemas {0} +enum = \u05D0\u05D9\u05DF \u05E2\u05E8\u05DA \u05D1\u05E1\u05E4\u05D9\u05E8\u05D4 {0} +exclusiveMaximum = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05E7\u05E1\u05D9\u05DE\u05DC\u05D9 \u05D1\u05DC\u05E2\u05D3\u05D9 \u05E9\u05DC {0} +exclusiveMinimum = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05D9\u05E0\u05D9\u05DE\u05DC\u05D9 \u05D1\u05DC\u05E2\u05D3\u05D9 \u05E9\u05DC {0} +false = \u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05E2\u05D1\u05D5\u05E8 ''{0}'' \u05D4\u05D9\u05D0 \u05E9\u05E7\u05E8 +format = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} +format.date = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05EA\u05D0\u05E8\u05D9\u05DA \u05DE\u05DC\u05D0 RFC 3339 \u05D7\u05D5\u05E7\u05D9 +format.date-time = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 3339 \u05EA\u05D0\u05E8\u05D9\u05DA-\u05E9\u05E2\u05D4 \u05D7\u05D5\u05E7\u05D9 +format.duration = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E9\u05DA ISO 8601 \u05D7\u05D5\u05E7\u05D9 +format.email = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05EA\u05D9\u05D1\u05EA \u05D3\u05D5\u05D0\u05E8 RFC 5321 \u05D7\u05D5\u05E7\u05D9\u05EA +format.ipv4 = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05DB\u05EA\u05D5\u05D1\u05EA IP \u05D7\u05D5\u05E7\u05D9\u05EA \u05E9\u05DC RFC 2673 +format.ipv6 = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05DB\u05EA\u05D5\u05D1\u05EA IP \u05D7\u05D5\u05E7\u05D9\u05EA \u05E9\u05DC RFC 4291 +format.idn-email = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05D3\u05E4\u05D5\u05E1 {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05EA\u05D9\u05D1\u05EA \u05D3\u05D5\u05D0\u05E8 RFC 6531 \u05D7\u05D5\u05E7\u05D9\u05EA +format.idn-hostname = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05E9\u05DD \u05DE\u05D0\u05E8\u05D7 \u05D7\u05D5\u05E7\u05D9 RFC 5890 \u05D1\u05D9\u05E0\u05DC\u05D0\u05D5\u05DE\u05D9 +format.iri = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 3987 IRI \u05D7\u05D5\u05E7\u05D9 +format.iri-reference = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05D4\u05E4\u05E0\u05D9\u05D4 \u05D7\u05D5\u05E7\u05D9\u05EA \u05E9\u05DC RFC 3987 IRI +format.uri = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 3986 URI \u05D7\u05D5\u05E7\u05D9 +format.uri-reference = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05D4\u05E4\u05E0\u05D9\u05D4 \u05D7\u05D5\u05E7\u05D9\u05EA \u05E9\u05DC RFC 3986 URI +format.uri-template = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1\u05EA \u05DC\u05D4\u05D9\u05D5\u05EA \u05EA\u05D1\u05E0\u05D9\u05EA URI \u05D7\u05D5\u05E7\u05D9\u05EA \u05E9\u05DC RFC 6570 +format.uuid = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 4122 UUID \u05D7\u05D5\u05E7\u05D9 +format.regex = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05D9\u05D8\u05D5\u05D9 \u05E8\u05D2\u05D5\u05DC\u05E8\u05D9 \u05D7\u05D5\u05E7\u05D9 ECMA-262 +format.time = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D6\u05DE\u05DF RFC 3339 \u05D7\u05D5\u05E7\u05D9 +format.hostname = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05E9\u05DD \u05DE\u05D0\u05E8\u05D7 RFC 1123 \u05D7\u05D5\u05E7\u05D9 +format.json-pointer = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E6\u05D1\u05D9\u05E2 RFC 6901 JSON \u05D7\u05D5\u05E7\u05D9 +format.relative-json-pointer = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {0} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E6\u05D1\u05D9\u05E2 JSON \u05D9\u05D7\u05E1\u05D9 \u05E9\u05DC IETF \u05D7\u05D5\u05E7\u05D9 +format.unknown = \u05D9\u05E9 \u05E4\u05D5\u05E8\u05DE\u05D8 \u05DC\u05D0 \u05D9\u05D3\u05D5\u05E2 ''{0}'' +id = ''{0}'' \u05D0\u05D9\u05E0\u05D5 {1} \u05D7\u05D5\u05E7\u05D9 +items = \u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05D2\u05D3\u05E8 \u05D1\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D5\u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05DE\u05D0\u05E4\u05E9\u05E8\u05EA \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05E0\u05D5\u05E1\u05E4\u05D9\u05DD +maxContains = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E1\u05E4\u05E8 \u05E9\u05DC\u05DD \u05DC\u05D0 \u05E9\u05DC\u05D9\u05DC\u05D9 \u05D1-{0} +maxItems = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05DB\u05DC\u05D5\u05DC \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {0} \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05D0\u05DA \u05E0\u05DE\u05E6\u05D0\u05D5 {1} +maxLength = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05D0\u05D5\u05E8\u05DA \u05E9\u05DC {0} \u05EA\u05D5\u05D5\u05D9\u05DD \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 +maxProperties = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {0} \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD +maximum = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05E7\u05E1\u05D9\u05DE\u05DC\u05D9 \u05E9\u05DC {0} +minContains = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E1\u05E4\u05E8 \u05E9\u05DC\u05DD \u05DC\u05D0 \u05E9\u05DC\u05D9\u05DC\u05D9 \u05D1-{0} +minContainsVsMaxContains = minContains \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05E7\u05D8\u05DF \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-maxContains \u05D1-{0} +minItems = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05DB\u05DC\u05D5\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {0} \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05D0\u05DA \u05E0\u05DE\u05E6\u05D0\u05D5 {1} +minLength = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05D0\u05D5\u05E8\u05DA \u05E9\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {0} \u05EA\u05D5\u05D5\u05D9\u05DD +minProperties = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05E4\u05D7\u05D5\u05EA {0} \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD +minimum = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05D9\u05E0\u05D9\u05DE\u05DC\u05D9 \u05E9\u05DC {0} +multipleOf = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DB\u05E4\u05D5\u05DC\u05D4 \u05E9\u05DC {0} +not = \u05DC\u05D0 \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05E1\u05DB\u05D9\u05DE\u05D4 {0} +notAllowed = \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05EA\u05E8 \u05D0\u05DA \u05D4\u05D5\u05D0 \u05E0\u05DE\u05E6\u05D0 \u05D1\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD +oneOf = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D7\u05EA \u05D5\u05D9\u05D7\u05D9\u05D3\u05D4, \u05D0\u05D1\u05DC {0} \u05D7\u05D5\u05E7\u05D9\u05D9\u05DD +oneOf.indexes = \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D7\u05EA \u05D5\u05D9\u05D7\u05D9\u05D3\u05D4, \u05D0\u05D1\u05DC {0} \u05EA\u05E7\u05E4\u05D9\u05DD \u05E2\u05DD \u05D4\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1\u05D9\u05DD ''{1}'' +pattern = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA \u05D4\u05D1\u05D9\u05D8\u05D5\u05D9 \u05D4\u05E8\u05D2\u05D5\u05DC\u05E8\u05D9 {0} +patternProperties = \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05DB\u05DC\u05E9\u05D4\u05D9 \u05E2\u05DD ''\u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9 \u05D3\u05E4\u05D5\u05E1'' +prefixItems = \u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0 \u05DE\u05D0\u05DE\u05EA \u05D1\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 \u05D6\u05D4 +properties = \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD ''\u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD'' +propertyNames = \u05E9\u05DD \u05D4\u05E0\u05DB\u05E1 ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05D7\u05D5\u05E7\u05D9: {1} +readOnly = \u05D4\u05D5\u05D0 \u05E9\u05D3\u05D4 \u05DC\u05E7\u05E8\u05D9\u05D0\u05D4 \u05D1\u05DC\u05D1\u05D3, \u05DC\u05D0 \u05E0\u05D9\u05EA\u05DF \u05DC\u05E9\u05E0\u05D5\u05EA \u05D0\u05D5\u05EA\u05D5 +required = \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF \u05D4\u05E0\u05D3\u05E8\u05E9 ''{0}'' \u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0 +type = {0} \u05E0\u05DE\u05E6\u05D0, {1} \u05E6\u05E4\u05D5\u05D9 +unevaluatedItems = \u05D4\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05E2\u05E8\u05DA \u05D5\u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05DE\u05D0\u05E4\u05E9\u05E8\u05EA \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05DC\u05DC\u05D0 \u05D4\u05E2\u05E8\u05DB\u05D4 +unevaluatedProperties = \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{0}'' \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05E2\u05E8\u05DA \u05D5\u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05DE\u05D0\u05E4\u05E9\u05E8\u05EA \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD \u05DC\u05DC\u05D0 \u05D4\u05E2\u05E8\u05DB\u05D4 +unionType = {0} \u05E0\u05DE\u05E6\u05D0, {1} \u05E6\u05E4\u05D5\u05D9 +uniqueItems = \u05D7\u05D9\u05D9\u05D1\u05D9\u05DD \u05DC\u05DB\u05DC\u05D5\u05DC \u05E8\u05E7 \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05D9\u05D9\u05D7\u05D5\u05D3\u05D9\u05D9\u05DD \u05D1\u05DE\u05E2\u05E8\u05DA +writeOnly = \u05D4\u05D5\u05D0 \u05E9\u05D3\u05D4 \u05DC\u05DB\u05EA\u05D9\u05D1\u05D4 \u05D1\u05DC\u05D1\u05D3, \u05D4\u05D5\u05D0 \u05DC\u05D0 \u05D9\u05DB\u05D5\u05DC \u05DC\u05D4\u05D5\u05E4\u05D9\u05E2 \u05D1\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD +contentEncoding = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05D0\u05EA \u05E7\u05D9\u05D3\u05D5\u05D3 \u05D4\u05EA\u05D5\u05DB\u05DF {0} +contentMediaType = \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05DB\u05DF \u05D0\u05E0\u05D9 diff --git a/src/main/resources/jsv-messages_hr.properties b/src/main/resources/jsv-messages_hr.properties new file mode 100644 index 000000000..6b2060ef3 --- /dev/null +++ b/src/main/resources/jsv-messages_hr.properties @@ -0,0 +1,70 @@ +$ref = ima gre\u0161ku s ''refs'' +additionalItems = indeks ''{0}'' nije definiran u shemi i shema ne dopu\u0161ta dodatne stavke +additionalProperties = svojstvo ''{0}'' nije definirano u shemi i shema ne dopu\u0161ta dodatna svojstva +allOf = mora biti va\u017Ee\u0107e za sve sheme {0} +anyOf = mora biti va\u017Ee\u0107e za bilo koju od shema {0} +const = mora biti konstantna vrijednost ''{0}'' +contains = ne sadr\u017Ei element koji prolazi ove provjere: {1} +contains.max = mora sadr\u017Eavati najvi\u0161e {0} elemenata koji prolaze ove provjere: {1} +contains.min = mora sadr\u017Eavati najmanje {0} elementa koji prolaze ove provjere: {1} +dependencies = ima pogre\u0161ku s ovisnostima {0} +dependentRequired = nedostaje svojstvo ''{0}'' koje je ovisno potrebno jer je prisutan ''{1}'' +dependentSchemas = ima pogre\u0161ku s dependentSchemas {0} +enum = nema vrijednost u enumeraciji {0} +exclusiveMaximum = mora imati isklju\u010Divu maksimalnu vrijednost od {0} +exclusiveMinimum = mora imati isklju\u010Divu minimalnu vrijednost od {0} +false = shema za ''{0}'' je false +format = ne odgovara {0} uzorku +format.date = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i puni datum RFC 3339 +format.date-time = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 3339 datum-vrijeme +format.duration = ne odgovara uzorku {0} mora biti va\u017Ee\u0107e ISO 8601 trajanje +format.email = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i po\u0161tanski sandu\u010Di\u0107 RFC 5321 +format.ipv4 = ne odgovara uzorku {0} mora biti va\u017Ee\u0107a RFC 2673 IP adresa +format.ipv6 = ne odgovara uzorku {0} mora biti va\u017Ee\u0107a RFC 4291 IP adresa +format.idn-email = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i po\u0161tanski sandu\u010Di\u0107 RFC 6531 +format.idn-hostname = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 5890 internacionalizirani naziv glavnog ra\u010Dunala +format.iri = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 3987 IRI +format.iri-reference = ne odgovara uzorku {0} mora biti va\u017Ee\u0107a RFC 3987 IRI referenca +format.uri = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 3986 URI +format.uri-reference = ne odgovara uzorku {0} mora biti va\u017Ee\u0107a RFC 3986 URI referenca +format.uri-template = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 6570 URI predlo\u017Eak +format.uuid = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 4122 UUID +format.regex = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i regularni izraz ECMA-262 +format.time = ne odgovara uzorku {0} mora biti va\u017Ee\u0107e RFC 3339 vrijeme +format.hostname = ne odgovara uzorku {0} mora biti va\u017Ee\u0107e RFC 1123 ime glavnog ra\u010Dunala +format.json-pointer = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i RFC 6901 JSON pokaziva\u010D +format.relative-json-pointer = ne odgovara uzorku {0} mora biti va\u017Ee\u0107i IETF relativni JSON pokaziva\u010D +format.unknown = ima nepoznati format ''{0}'' +id = ''{0}'' nije valjan {1} +items = indeks ''{0}'' nije definiran u shemi i shema ne dopu\u0161ta dodatne stavke +maxContains = mora biti nenegativan cijeli broj u {0} +maxItems = mora imati najvi\u0161e {0} stavki, ali prona\u0111eno {1} +maxLength = mora imati najvi\u0161e {0} znakova +maxProperties = mora imati najvi\u0161e {0} svojstava +maximum = mora imati najve\u0107u vrijednost od {0} +minContains = mora biti nenegativan cijeli broj u {0} +minContainsVsMaxContains = minContains mora biti manji od ili jednak maxContains u {0} +minItems = mora imati najmanje {0} stavki, ali prona\u0111eno {1} +minLength = mora imati najmanje {0} znakova +minProperties = mora imati najmanje {0} svojstava +minimum = mora imati minimalnu vrijednost od {0} +multipleOf = mora biti vi\u0161estruko od {0} +not = ne smije biti va\u017Ee\u0107e za shemu {0} +notAllowed = svojstvo ''{0}'' nije dopu\u0161teno, ali je u podacima +oneOf = mora biti va\u017Ee\u0107e za jednu i samo jednu shemu, ali {0} su va\u017Ee\u0107e +oneOf.indexes = mora biti va\u017Ee\u0107e za jednu i samo jednu shemu, ali {0} su va\u017Ee\u0107e s indeksima ''{1}'' +pattern = ne odgovara uzorku regularnog izraza {0} +patternProperties = ima neke gre\u0161ke sa ''svojstvima uzorka'' +prefixItems = validator nije prona\u0111en u ovom indeksu +properties = ima gre\u0161ku sa ''svojstvima'' +propertyNames = ime svojstva ''{0}'' nije va\u017Ee\u0107e: {1} +readOnly = polje je samo za \u010Ditanje, ne mo\u017Ee se mijenjati +required = potrebno svojstvo ''{0}'' nije prona\u0111eno +type = {0} prona\u0111eno, {1} o\u010Dekivano +unevaluatedItems = indeks ''{0}'' se ne procjenjuje i shema ne dopu\u0161ta neprocijenjene stavke +unevaluatedProperties = svojstvo ''{0}'' se ne procjenjuje i shema ne dopu\u0161ta neprocijenjena svojstva +unionType = {0} prona\u0111eno, {1} o\u010Dekivano +uniqueItems = mora imati samo jedinstvene stavke u nizu +writeOnly = polje je samo za pisanje, ne mo\u017Ee se pojaviti u podacima +contentEncoding = ne odgovara kodiranju sadr\u017Eaja {0} +contentMediaType = nije ja sadr\u017Eaj diff --git a/src/main/resources/jsv-messages_hu.properties b/src/main/resources/jsv-messages_hu.properties new file mode 100644 index 000000000..2902a37d9 --- /dev/null +++ b/src/main/resources/jsv-messages_hu.properties @@ -0,0 +1,70 @@ +$ref = hibs a ''refs'' +additionalItems = a ''{0}'' index nincs megadva a smban, s a sma nem engedlyez tovbbi elemeket +additionalProperties = a ''{0}'' tulajdonsg nincs megadva a smban, s a sma nem engedlyez tovbbi tulajdonsgokat +allOf = rvnyesnek kell lennie az sszes smra {0} +anyOf = rvnyesnek kell lennie a(z) {0} smk brmelyikre +const = a ''{0}'' lland rtknek kell lennie +contains = nem tartalmaz olyan elemet, amely tmegy a kvetkez\u0151 ellen\u0151rzseken: {1} +contains.max = legfeljebb {0} olyan elemet tartalmazhat, amely megfelel a kvetkez\u0151 ellen\u0151rzseknek: {1} +contains.min = tartalmaznia kell legalbb {0} olyan elemet, amely tmegy a kvetkez\u0151 ellen\u0151rzseken: {1} +dependencies = hiba van a(z) {0} fgg\u0151sgekkel +dependentRequired = hinyzik a(z) ''{0}'' tulajdonsga, amely szksges, mert a ''{1}'' jelen van +dependentSchemas = hibs a dependentSchemas {0} +enum = nincs rtke a(z) {0} felsorolsban +exclusiveMaximum = kizrlagos maximlis rtke {0} +exclusiveMinimum = kizrlagos minimlis rtke {0} +false = ''{0}'' sma hamis +format = nem egyezik a {0} mintval +format.date = nem egyezik a(z) {0} mintval, rvnyes RFC 3339 teljes dtumnak kell lennie +format.date-time = nem egyezik a {0} mintval, rvnyes RFC 3339 dtum-id\u0151 +format.duration = nem egyezik a {0} mintval, rvnyes ISO 8601 id\u0151tartamnak kell lennie +format.email = nem egyezik a {0} mintval, rvnyes RFC 5321-es postafiknak kell lennie +format.ipv4 = nem egyezik a(z) {0} mintval, rvnyes RFC 2673 IP-cmnek kell lennie +format.ipv6 = nem egyezik a {0} mintval, rvnyes RFC 4291 IP-cmnek kell lennie +format.idn-email = nem egyezik a {0} mintval, rvnyes RFC 6531-es postafiknak kell lennie +format.idn-hostname = nem egyezik a(z) {0} mintval, rvnyes RFC 5890 nemzetkziestett gazdagpnvnek kell lennie +format.iri = nem egyezik a {0} mintval, rvnyes RFC 3987 IRI-nek kell lennie +format.iri-reference = nem egyezik a {0} mintval, rvnyes RFC 3987 IRI-hivatkozsnak kell lennie +format.uri = nem egyezik a {0} mintval, rvnyes RFC 3986 URI-nek kell lennie +format.uri-reference = nem egyezik a {0} mintval, rvnyes RFC 3986 URI-hivatkozsnak kell lennie +format.uri-template = nem egyezik a {0} mintval, rvnyes RFC 6570 URI-sablonnak kell lennie +format.uuid = nem egyezik a {0} mintval, rvnyes RFC 4122 UUID-nek kell lennie +format.regex = nem egyezik a {0} mintval, rvnyes ECMA-262 regulris kifejezsnek kell lennie +format.time = nem egyezik a {0} mintval, rvnyes RFC 3339 id\u0151nek kell lennie +format.hostname = nem egyezik a(z) {0} mintval, rvnyes RFC 1123 gazdagpnvnek kell lennie +format.json-pointer = nem egyezik a {0} mintval, rvnyes RFC 6901 JSON-mutatnak kell lennie +format.relative-json-pointer = nem egyezik a {0} mintval, rvnyes IETF relatv JSON-mutatnak kell lennie +format.unknown = ismeretlen formtuma: ''{0}'' +id = a ''{0}'' nem rvnyes {1} +items = a ''{0}'' index nincs megadva a smban, s a sma nem engedlyez tovbbi elemeket +maxContains = egy nem negatv egsz szmnak kell lennie a kvetkez\u0151ben: {0} +maxItems = legfeljebb {0} elemet tartalmazhat, de {1} tallhat +maxLength = legfeljebb {0} karakter hosszsg lehet +maxProperties = legfeljebb {0} tulajdonsggal kell rendelkeznie +maximum = maximum {0} rtkkel kell rendelkeznie +minContains = egy nem negatv egsz szmnak kell lennie a kvetkez\u0151ben: {0} +minContainsVsMaxContains = A minContains rtknek kisebbnek vagy egyenl\u0151nek kell lennie, mint a maxContains a kvetkez\u0151ben: {0} +minItems = legalbb {0} elemnek kell lennie, de {1} tallhat +minLength = legalbb {0} karakter hosszsgnak kell lennie +minProperties = legalbb {0} tulajdonsggal kell rendelkeznie +minimum = legalbb {0} rtkkel kell rendelkeznie +multipleOf = {0} tbbszrsnek kell lennie +not = nem rvnyes a(z) {0} smra +notAllowed = a ''{0}'' tulajdonsg nem engedlyezett, de benne van az adatokban +oneOf = rvnyesnek kell lennie egy s csak egy smra, de a {0} rvnyes +oneOf.indexes = rvnyesnek kell lennie egy s csak egy smra, de a {0} rvnyes a kvetkez\u0151 indexekkel: ''{1}'' +pattern = nem egyezik a kvetkez\u0151 regex mintval: {0} +patternProperties = hibs a "minta tulajdonsgai" +prefixItems = ezen az indexen nem tallhat rvnyest\u0151 +properties = hibs a "tulajdonsgok" +propertyNames = a(z) ''{0}'' tulajdonsg neve rvnytelen: {1} +readOnly = csak olvashat mez\u0151, nem mdosthat +required = a(z) ''{0}'' ktelez\u0151 tulajdonsg nem tallhat +type = {0} tallhat, {1} vrhat +unevaluatedItems = a ''{0}'' index nincs kirtkelve, s a sma nem engedlyezi az rtkeletlen elemeket +unevaluatedProperties = a(z) ''{0}'' tulajdonsg nincs kirtkelve, s a sma nem engedlyezi az rtkeletlen tulajdonsgokat +unionType = {0} tallhat, {1} vrhat +uniqueItems = csak egyedi elemek lehetnek a tmbben +writeOnly = csak rhat mez\u0151, nem jelenhet meg az adatokban +contentEncoding = nem egyezik a kvetkez\u0151 tartalomkdolssal: {0} +contentMediaType = nem egy tartalom n diff --git a/src/main/resources/jsv-messages_it.properties b/src/main/resources/jsv-messages_it.properties new file mode 100644 index 000000000..e5738ffbc --- /dev/null +++ b/src/main/resources/jsv-messages_it.properties @@ -0,0 +1,70 @@ +$ref = ha un errore con ''refs'' +additionalItems = l''indice ''{0}'' non definito nello schema e lo schema non consente elementi aggiuntivi +additionalProperties = la propriet ''{0}'' non definita nello schema e lo schema non consente propriet aggiuntive +allOf = deve essere valido per tutti gli schemi {0} +anyOf = deve essere valido per uno qualsiasi degli schemi {0} +const = deve essere il valore costante ''{0}'' +contains = non contiene un elemento che supera queste convalide: {1} +contains.max = deve contenere al massimo {0} elemento/i che supera queste convalide: {1} +contains.min = deve contenere almeno {0} elemento/i che supera queste convalide: {1} +dependencies = presenta un errore con le dipendenze {0} +dependentRequired = ha una propriet mancante ''{0}'' che dipendente obbligatoria perch ''{1}'' presente +dependentSchemas = ha un errore con dipendentiSchemas {0} +enum = non ha un valore nell''enumerazione {0} +exclusiveMaximum = deve avere un valore massimo esclusivo di {0} +exclusiveMinimum = deve avere un valore minimo esclusivo di {0} +false = lo schema per ''{0}'' falso +format = non corrisponde al modello {0} +format.date = non corrisponde al modello {0} deve essere una data completa RFC 3339 valida +format.date-time = non corrisponde al modello {0} deve essere una data-ora RFC 3339 valida +format.duration = non corrisponde al modello {0} deve essere una durata ISO 8601 valida +format.email = non corrisponde al modello {0} deve essere una casella di posta RFC 5321 valida +format.ipv4 = non corrisponde al modello {0} deve essere un indirizzo IP RFC 2673 valido +format.ipv6 = non corrisponde al modello {0} deve essere un indirizzo IP RFC 4291 valido +format.idn-email = non corrisponde al modello {0} deve essere una casella di posta RFC 6531 valida +format.idn-hostname = non corrisponde al modello {0} deve essere un nome host internazionalizzato RFC 5890 valido +format.iri = non corrisponde al modello {0} deve essere un IRI RFC 3987 valido +format.iri-reference = non corrisponde al modello {0} deve essere un riferimento IRI RFC 3987 valido +format.uri = non corrisponde al modello {0} deve essere un URI RFC 3986 valido +format.uri-reference = non corrisponde al modello {0} deve essere un riferimento URI RFC 3986 valido +format.uri-template = non corrisponde al modello {0} deve essere un modello URI RFC 6570 valido +format.uuid = non corrisponde al modello {0} deve essere un UUID RFC 4122 valido +format.regex = non corrisponde al modello {0} deve essere un''espressione regolare ECMA-262 valida +format.time = non corrisponde al modello {0} deve essere un''ora RFC 3339 valida +format.hostname = non corrisponde al modello {0} deve essere un nome host RFC 1123 valido +format.json-pointer = non corrisponde al modello {0} deve essere un puntatore JSON RFC 6901 valido +format.relative-json-pointer = non corrisponde al modello {0} deve essere un puntatore JSON relativo IETF valido +format.unknown = ha un formato sconosciuto ''{0}'' +id = ''{0}'' non un {1} valido +items = l''indice ''{0}'' non definito nello schema e lo schema non consente elementi aggiuntivi +maxContains = deve essere un numero intero non negativo in {0} +maxItems = deve avere al massimo {0} elementi ma trovati {1} +maxLength = deve contenere al massimo {0} caratteri +maxProperties = deve avere al massimo {0} propriet +maximum = deve avere un valore massimo di {0} +minContains = deve essere un numero intero non negativo in {0} +minContainsVsMaxContains = minContains deve essere minore o uguale a maxContains in {0} +minItems = deve avere almeno {0} elementi ma trovati {1} +minLength = deve contenere almeno {0} caratteri +minProperties = deve avere almeno {0} propriet +minimum = deve avere un valore minimo di {0} +multipleOf = deve essere multiplo di {0} +not = non deve essere valido per lo schema {0} +notAllowed = la propriet ''{0}'' non consentita ma nei dati +oneOf = deve essere valido per uno e un solo schema, ma {0} sono validi +oneOf.indexes = deve essere valido per uno e un solo schema, ma {0} sono validi con gli indici ''{1}'' +pattern = non corrisponde al pattern regex {0} +patternProperties = presenta qualche errore con le ''propriet del modello'' +prefixItems = nessun validatore trovato in questo indice +properties = presenta un errore con ''propriet'' +propertyNames = il nome della propriet ''{0}'' non valido: {1} +readOnly = un campo di sola lettura, non pu essere modificato +required = propriet richiesta ''{0}'' non trovata +type = {0} trovato, {1} previsto +unevaluatedItems = l''indice ''{0}'' non viene valutato e lo schema non consente elementi non valutati +unevaluatedProperties = la propriet ''{0}'' non viene valutata e lo schema non consente propriet non valutate +unionType = {0} trovato, {1} previsto +uniqueItems = deve contenere solo elementi univoci nell''array +writeOnly = un campo di sola scrittura, non pu apparire nei dati +contentEncoding = non corrisponde alla codifica del contenuto {0} +contentMediaType = non un contenuto me diff --git a/src/main/resources/jsv-messages_ja.properties b/src/main/resources/jsv-messages_ja.properties new file mode 100644 index 000000000..cb2a3259b --- /dev/null +++ b/src/main/resources/jsv-messages_ja.properties @@ -0,0 +1,70 @@ +$ref = ''refs'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059 +additionalItems = \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{0}'' \u304C\u30B9\u30AD\u30FC\u30DE\u3067\u5B9A\u7FA9\u3055\u308C\u3066\u304A\u3089\u305A\u3001\u30B9\u30AD\u30FC\u30DE\u3067\u306F\u8FFD\u52A0\u306E\u9805\u76EE\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093 +additionalProperties = \u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u304C\u30B9\u30AD\u30FC\u30DE\u3067\u5B9A\u7FA9\u3055\u308C\u3066\u304A\u3089\u305A\u3001\u30B9\u30AD\u30FC\u30DE\u3067\u306F\u8FFD\u52A0\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093 +allOf = \u3059\u3079\u3066\u306E\u30B9\u30AD\u30FC\u30DE {0} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +anyOf = \u3044\u305A\u308C\u304B\u306E\u30B9\u30AD\u30FC\u30DE {0} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +const = \u5B9A\u6570\u5024 ''{0}'' \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +contains = \u6B21\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u8981\u7D20\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093: {1} +contains.max = \u3053\u308C\u3089\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u8981\u7D20\u306F\u6700\u5927\u3067\u3082 {0} \u500B\u542B\u307E\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {1} +contains.min = \u3053\u308C\u3089\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u5C11\u306A\u304F\u3068\u3082 {0} \u500B\u306E\u8981\u7D20\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {1} +dependencies = \u4F9D\u5B58\u95A2\u4FC2 {0} \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059 +dependentRequired = \u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u304C\u3042\u308A\u307E\u305B\u3093\u3002''{1}'' \u304C\u5B58\u5728\u3059\u308B\u305F\u3081\u3001\u4F9D\u5B58\u95A2\u4FC2\u304C\u5FC5\u8981\u3067\u3059\u3002 +dependentSchemas = {0}:dependentSchemas {0} \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059 +enum = \u5217\u6319\u578B {0} \u306B\u5024\u304C\u3042\u308A\u307E\u305B\u3093 +exclusiveMaximum = \u6392\u4ED6\u7684\u306A\u6700\u5927\u5024\u306F {0} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +exclusiveMinimum = \u6392\u4ED6\u7684\u306A\u6700\u5C0F\u5024\u306F {0} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +false = ''{0}'' \u306E\u30B9\u30AD\u30FC\u30DE\u306F false \u3067\u3059 +format = {0} \u30D1\u30BF\u30FC\u30F3 \u306B\u4E00\u81F4\u3057\u307E\u305B\u3093 +format.date = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3339 \u306E\u5B8C\u5168\u306A\u65E5\u4ED8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.date-time = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3339 \u65E5\u4ED8/\u6642\u523B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.duration = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A ISO 8601 \u671F\u9593\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.email = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 5321 \u30E1\u30FC\u30EB\u30DC\u30C3\u30AF\u30B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.ipv4 = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 2673 IP \u30A2\u30C9\u30EC\u30B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.ipv6 = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 4291 IP \u30A2\u30C9\u30EC\u30B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.idn-email = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 6531 \u30E1\u30FC\u30EB\u30DC\u30C3\u30AF\u30B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.idn-hostname = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 5890 \u56FD\u969B\u5316\u30DB\u30B9\u30C8\u540D\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.iri = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3987 IRI \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.iri-reference = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3987 IRI \u53C2\u7167\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.uri = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3986 URI \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.uri-reference = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3986 URI \u53C2\u7167\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.uri-template = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 6570 URI \u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.uuid = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 4122 UUID \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.regex = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A ECMA-262 \u6B63\u898F\u8868\u73FE\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.time = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 3339 \u6642\u523B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.hostname = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 1123 \u30DB\u30B9\u30C8\u540D\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.json-pointer = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A RFC 6901 JSON \u30DD\u30A4\u30F3\u30BF\u30FC\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.relative-json-pointer = {0} \u30D1\u30BF\u30FC\u30F3\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6709\u52B9\u306A IETF \u76F8\u5BFE JSON \u30DD\u30A4\u30F3\u30BF\u30FC\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +format.unknown = \u4E0D\u660E\u306A\u5F62\u5F0F ''{0}'' \u304C\u3042\u308A\u307E\u3059 +id = ''{0}'' \u306F\u6709\u52B9\u306A {1} \u3067\u306F\u3042\u308A\u307E\u305B\u3093 +items = \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{0}'' \u304C\u30B9\u30AD\u30FC\u30DE\u3067\u5B9A\u7FA9\u3055\u308C\u3066\u304A\u3089\u305A\u3001\u30B9\u30AD\u30FC\u30DE\u3067\u306F\u8FFD\u52A0\u306E\u9805\u76EE\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093 +maxContains = {0} \u306B\u306F\u8CA0\u3067\u306A\u3044\u6574\u6570\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +maxItems = \u30A2\u30A4\u30C6\u30E0\u306F\u6700\u5927\u3067\u3082 {0} \u500B\u5FC5\u8981\u3067\u3059\u304C\u3001{1} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F +maxLength = \u9577\u3055\u306F\u6700\u5927 {0} \u6587\u5B57\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +maxProperties = \u6700\u5927\u3067 {0} \u500B\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u304C\u5FC5\u8981\u3067\u3059 +maximum = \u6700\u5927\u5024\u306F {0} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +minContains = {0} \u306B\u306F\u8CA0\u3067\u306A\u3044\u6574\u6570\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +minContainsVsMaxContains = minContains \u306F {0} \u306E maxContains \u4EE5\u4E0B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +minItems = \u5C11\u306A\u304F\u3068\u3082 {0} \u500B\u306E\u9805\u76EE\u304C\u5FC5\u8981\u3067\u3059\u304C\u3001{1} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F +minLength = \u5C11\u306A\u304F\u3068\u3082 {0} \u6587\u5B57\u306E\u9577\u3055\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +minProperties = \u5C11\u306A\u304F\u3068\u3082 {0} \u500B\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u304C\u5FC5\u8981\u3067\u3059 +minimum = \u6700\u5C0F\u5024\u306F {0} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 +multipleOf = {0} \u306E\u500D\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +not = \u30B9\u30AD\u30FC\u30DE {0} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u3063\u3066\u306F\u306A\u308A\u307E\u305B\u3093 +notAllowed = \u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u304C\u3001\u30C7\u30FC\u30BF\u5185\u306B\u3042\u308A\u307E\u3059 +oneOf = 1 \u3064\u306E\u30B9\u30AD\u30FC\u30DE\u306B\u5BFE\u3057\u3066\u306E\u307F\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u304C\u3001{0} \u306F\u6709\u52B9\u3067\u3059 +oneOf.indexes = 1 \u3064\u306E\u30B9\u30AD\u30FC\u30DE\u306B\u5BFE\u3057\u3066\u306E\u307F\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u304C\u3001{0} \u306F\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{1}'' \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3059 +pattern = \u6B63\u898F\u8868\u73FE\u30D1\u30BF\u30FC\u30F3 {0} \u306B\u4E00\u81F4\u3057\u307E\u305B\u3093 +patternProperties = ''\u30D1\u30BF\u30FC\u30F3 \u30D7\u30ED\u30D1\u30C6\u30A3'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059 +prefixItems = \u3053\u306E\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3067\u30D0\u30EA\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 +properties = ''\u30D7\u30ED\u30D1\u30C6\u30A3'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059 +propertyNames = \u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u306E\u540D\u524D\u304C\u7121\u52B9\u3067\u3059: {1} +readOnly = \u306F\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u3059\u3002\u5909\u66F4\u3067\u304D\u307E\u305B\u3093 +required = \u5FC5\u9808\u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 +type = {0} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3001{1} \u304C\u4E88\u671F\u3055\u308C\u307E\u3057\u305F +unevaluatedItems = \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{0}'' \u306F\u8A55\u4FA1\u3055\u308C\u3066\u304A\u3089\u305A\u3001\u30B9\u30AD\u30FC\u30DE\u306F\u672A\u8A55\u4FA1\u306E\u9805\u76EE\u3092\u8A31\u53EF\u3057\u3066\u3044\u307E\u305B\u3093 +unevaluatedProperties = \u30D7\u30ED\u30D1\u30C6\u30A3 ''{0}'' \u306F\u8A55\u4FA1\u3055\u308C\u3066\u304A\u3089\u305A\u3001\u30B9\u30AD\u30FC\u30DE\u306F\u672A\u8A55\u4FA1\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u8A31\u53EF\u3057\u3066\u3044\u307E\u305B\u3093 +unionType = {0} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3001{1} \u304C\u4E88\u671F\u3055\u308C\u307E\u3057\u305F +uniqueItems = \u914D\u5217\u306B\u306F\u4E00\u610F\u306E\u9805\u76EE\u306E\u307F\u3092\u542B\u3081\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 +writeOnly = \u306F\u66F8\u304D\u8FBC\u307F\u5C02\u7528\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u3059\u3002\u30C7\u30FC\u30BF\u306B\u306F\u8868\u793A\u3067\u304D\u307E\u305B\u3093\u3002 +contentEncoding = \u30B3\u30F3\u30C6\u30F3\u30C4 \u30A8\u30F3\u30B3\u30FC\u30C7\u30A3\u30F3\u30B0 {0} \u3068\u4E00\u81F4\u3057\u307E\u305B\u3093 +contentMediaType = \u30B3\u30F3\u30C6\u30F3\u30C4\u3067\u306F\u3042\u308A\u307E\u305B\u3093 diff --git a/src/main/resources/jsv-messages_ko.properties b/src/main/resources/jsv-messages_ko.properties new file mode 100644 index 000000000..9a61c08aa --- /dev/null +++ b/src/main/resources/jsv-messages_ko.properties @@ -0,0 +1,70 @@ +$ref = ''refs''\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4. +additionalItems = \uC0C9\uC778 ''{0}''\uC774(\uAC00) \uC2A4\uD0A4\uB9C8\uC5D0 \uC815\uC758\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC73C\uBA70 \uC2A4\uD0A4\uB9C8\uAC00 \uCD94\uAC00 \uD56D\uBAA9\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +additionalProperties = ''{0}'' \uC18D\uC131\uC774 \uC2A4\uD0A4\uB9C8\uC5D0 \uC815\uC758\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC73C\uBA70 \uC2A4\uD0A4\uB9C8\uAC00 \uCD94\uAC00 \uC18D\uC131\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +allOf = \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8\uC5D0 \uC720\uD6A8\uD574\uC57C \uD569\uB2C8\uB2E4. {0} +anyOf = \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8 {0}\uC5D0 \uC720\uD6A8\uD574\uC57C \uD569\uB2C8\uB2E4. +const = \uC0C1\uC218 \uAC12 ''{0}''\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +contains = \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD55C \uC694\uC18C\uB97C \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {1} +contains.max = \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD558\uB294 \uCD5C\uB300 {0}\uAC1C\uC758 \uC694\uC18C\uB97C \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4: {1} +contains.min = \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD558\uB294 \uC694\uC18C\uAC00 \uCD5C\uC18C\uD55C {0}\uAC1C \uD3EC\uD568\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4: {1} +dependencies = \uC885\uC18D\uC131 {0}\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4. +dependentRequired = ''{1}''\uC774(\uAC00) \uC874\uC7AC\uD558\uAE30 \uB54C\uBB38\uC5D0 \uC885\uC18D \uD544\uC218\uC778 ''{0}'' \uC18D\uC131\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. +dependentSchemas = {0}:dependentSchemas {0}\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4. +enum = \uC5F4\uAC70\uD615 {0}\uC5D0 \uAC12\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. +exclusiveMaximum = {0}\uC758 \uBC30\uD0C0\uC801 \uCD5C\uB300\uAC12\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4. +exclusiveMinimum = {0}\uC758 \uBC30\uD0C0\uC801 \uCD5C\uC18C\uAC12\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4. +false = ''{0}''\uC5D0 \uB300\uD55C \uC2A4\uD0A4\uB9C8\uAC00 false\uC785\uB2C8\uB2E4. +format = {0} \uD328\uD134 \uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +format.date = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3339 \uC804\uCCB4 \uB0A0\uC9DC\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.date-time = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3339 \uB0A0\uC9DC-\uC2DC\uAC04\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.duration = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C ISO 8601 \uAE30\uAC04\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.email = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 5321 \uBA54\uC77C\uBC15\uC2A4\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.ipv4 = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 2673 IP \uC8FC\uC18C\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.ipv6 = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 4291 IP \uC8FC\uC18C\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.idn-email = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 6531 \uC0AC\uC11C\uD568\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.idn-hostname = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 5890 \uAD6D\uC81C\uD654 \uD638\uC2A4\uD2B8 \uC774\uB984\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.iri = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3987 IRI\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.iri-reference = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3987 IRI \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.uri = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3986 URI\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.uri-reference = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3986 URI \uCC38\uC870\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.uri-template = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 6570 URI \uD15C\uD50C\uB9BF\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.uuid = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 4122 UUID\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.regex = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C ECMA-262 \uC815\uADDC \uD45C\uD604\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.time = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 3339 \uC2DC\uAC04\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.hostname = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 1123 \uD638\uC2A4\uD2B8 \uC774\uB984\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +format.json-pointer = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C RFC 6901 JSON \uD3EC\uC778\uD130\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.relative-json-pointer = {0} \uD328\uD134\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC720\uD6A8\uD55C IETF \uC0C1\uB300 JSON \uD3EC\uC778\uD130\uC5EC\uC57C \uD569\uB2C8\uB2E4. +format.unknown = \uC54C \uC218 \uC5C6\uB294 \uD615\uC2DD ''{0}''\uC774(\uAC00) \uC788\uC2B5\uB2C8\uB2E4. +id = ''{0}''\uC740(\uB294) \uC720\uD6A8\uD55C {1}\uC774 \uC544\uB2D9\uB2C8\uB2E4. +items = \uC0C9\uC778 ''{0}''\uC774(\uAC00) \uC2A4\uD0A4\uB9C8\uC5D0 \uC815\uC758\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC73C\uBA70 \uC2A4\uD0A4\uB9C8\uAC00 \uCD94\uAC00 \uD56D\uBAA9\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +maxContains = {0}\uC5D0\uC11C \uC74C\uC218\uAC00 \uC544\uB2CC \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4. +maxItems = \uCD5C\uB300 {0}\uAC1C\uC758 \uD56D\uBAA9\uC774 \uC788\uC5B4\uC57C \uD558\uC9C0\uB9CC {1}\uAC1C\uB97C \uCC3E\uC558\uC2B5\uB2C8\uB2E4. +maxLength = \uAE38\uC774\uB294 \uCD5C\uB300 {0}\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4. +maxProperties = \uCD5C\uB300 {0}\uAC1C\uC758 \uC18D\uC131\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4. +maximum = \uCD5C\uB300\uAC12\uC740 {0}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +minContains = {0}\uC5D0\uC11C \uC74C\uC218\uAC00 \uC544\uB2CC \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4. +minContainsVsMaxContains = minContains\uB294 {0}\uC758 maxContains\uBCF4\uB2E4 \uC791\uAC70\uB098 \uAC19\uC544\uC57C \uD569\uB2C8\uB2E4. +minItems = \uCD5C\uC18C {0}\uAC1C\uC758 \uD56D\uBAA9\uC774 \uC788\uC5B4\uC57C \uD558\uC9C0\uB9CC {1}\uAC1C\uB97C \uCC3E\uC558\uC2B5\uB2C8\uB2E4. +minLength = \uAE38\uC774\uB294 {0}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +minProperties = \uCD5C\uC18C\uD55C {0}\uAC1C\uC758 \uC18D\uC131\uC774 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4. +minimum = \uCD5C\uC18C\uAC12\uC740 {0}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. +multipleOf = {0}\uC758 \uBC30\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4. +not = \uC2A4\uD0A4\uB9C8 {0}\uC5D0 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544\uC57C \uD569\uB2C8\uB2E4. +notAllowed = ''{0}'' \uC18D\uC131\uC740 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC9C0\uB9CC \uB370\uC774\uD130\uC5D0 \uC788\uC2B5\uB2C8\uB2E4. +oneOf = \uD558\uB098\uC758 \uC2A4\uD0A4\uB9C8\uC5D0\uB9CC \uC720\uD6A8\uD574\uC57C \uD558\uC9C0\uB9CC {0}\uC740(\uB294) \uC720\uD6A8\uD569\uB2C8\uB2E4. +oneOf.indexes = \uD558\uB098\uC758 \uC2A4\uD0A4\uB9C8\uC5D0\uB9CC \uC720\uD6A8\uD574\uC57C \uD558\uC9C0\uB9CC {0}\uC740(\uB294) \uC0C9\uC778 ''{1}''\uC5D0 \uC720\uD6A8\uD569\uB2C8\uB2E4. +pattern = \uC815\uADDC\uC2DD \uD328\uD134 {0}\uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +patternProperties = ''\uD328\uD134 \uC18D\uC131''\uC5D0 \uC77C\uBD80 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4. +prefixItems = \uC774 \uC0C9\uC778\uC5D0\uC11C \uC720\uD6A8\uC131 \uAC80\uC0AC\uAE30\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +properties = ''\uC18D\uC131''\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4. +propertyNames = ''{0}'' \uC18D\uC131 \uC774\uB984\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {1} +readOnly = \uC77D\uAE30 \uC804\uC6A9 \uD544\uB4DC\uC774\uBBC0\uB85C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +required = \uD544\uC218 \uC18D\uC131 ''{0}''\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +type = {0} \uBC1C\uACAC, {1} \uC608\uC0C1 +unevaluatedItems = ''{0}'' \uC0C9\uC778\uC740 \uD3C9\uAC00\uB418\uC9C0 \uC54A\uC73C\uBA70 \uC2A4\uD0A4\uB9C8\uB294 \uD3C9\uAC00\uB418\uC9C0 \uC54A\uC740 \uD56D\uBAA9\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +unevaluatedProperties = ''{0}'' \uC18D\uC131\uC740 \uD3C9\uAC00\uB418\uC9C0 \uC54A\uC73C\uBA70 \uC2A4\uD0A4\uB9C8\uB294 \uD3C9\uAC00\uB418\uC9C0 \uC54A\uC740 \uC18D\uC131\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +unionType = {0} \uBC1C\uACAC, {1} \uC608\uC0C1 +uniqueItems = \uBC30\uC5F4\uC5D0 \uACE0\uC720\uD55C \uD56D\uBAA9\uB9CC \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4. +writeOnly = \uC4F0\uAE30 \uC804\uC6A9 \uD544\uB4DC\uC774\uBBC0\uB85C \uB370\uC774\uD130\uC5D0 \uB098\uD0C0\uB0A0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +contentEncoding = \uCF58\uD150\uCE20 \uC778\uCF54\uB529 {0}\uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +contentMediaType = \uCF58\uD150\uCE20\uAC00 \uC544\uB2D9\uB2C8\uB2E4. diff --git a/src/main/resources/jsv-messages_nb.properties b/src/main/resources/jsv-messages_nb.properties new file mode 100644 index 000000000..b536ed4f1 --- /dev/null +++ b/src/main/resources/jsv-messages_nb.properties @@ -0,0 +1,70 @@ +$ref = har en feil med ''refs'' +additionalItems = indeks ''{0}'' er ikke definert i skjemaet og skjemaet tillater ikke flere elementer +additionalProperties = egenskapen ''{0}'' er ikke definert i skjemaet og skjemaet tillater ikke ytterligere egenskaper +allOf = m vre gyldig for alle skjemaene {0} +anyOf = m vre gyldig for alle skjemaene {0} +const = m vre konstantverdien ''{0}'' +contains = inneholder ikke et element som bestr disse valideringene: {1} +contains.max = m inneholde maksimalt {0} element(er) som bestr disse valideringene: {1} +contains.min = m inneholde minst {0} element(er) som bestr disse valideringene: {1} +dependencies = har en feil med avhengigheter {0} +dependentRequired = har en manglende egenskap ''{0}'' som er avhengig ndvendig fordi ''{1}'' er tilstede +dependentSchemas = har en feil med dependentSchemas {0} +enum = har ikke en verdi i oppregningen {0} +exclusiveMaximum = m ha en eksklusiv maksimumsverdi p {0} +exclusiveMinimum = m ha en eksklusiv minimumsverdi p {0} +false = skjemaet for ''{0}'' er usant +format = samsvarer ikke med {0}-mnsteret +format.date = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3339 full-dato +format.date-time = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3339 dato-klokkeslett +format.duration = samsvarer ikke med {0}-mnsteret m vre en gyldig ISO 8601-varighet +format.email = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 5321-postboks +format.ipv4 = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 2673 IP-adresse +format.ipv6 = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 4291 IP-adresse +format.idn-email = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 6531-postboks +format.idn-hostname = samsvarer ikke med {0}-mnsteret m vre et gyldig RFC 5890 internasjonalisert vertsnavn +format.iri = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3987 IRI +format.iri-reference = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3987 IRI-referanse +format.uri = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3986 URI +format.uri-reference = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3986 URI-referanse +format.uri-template = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 6570 URI-mal +format.uuid = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 4122 UUID +format.regex = samsvarer ikke med {0}-mnsteret m vre et gyldig ECMA-262 regulrt uttrykk +format.time = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 3339-tid +format.hostname = samsvarer ikke med {0}-mnsteret m vre et gyldig RFC 1123-vertsnavn +format.json-pointer = samsvarer ikke med {0}-mnsteret m vre en gyldig RFC 6901 JSON-peker +format.relative-json-pointer = samsvarer ikke med {0}-mnsteret m vre en gyldig IETF relativ JSON-peker +format.unknown = har et ukjent format ''{0}'' +id = ''{0}'' er ikke en gyldig {1} +items = indeks ''{0}'' er ikke definert i skjemaet, og skjemaet tillater ikke flere elementer +maxContains = m vre et ikke-negativt heltall i {0} +maxItems = m ha maksimalt {0} varer, men fant {1} +maxLength = m best av maksimalt {0} tegn +maxProperties = m ha maksimalt {0} egenskaper +maximum = m ha en maksimal verdi p {0} +minContains = m vre et ikke-negativt heltall i {0} +minContainsVsMaxContains = minContains m vre mindre enn eller lik maxContains i {0} +minItems = m ha minst {0} elementer, men fant {1} +minLength = m best av minst {0} tegn +minProperties = m ha minst {0} egenskaper +minimum = m ha en minimumsverdi p {0} +multipleOf = m vre multiplum av {0} +not = m ikke vre gyldig for skjemaet {0} +notAllowed = egenskapen ''{0}'' er ikke tillatt, men den er i dataene +oneOf = m vre gyldig for ett og bare ett skjema, men {0} er gyldige +oneOf.indexes = m vre gyldig for ett og bare ett skjema, men {0} er gyldige med indeksene ''{1}'' +pattern = samsvarer ikke med regex-mnsteret {0} +patternProperties = har en feil med ''mnsteregenskaper'' +prefixItems = ingen validator funnet i denne indeksen +properties = har en feil med ''egenskaper'' +propertyNames = egenskapen ''{0}'' navn er ikke gyldig: {1} +readOnly = er et skrivebeskyttet felt, det kan ikke endres +required = pkrevd egenskap ''{0}'' ikke funnet +type = {0} funnet, {1} forventet +unevaluatedItems = indeks ''{0}'' er ikke evaluert og skjemaet tillater ikke uevaluerte elementer +unevaluatedProperties = egenskapen ''{0}'' er ikke evaluert og skjemaet tillater ikke uevaluerte egenskaper +unionType = {0} funnet, {1} forventet +uniqueItems = m bare ha unike elementer i matrisen +writeOnly = er et skrivebeskyttet felt, det kan ikke vises i dataene +contentEncoding = samsvarer ikke med innholdskoding {0} +contentMediaType = er ikke et innhold for meg diff --git a/src/main/resources/jsv-messages_nl.properties b/src/main/resources/jsv-messages_nl.properties new file mode 100644 index 000000000..437d94429 --- /dev/null +++ b/src/main/resources/jsv-messages_nl.properties @@ -0,0 +1,70 @@ +$ref = bevat een fout met ''refs'' +additionalItems = index ''{0}'' is niet gedefinieerd in het schema en het schema staat geen extra items toe +additionalProperties = eigenschap ''{0}'' is niet gedefinieerd in het schema en het schema staat geen aanvullende eigenschappen toe +allOf = moet geldig zijn voor alle schema''s {0} +anyOf = moet geldig zijn voor elk van de schema''s {0} +const = moet de constante waarde ''{0}'' zijn +contains = bevat geen element dat deze validaties doorstaat: {1} +contains.max = moet maximaal {0} element(en) bevatten die aan deze validaties voldoen: {1} +contains.min = moet minimaal {0} element(en) bevatten die aan deze validaties voldoen: {1} +dependencies = bevat een fout met afhankelijkheden {0} +dependentRequired = heeft een ontbrekende eigenschap ''{0}'' die afhankelijk is vereist omdat ''{1}'' aanwezig is +dependentSchemas = bevat een fout met dependSchemas {0} +enum = heeft geen waarde in de opsomming {0} +exclusiveMaximum = moet een exclusieve maximumwaarde hebben van {0} +exclusiveMinimum = moet een exclusieve minimumwaarde hebben van {0} +false = schema voor ''{0}'' is false +format = komt niet overeen met het {0} patroon +format.date = komt niet overeen met het {0}-patroon moet een geldige RFC 3339-volledige datum zijn +format.date-time = komt niet overeen met het {0}-patroon moet een geldige RFC 3339-datum-tijd zijn +format.duration = komt niet overeen met het patroon {0} moet een geldige ISO 8601-duur hebben +format.email = komt niet overeen met het patroon {0} moet een geldige RFC 5321-mailbox zijn +format.ipv4 = komt niet overeen met het patroon {0} moet een geldig RFC 2673 IP-adres zijn +format.ipv6 = komt niet overeen met het patroon {0} moet een geldig RFC 4291 IP-adres zijn +format.idn-email = komt niet overeen met het patroon {0} moet een geldige RFC 6531-mailbox zijn +format.idn-hostname = komt niet overeen met het patroon {0} moet een geldige genternationaliseerde RFC 5890-hostnaam zijn +format.iri = komt niet overeen met het {0}-patroon moet een geldige RFC 3987 IRI zijn +format.iri-reference = komt niet overeen met het {0}-patroon moet een geldige RFC 3987 IRI-referentie zijn +format.uri = komt niet overeen met het {0}-patroon moet een geldige RFC 3986-URI zijn +format.uri-reference = komt niet overeen met het {0}-patroon moet een geldige RFC 3986 URI-referentie zijn +format.uri-template = komt niet overeen met het {0}-patroon moet een geldige RFC 6570 URI-sjabloon zijn +format.uuid = komt niet overeen met het {0}-patroon moet een geldige RFC 4122 UUID zijn +format.regex = komt niet overeen met het patroon {0} moet een geldige reguliere ECMA-262-expressie zijn +format.time = komt niet overeen met het {0}-patroon moet een geldige RFC 3339-tijd zijn +format.hostname = komt niet overeen met het patroon {0} moet een geldige RFC 1123-hostnaam zijn +format.json-pointer = komt niet overeen met het {0}-patroon moet een geldige RFC 6901 JSON-aanwijzer zijn +format.relative-json-pointer = komt niet overeen met het {0}-patroon moet een geldige IETF relatieve JSON-aanwijzer zijn +format.unknown = heeft een onbekend formaat ''{0}'' +id = ''{0}'' is geen geldige {1} +items = index ''{0}'' is niet gedefinieerd in het schema en het schema staat geen extra items toe +maxContains = moet een niet-negatief geheel getal zijn in {0} +maxItems = moet maximaal {0} items bevatten, maar heeft {1} gevonden +maxLength = mag maximaal {0} tekens lang zijn +maxProperties = moet maximaal {0} eigenschappen hebben +maximum = moet een maximale waarde van {0} hebben +minContains = moet een niet-negatief geheel getal zijn in {0} +minContainsVsMaxContains = minContains moet kleiner zijn dan of gelijk zijn aan maxContains in {0} +minItems = moet minstens {0} items hebben, maar {1} gevonden +minLength = moet minimaal {0} tekens lang zijn +minProperties = moet minimaal {0} eigenschappen hebben +minimum = moet een minimumwaarde van {0} hebben +multipleOf = moet een veelvoud zijn van {0} +not = mag niet geldig zijn voor het schema {0} +notAllowed = eigenschap ''{0}'' is niet toegestaan, maar staat wel in de gegevens +oneOf = moet geldig zijn voor slechts n schema, maar {0} zijn geldig +oneOf.indexes = moet geldig zijn voor n en slechts n schema, maar {0} zijn geldig met indexen ''{1}'' +pattern = komt niet overeen met het regex-patroon {0} +patternProperties = bevat een fout met ''patrooneigenschappen'' +prefixItems = geen validator gevonden bij deze index +properties = bevat een fout met ''properties'' +propertyNames = eigenschap ''{0}'' naam is niet geldig: {1} +readOnly = is een alleen-lezen veld, dit kan niet worden gewijzigd +required = vereiste eigenschap ''{0}'' niet gevonden +type = {0} gevonden, {1} verwacht +unevaluatedItems = index ''{0}'' wordt niet gevalueerd en het schema staat geen niet-gevalueerde items toe +unevaluatedProperties = eigenschap ''{0}'' wordt niet gevalueerd en het schema staat geen niet-gevalueerde eigenschappen toe +unionType = {0} gevonden, {1} verwacht +uniqueItems = mag alleen unieke items in de array bevatten +writeOnly = is een alleen-schrijven-veld, het kan niet voorkomen in de gegevens +contentEncoding = komt niet overeen met inhoudscodering {0} +contentMediaType = is geen inhoudsmij diff --git a/src/main/resources/jsv-messages_pl.properties b/src/main/resources/jsv-messages_pl.properties new file mode 100644 index 000000000..4717b151b --- /dev/null +++ b/src/main/resources/jsv-messages_pl.properties @@ -0,0 +1,70 @@ +$ref = zawiera b\u0142\u0105d z \u201Erefami\u201D +additionalItems = indeks \u201E{0}\u201D nie jest zdefiniowany w schemacie i schemat nie pozwala na dodatkowe elementy +additionalProperties = w\u0142a\u015Bciwo\u015B\u0107 \u201E{0}\u201D nie jest zdefiniowana w schemacie i schemat nie pozwala na dodatkowe w\u0142a\u015Bciwo\u015Bci +allOf = musi by\u0107 poprawny dla wszystkich schematw {0} +anyOf = musi by\u0107 poprawny dla dowolnego schematu {0} +const = musi by\u0107 sta\u0142\u0105 warto\u015Bci\u0105 \u201E{0}\u201D +contains = nie zawiera elementu, ktry przechodzi te weryfikacje: {1} +contains.max = musi zawiera\u0107 maksymalnie {0} elementw, ktre przechodz\u0105 t\u0119 weryfikacj\u0119: {1} +contains.min = musi zawiera\u0107 co najmniej {0} elementw, ktre przechodz\u0105 t\u0119 weryfikacj\u0119: {1} +dependencies = zawiera b\u0142\u0105d z zale\u017Cno\u015Bciami {0} +dependentRequired = ma brakuj\u0105c\u0105 w\u0142a\u015Bciwo\u015B\u0107 \u201E{0}\u201D, ktra jest zale\u017Cna i wymagana, poniewa\u017C wyst\u0119puje \u201E{1}\u201D +dependentSchemas = zawiera b\u0142\u0105d w schematach zale\u017Cnych {0} +enum = nie ma warto\u015Bci w wyliczeniu {0} +exclusiveMaximum = musi mie\u0107 wy\u0142\u0105czn\u0105 warto\u015B\u0107 maksymaln\u0105 wynosz\u0105c\u0105 {0} +exclusiveMinimum = musi mie\u0107 wy\u0142\u0105czn\u0105 minimaln\u0105 warto\u015B\u0107 {0} +false = schemat dla \u201E{0}\u201D jest fa\u0142szywy +format = nie pasuje do wzorca {0} +format.date = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 pe\u0142n\u0105 dat\u0105 RFC 3339 +format.date-time = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 dat\u0105 i godzin\u0105 RFC 3339 +format.duration = nie pasuje do wzorca {0}, musi mie\u0107 prawid\u0142owy czas trwania ISO 8601 +format.email = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 skrzynk\u0105 pocztow\u0105 RFC 5321 +format.ipv4 = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym adresem IP RFC 2673 +format.ipv6 = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym adresem IP RFC 4291 +format.idn-email = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 skrzynk\u0105 pocztow\u0105 RFC 6531 +format.idn-hostname = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 mi\u0119dzynarodow\u0105 nazw\u0105 hosta zgodn\u0105 z RFC 5890 +format.iri = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym IRI RFC 3987 +format.iri-reference = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym odwo\u0142aniem IRI RFC 3987 +format.uri = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym identyfikatorem URI RFC 3986 +format.uri-reference = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym odwo\u0142aniem URI RFC 3986 +format.uri-template = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym szablonem URI RFC 6570 +format.uuid = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym identyfikatorem UUID RFC 4122 +format.regex = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym wyra\u017Ceniem regularnym ECMA-262 +format.time = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym czasem RFC 3339 +format.hostname = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142ow\u0105 nazw\u0105 hosta zgodn\u0105 z RFC 1123 +format.json-pointer = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym wska\u017Anikiem JSON RFC 6901 +format.relative-json-pointer = nie pasuje do wzorca {0} musi by\u0107 prawid\u0142owym wzgl\u0119dnym wska\u017Anikiem JSON IETF +format.unknown = ma nieznany format \u201E{0}\u201D +id = \u201E{0}\u201D nie jest prawid\u0142owym {1} +items = indeks ''{0}'' nie jest zdefiniowany w schemacie i schemat nie pozwala na dodatkowe elementy +maxContains = musi by\u0107 nieujemn\u0105 liczb\u0105 ca\u0142kowit\u0105 w {0} +maxItems = musi mie\u0107 co najwy\u017Cej {0} elementw, ale znaleziono {1} +maxLength = musi mie\u0107 maksymalnie {0} znakw +maxProperties = musi mie\u0107 co najwy\u017Cej {0} w\u0142a\u015Bciwo\u015Bci +maximum = musi mie\u0107 maksymaln\u0105 warto\u015B\u0107 {0} +minContains = musi by\u0107 nieujemn\u0105 liczb\u0105 ca\u0142kowit\u0105 w {0} +minContainsVsMaxContains = minContains musi by\u0107 mniejsze lub rwne maxContains w {0} +minItems = musi mie\u0107 co najmniej {0} elementw, ale znaleziono {1} +minLength = musi mie\u0107 co najmniej {0} znakw +minProperties = musi mie\u0107 co najmniej {0} w\u0142a\u015Bciwo\u015Bci +minimum = musi mie\u0107 minimaln\u0105 warto\u015B\u0107 {0} +multipleOf = musi by\u0107 wielokrotno\u015Bci\u0105 {0} +not = nie mo\u017Ce by\u0107 poprawny dla schematu {0} +notAllowed = w\u0142a\u015Bciwo\u015B\u0107 \u201E{0}\u201D jest niedozwolona, ale znajduje si\u0119 w danych +oneOf = musi by\u0107 poprawny dla jednego i tylko jednego schematu, ale {0} s\u0105 prawid\u0142owe +oneOf.indexes = musi by\u0107 poprawny dla jednego i tylko jednego schematu, ale {0} jest prawid\u0142owe z indeksami \u201E{1}\u201D +pattern = nie pasuje do wzorca wyra\u017Cenia regularnego {0} +patternProperties = zawiera b\u0142\u0105d dotycz\u0105cy \u201Ew\u0142a\u015Bciwo\u015Bci wzorca\u201D +prefixItems = w tym indeksie nie znaleziono walidatora +properties = zawiera b\u0142\u0105d dotycz\u0105cy \u201Ew\u0142a\u015Bciwo\u015Bci\u201D +propertyNames = nazwa w\u0142a\u015Bciwo\u015Bci \u201E{0}\u201D jest nieprawid\u0142owa: {1} +readOnly = jest polem tylko do odczytu, nie mo\u017Cna go zmieni\u0107 +required = nie znaleziono wymaganej w\u0142a\u015Bciwo\u015Bci \u201E{0}\u201D. +type = Znaleziono {0}, oczekiwano {1} +unevaluatedItems = indeks \u201E{0}\u201D nie jest oceniany i schemat nie pozwala na nieocenione elementy +unevaluatedProperties = w\u0142a\u015Bciwo\u015B\u0107 \u201E{0}\u201D nie jest oceniana i schemat nie pozwala na nieocenione w\u0142a\u015Bciwo\u015Bci +unionType = Znaleziono {0}, oczekiwano {1} +uniqueItems = tablica musi zawiera\u0107 tylko unikalne elementy +writeOnly = jest polem tylko do zapisu, nie mo\u017Ce pojawia\u0107 si\u0119 w danych +contentEncoding = nie pasuje do kodowania tre\u015Bci {0} +contentMediaType = nie jest tre\u015Bci\u0105 diff --git a/src/main/resources/jsv-messages_pt.properties b/src/main/resources/jsv-messages_pt.properties new file mode 100644 index 000000000..39b130852 --- /dev/null +++ b/src/main/resources/jsv-messages_pt.properties @@ -0,0 +1,70 @@ +$ref = tem um erro com ''refs'' +additionalItems = o ndice ''{0}'' no est definido no esquema e o esquema no permite itens adicionais +additionalProperties = a propriedade ''{0}'' no est definida no esquema e o esquema no permite propriedades adicionais +allOf = deve ser vlido para todos os esquemas {0} +anyOf = deve ser vlido para qualquer um dos esquemas {0} +const = deve ser o valor constante ''{0}'' +contains = no contm um elemento que passe nestas validaes: {1} +contains.max = deve conter no mximo {0} elemento(s) que passe(m) nestas validaes: {1} +contains.min = deve conter pelo menos {0} elemento(s) que passe(m) nestas validaes: {1} +dependencies = h um erro com dependncias {0} +dependentRequired = tem uma propriedade ausente ''{0}'' que dependente necessria porque ''{1}'' est presente +dependentSchemas = h um erro com dependenteSchemas {0} +enum = no possui valor na enumerao {0} +exclusiveMaximum = deve ter um valor mximo exclusivo de {0} +exclusiveMinimum = deve ter um valor mnimo exclusivo de {0} +false = o esquema para ''{0}'' falso +format = no corresponde ao padro {0} +format.date = no corresponde ao padro {0} deve ser uma data completa RFC 3339 vlida +format.date-time = no corresponde ao padro {0} deve ser uma data e hora RFC 3339 vlida +format.duration = no corresponde ao padro {0} deve ter uma durao ISO 8601 vlida +format.email = no corresponde ao padro {0} deve ser uma caixa de correio RFC 5321 vlida +format.ipv4 = no corresponde ao padro {0} deve ser um endereo IP RFC 2673 vlido +format.ipv6 = no corresponde ao padro {0} deve ser um endereo IP RFC 4291 vlido +format.idn-email = no corresponde ao padro {0} deve ser uma caixa de correio RFC 6531 vlida +format.idn-hostname = no corresponde ao padro {0} deve ser um nome de host internacionalizado RFC 5890 vlido +format.iri = no corresponde ao padro {0} deve ser um RFC 3987 IRI vlido +format.iri-reference = no corresponde ao padro {0} deve ser uma referncia IRI RFC 3987 vlida +format.uri = no corresponde ao padro {0} deve ser um URI RFC 3986 vlido +format.uri-reference = no corresponde ao padro {0} deve ser uma referncia de URI RFC 3986 vlida +format.uri-template = no corresponde ao padro {0} deve ser um modelo de URI RFC 6570 vlido +format.uuid = no corresponde ao padro {0} deve ser um UUID RFC 4122 vlido +format.regex = no corresponde ao padro {0} deve ser uma expresso regular ECMA-262 vlida +format.time = no corresponde ao padro {0} deve ser um horrio RFC 3339 vlido +format.hostname = no corresponde ao padro {0} deve ser um nome de host RFC 1123 vlido +format.json-pointer = no corresponde ao padro {0} deve ser um ponteiro JSON RFC 6901 vlido +format.relative-json-pointer = no corresponde ao padro {0} deve ser um ponteiro JSON relativo IETF vlido +format.unknown = tem um formato desconhecido ''{0}'' +id = ''{0}'' no um {1} vlido +items = o ndice ''{0}'' no est definido no esquema e o esquema no permite itens adicionais +maxContains = deve ser um nmero inteiro no negativo em {0} +maxItems = deve ter no mximo {0} itens, mas foram encontrados {1} +maxLength = deve ter no mximo {0} caracteres +maxProperties = deve ter no mximo {0} propriedades +maximum = deve ter um valor mximo de {0} +minContains = deve ser um nmero inteiro no negativo em {0} +minContainsVsMaxContains = minContains deve ser menor ou igual a maxContains em {0} +minItems = deve ter pelo menos {0} itens, mas foi encontrado {1} +minLength = deve ter pelo menos {0} caracteres +minProperties = deve ter pelo menos {0} propriedades +minimum = deve ter um valor mnimo de {0} +multipleOf = deve ser mltiplo de {0} +not = no deve ser vlido para o esquema {0} +notAllowed = propriedade ''{0}'' no permitida, mas est nos dados +oneOf = deve ser vlido para um e apenas um esquema, mas {0} so vlidos +oneOf.indexes = deve ser vlido para um e somente um esquema, mas {0} so vlidos com ndices ''{1}'' +pattern = no corresponde ao padro regex {0} +patternProperties = h algum erro com ''propriedades do padro'' +prefixItems = nenhum validador encontrado neste ndice +properties = h um erro com ''propriedades'' +propertyNames = o nome da propriedade ''{0}'' no vlido: {1} +readOnly = um campo somente leitura, no pode ser alterado +required = propriedade obrigatria ''{0}'' no encontrada +type = {0} encontrado, {1} esperado +unevaluatedItems = o ndice ''{0}'' no avaliado e o esquema no permite itens no avaliados +unevaluatedProperties = a propriedade ''{0}'' no foi avaliada e o esquema no permite propriedades no avaliadas +unionType = {0} encontrado, {1} esperado +uniqueItems = deve ter apenas itens nicos no array +writeOnly = um campo somente gravao, no pode aparecer nos dados +contentEncoding = no corresponde codificao de contedo {0} +contentMediaType = no um contedo para mim diff --git a/src/main/resources/jsv-messages_ro.properties b/src/main/resources/jsv-messages_ro.properties new file mode 100644 index 000000000..2c5fcbe72 --- /dev/null +++ b/src/main/resources/jsv-messages_ro.properties @@ -0,0 +1,70 @@ +$ref = are o eroare cu \u201Erefs\u201D +additionalItems = indexul \u201E{0}\u201D nu este definit n schem\u0103, iar schema nu permite elemente suplimentare +additionalProperties = proprietatea \u201E{0}\u201D nu este definit\u0103 n schem\u0103, iar schema nu permite propriet\u0103\u021Bi suplimentare +allOf = trebuie s\u0103 fie valid pentru toate schemele {0} +anyOf = trebuie s\u0103 fie valid pentru oricare dintre schemele {0} +const = trebuie s\u0103 fie valoarea constant\u0103 \u201E{0}\u201D +contains = nu con\u0163ine un element care trece aceste valid\u0103ri: {1} +contains.max = trebuie s\u0103 con\u021Bin\u0103 cel mult {0} element(e) care trece aceste valid\u0103ri: {1} +contains.min = trebuie s\u0103 con\u021Bin\u0103 cel pu\u021Bin {0} element(e) care trece aceste valid\u0103ri: {1} +dependencies = are o eroare cu dependen\u021Bele {0} +dependentRequired = are o proprietate lips\u0103 ''{0}'' care este dependent\u0103 necesar\u0103 deoarece ''{1}'' este prezent +dependentSchemas = are o eroare cu dependentSchemas {0} +enum = nu are o valoare n enumerarea {0} +exclusiveMaximum = trebuie s\u0103 aib\u0103 o valoare maxim\u0103 exclusiv\u0103 de {0} +exclusiveMinimum = trebuie s\u0103 aib\u0103 o valoare minim\u0103 exclusiv\u0103 de {0} +false = schema pentru \u201E{0}\u201D este fals\u0103 +format = nu se potrive\u0219te cu modelul {0} +format.date = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o dat\u0103 complet\u0103 RFC 3339 valid\u0103 +format.date-time = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un RFC 3339 data-ora valid +format.duration = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o durat\u0103 ISO 8601 valid\u0103 +format.email = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o cutie po\u0219tal\u0103 RFC 5321 valid\u0103 +format.ipv4 = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o adres\u0103 IP RFC 2673 valid\u0103 +format.ipv6 = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o adres\u0103 IP RFC 4291 valid\u0103 +format.idn-email = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o cutie po\u0219tal\u0103 RFC 6531 valid\u0103 +format.idn-hostname = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un nume de gazd\u0103 interna\u021Bionalizat RFC 5890 valid +format.iri = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un RFC 3987 IRI valid +format.iri-reference = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o referin\u021B\u0103 IRI RFC 3987 valid\u0103 +format.uri = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un URI RFC 3986 valid +format.uri-reference = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o referin\u021B\u0103 URI RFC 3986 valid\u0103 +format.uri-template = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un \u0219ablon URI RFC 6570 valid +format.uuid = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un UUID RFC 4122 valid +format.regex = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie o expresie regulat\u0103 ECMA-262 valid\u0103 +format.time = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un timp RFC 3339 valid +format.hostname = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un nume de gazd\u0103 RFC 1123 valid +format.json-pointer = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un pointer JSON RFC 6901 valid +format.relative-json-pointer = nu se potrive\u0219te cu modelul {0} trebuie s\u0103 fie un pointer JSON relativ IETF valid +format.unknown = are un format necunoscut ''{0}'' +id = \u201E{0}\u201D nu este un {1} valid +items = indexul \u201E{0}\u201D nu este definit n schem\u0103, iar schema nu permite articole suplimentare +maxContains = trebuie s\u0103 fie un num\u0103r ntreg nenegativ n {0} +maxItems = trebuie s\u0103 aib\u0103 cel mult {0} articole, dar g\u0103site {1} +maxLength = trebuie s\u0103 aib\u0103 cel mult {0} caractere +maxProperties = trebuie s\u0103 aib\u0103 cel mult {0} propriet\u0103\u021Bi +maximum = trebuie s\u0103 aib\u0103 o valoare maxim\u0103 de {0} +minContains = trebuie s\u0103 fie un num\u0103r ntreg nenegativ n {0} +minContainsVsMaxContains = minContains trebuie s\u0103 fie mai mic sau egal cu maxContains n {0} +minItems = trebuie s\u0103 aib\u0103 cel pu\u021Bin {0} articole, dar g\u0103site {1} +minLength = trebuie s\u0103 aib\u0103 cel pu\u021Bin {0} caractere +minProperties = trebuie s\u0103 aib\u0103 cel pu\u021Bin {0} propriet\u0103\u021Bi +minimum = trebuie s\u0103 aib\u0103 o valoare minim\u0103 de {0} +multipleOf = trebuie s\u0103 fie multiplu de {0} +not = nu trebuie s\u0103 fie valid pentru schema {0} +notAllowed = proprietatea \u201E{0}\u201D nu este permis\u0103, dar este n date +oneOf = trebuie s\u0103 fie valid pentru una \u0219i doar o schem\u0103, dar {0} sunt valide +oneOf.indexes = trebuie s\u0103 fie valid pentru una \u0219i doar o schem\u0103, dar {0} sunt valide cu indec\u0219ii \u201E{1}\u201D +pattern = nu se potrive\u0219te cu modelul regex {0} +patternProperties = are o eroare cu \u201Epropriet\u0103\u021Bile modelului\u201D +prefixItems = nu a fost g\u0103sit niciun validator la acest index +properties = are o eroare cu \u201Epropriet\u0103\u021Bi\u201D +propertyNames = numele propriet\u0103\u021Bii \u201E{0}\u201D nu este valid: {1} +readOnly = este un cmp numai n citire, nu poate fi modificat +required = proprietatea obligatorie \u201E{0}\u201D nu a fost g\u0103sit\u0103 +type = {0} g\u0103sit, {1} a\u0219teptat +unevaluatedItems = indexul \u201E{0}\u201D nu este evaluat \u0219i schema nu permite elemente neevaluate +unevaluatedProperties = proprietatea \u201E{0}\u201D nu este evaluat\u0103 \u0219i schema nu permite propriet\u0103\u021Bi neevaluate +unionType = {0} g\u0103sit, {1} a\u0219teptat +uniqueItems = trebuie s\u0103 aib\u0103 numai elemente unice n matrice +writeOnly = este un cmp numai pentru scriere, nu poate ap\u0103rea n date +contentEncoding = nu se potrive\u0219te cu codificarea con\u021Binutului {0} +contentMediaType = nu este un con\u021Binut eu diff --git a/src/main/resources/jsv-messages_ru.properties b/src/main/resources/jsv-messages_ru.properties new file mode 100644 index 000000000..b0e9eaf13 --- /dev/null +++ b/src/main/resources/jsv-messages_ru.properties @@ -0,0 +1,70 @@ +$ref = \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441 refs. +additionalItems = \u0438\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D \u0432 \u0441\u0445\u0435\u043C\u0435, \u0438 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432. +additionalProperties = \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{0}'' \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u043E \u0432 \u0441\u0445\u0435\u043C\u0435, \u0438 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u0441\u0432\u043E\u0439\u0441\u0442\u0432. +allOf = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0434\u043B\u044F \u0432\u0441\u0435\u0445 \u0441\u0445\u0435\u043C {0}. +anyOf = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0434\u043B\u044F \u043B\u044E\u0431\u043E\u0439 \u0438\u0437 \u0441\u0445\u0435\u043C {0}. +const = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u043E\u0441\u0442\u043E\u044F\u043D\u043D\u044B\u043C \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C ''{0}'' +contains = \u043D\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u044D\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438: {1} +contains.max = \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {0} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0438\u0445 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438: {1} +contains.min = \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u043A\u0430\u043A \u043C\u0438\u043D\u0438\u043C\u0443\u043C {0} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0438\u0445 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438: {1} +dependencies = \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044F\u043C\u0438 {0}. +dependentRequired = \u0438\u043C\u0435\u0435\u0442 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0435\u0435 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{0}'', \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u043C, \u043F\u043E\u0441\u043A\u043E\u043B\u044C\u043A\u0443 \u043F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 ''{1}''. +dependentSchemas = \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441dependentSchemas {0}. +enum = \u043D\u0435 \u0438\u043C\u0435\u0435\u0442 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0432 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0438 {0} +exclusiveMaximum = \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u043C\u0435\u0442\u044C \u044D\u043A\u0441\u043A\u043B\u044E\u0437\u0438\u0432\u043D\u043E\u0435 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 {0}. +exclusiveMinimum = \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u043C\u0435\u0442\u044C \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 {0}. +false = \u0441\u0445\u0435\u043C\u0430 \u0434\u043B\u044F ''{0}'' \u043D\u0435\u0432\u0435\u0440\u043D\u0430 +format = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} +format.date = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u043E\u043B\u043D\u0430\u044F \u0434\u0430\u0442\u0430 RFC 3339. +format.date-time = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u0430\u044F \u0434\u0430\u0442\u0430-\u0432\u0440\u0435\u043C\u044F RFC 3339. +format.duration = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C ISO 8601. +format.email = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u043F\u043E\u0447\u0442\u043E\u0432\u044B\u043C \u044F\u0449\u0438\u043A\u043E\u043C RFC 5321. +format.ipv4 = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C IP-\u0430\u0434\u0440\u0435\u0441\u043E\u043C RFC 2673. +format.ipv6 = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C IP-\u0430\u0434\u0440\u0435\u0441\u043E\u043C RFC 4291. +format.idn-email = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u043F\u043E\u0447\u0442\u043E\u0432\u044B\u043C \u044F\u0449\u0438\u043A\u043E\u043C RFC 6531. +format.idn-hostname = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0438\u043D\u0442\u0435\u0440\u043D\u0430\u0446\u0438\u043E\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u043C \u0438\u043C\u0435\u043D\u0435\u043C \u0445\u043E\u0441\u0442\u0430 RFC 5890. +format.iri = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C IRI RFC 3987. +format.iri-reference = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u043E\u0439 RFC 3987 IRI. +format.uri = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C URI RFC 3986. +format.uri-reference = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u043E\u0439 URI RFC 3986. +format.uri-template = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u043C \u0448\u0430\u0431\u043B\u043E\u043D\u043E\u043C URI RFC 6570. +format.uuid = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C UUID RFC 4122. +format.regex = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u043C \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u044B\u043C \u0432\u044B\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u043C ECMA-262. +format.time = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0432\u0440\u0435\u043C\u044F RFC 3339. +format.hostname = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0438\u043C\u0435\u043D\u0435\u043C \u0445\u043E\u0441\u0442\u0430 RFC 1123. +format.json-pointer = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0443\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u0435\u043C JSON RFC 6901. +format.relative-json-pointer = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u043C \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0443\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u0435\u043C JSON IETF +format.unknown = \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ''{0}'' +id = ''{0}'' \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u043C {1} +items = \u0438\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D \u0432 \u0441\u0445\u0435\u043C\u0435, \u0438 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432. +maxContains = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435\u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0446\u0435\u043B\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C \u0432 {0}. +maxItems = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {0} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043E {1} +maxLength = \u0434\u043B\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {0} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432. +maxProperties = \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u043C\u0435\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {0} \u0441\u0432\u043E\u0439\u0441\u0442\u0432. +maximum = \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C {0}. +minContains = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435\u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0446\u0435\u043B\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C \u0432 {0}. +minContainsVsMaxContains = minContains \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043C\u0435\u043D\u044C\u0448\u0435 \u0438\u043B\u0438 \u0440\u0430\u0432\u043D\u043E maxContains \u0432 {0}. +minItems = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {0} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043E {1} +minLength = \u0434\u043B\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {0} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432. +minProperties = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {0} \u0441\u0432\u043E\u0439\u0441\u0442\u0432. +minimum = \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C {0}. +multipleOf = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043A\u0440\u0430\u0442\u043D\u043E {0} +not = \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0434\u043B\u044F \u0441\u0445\u0435\u043C\u044B {0} +notAllowed = \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{0}'' \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E, \u043D\u043E \u043E\u043D\u043E \u0435\u0441\u0442\u044C \u0432 \u0434\u0430\u043D\u043D\u044B\u0445 +oneOf = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0434\u043B\u044F \u043E\u0434\u043D\u043E\u0439 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0434\u043D\u043E\u0439 \u0441\u0445\u0435\u043C\u044B, \u043D\u043E {0} \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E. +oneOf.indexes = \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0434\u043B\u044F \u043E\u0434\u043D\u043E\u0439 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0434\u043D\u043E\u0439 \u0441\u0445\u0435\u043C\u044B, \u043D\u043E {0} \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0441 \u0438\u043D\u0434\u0435\u043A\u0441\u0430\u043C\u0438 ''{1}'' +pattern = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u043E\u0433\u043E \u0432\u044B\u0440\u0430\u0436\u0435\u043D\u0438\u044F {0} +patternProperties = \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430\u043C\u0438 \u0448\u0430\u0431\u043B\u043E\u043D\u0430. +prefixItems = \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u0438\u043D\u0434\u0435\u043A\u0441\u0443 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432\u0430\u043B\u0438\u0434\u0430\u0442\u043E\u0440 +properties = \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430\u043C\u0438. +propertyNames = \u0438\u043C\u044F \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 ''{0}'' \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E: {1} +readOnly = \u043F\u043E\u043B\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F, \u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C. +required = \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0435 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{0}'' \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E +type = \u043D\u0430\u0439\u0434\u0435\u043D\u043E {0}, \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F {1} +unevaluatedItems = \u0438\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0435\u0442\u0441\u044F, \u0438 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043D\u0435\u043E\u0446\u0435\u043D\u0435\u043D\u043D\u044B\u0445 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432. +unevaluatedProperties = \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{0}'' \u043D\u0435 \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0435\u0442\u0441\u044F, \u0438 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043D\u0435\u043E\u0446\u0435\u043D\u0435\u043D\u043D\u044B\u0445 \u0441\u0432\u043E\u0439\u0441\u0442\u0432. +unionType = \u043D\u0430\u0439\u0434\u0435\u043D\u043E {0}, \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F {1} +uniqueItems = \u043C\u0430\u0441\u0441\u0438\u0432 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0443\u043D\u0438\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B. +writeOnly = \u043F\u043E\u043B\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0437\u0430\u043F\u0438\u0441\u0438, \u043E\u043D\u043E \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u044C\u0441\u044F \u0432 \u0434\u0430\u043D\u043D\u044B\u0445. +contentEncoding = \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043A\u043E\u0434\u0438\u0440\u043E\u0432\u043A\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 {0} +contentMediaType = \u043C\u0435\u043D\u044F \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 diff --git a/src/main/resources/jsv-messages_sk.properties b/src/main/resources/jsv-messages_sk.properties new file mode 100644 index 000000000..68a4d5732 --- /dev/null +++ b/src/main/resources/jsv-messages_sk.properties @@ -0,0 +1,70 @@ +$ref = obsahuje chybu s ''refs'' +additionalItems = index ''{0}'' nie je definovan v schme a schma nepovo\u013Euje \u010Fal\u0161ie polo\u017Eky +additionalProperties = vlastnos\u0165 ''{0}'' nie je definovan v schme a schma neumo\u017E\u0148uje \u010Fal\u0161ie vlastnosti +allOf = mus by\u0165 platn pre v\u0161etky schmy {0} +anyOf = mus by\u0165 platn pre ktorko\u013Evek schmu {0} +const = mus by\u0165 kon\u0161tantn hodnota ''{0}'' +contains = neobsahuje prvok, ktor vyhovuje tmto overeniam: {1} +contains.max = mus obsahova\u0165 najviac {0} prvkov, ktor prejd tmito overeniami: {1} +contains.min = mus obsahova\u0165 aspo\u0148 {0} prvkov, ktor prejd tmito overeniami: {1} +dependencies = obsahuje chybu so zvislos\u0165ami {0} +dependentRequired = m chbajcu vlastnos\u0165 ''{0}'', ktor je zvisl vy\u017Eadovan, preto\u017Ee ''{1}'' je prtomn +dependentSchemas = obsahuje chybu s dependentSchemas {0} +enum = nem hodnotu v enumercii {0} +exclusiveMaximum = mus ma\u0165 vlu\u010Dn maximlnu hodnotu {0} +exclusiveMinimum = mus ma\u0165 vlu\u010Dn minimlnu hodnotu {0} +false = schma pre ''{0}'' je nepravda +format = nezhoduje sa so vzorom {0} +format.date = nezhoduje sa so vzorom {0}, mus to by\u0165 platn pln dtum RFC 3339 +format.date-time = nezhoduje sa so vzorom {0}, mus to by\u0165 platn dtum a \u010Das RFC 3339 +format.duration = nezhoduje sa so vzorom {0}, mus ma\u0165 platn trvanie ISO 8601 +format.email = nezhoduje sa so vzorom {0}, mus to by\u0165 platn po\u0161tov schrnka RFC 5321 +format.ipv4 = nezhoduje sa so vzorom {0}, mus to by\u0165 platn adresa IP RFC 2673 +format.ipv6 = nezhoduje sa so vzorom {0}, mus to by\u0165 platn adresa IP RFC 4291 +format.idn-email = nezhoduje sa so vzorom {0}, mus to by\u0165 platn po\u0161tov schrnka RFC 6531 +format.idn-hostname = nezhoduje sa so vzorom {0}, mus to by\u0165 platn medzinrodn nzov hostite\u013Ea pod\u013Ea RFC 5890 +format.iri = nezhoduje sa so vzorom {0} mus by\u0165 platn RFC 3987 IRI +format.iri-reference = nezhoduje sa so vzorom {0}, mus to by\u0165 platn RFC 3987 IRI-reference +format.uri = nezhoduje sa so vzorom {0}, mus by\u0165 platn RFC 3986 URI +format.uri-reference = nezhoduje sa so vzorom {0}, mus to by\u0165 platn odkaz URI RFC 3986 +format.uri-template = nezhoduje sa so vzorom {0}, mus to by\u0165 platn \u0161ablna URI RFC 6570 +format.uuid = nezhoduje sa so vzorom {0}, mus to by\u0165 platn UUID RFC 4122 +format.regex = nezhoduje sa so vzorom {0}, mus to by\u0165 platn regulrny vraz ECMA-262 +format.time = nezhoduje sa so vzorom {0}, mus to by\u0165 platn \u010Das RFC 3339 +format.hostname = nezhoduje sa so vzorom {0}, mus to by\u0165 platn nzov hostite\u013Ea RFC 1123 +format.json-pointer = nezhoduje sa so vzorom {0}, mus to by\u0165 platn ukazovate\u013E RFC 6901 JSON +format.relative-json-pointer = nezhoduje sa so vzorom {0}, mus by\u0165 platnm IETF relatvnym ukazovate\u013Eom JSON +format.unknown = m neznmy formt ''{0}'' +id = ''{0}'' nie je platn {1} +items = index ''{0}'' nie je definovan v schme a schma nepovo\u013Euje \u010Fal\u0161ie polo\u017Eky +maxContains = mus by\u0165 nezporn cel \u010Dslo v {0} +maxItems = mus ma\u0165 najviac {0} polo\u017Eiek, ale njdench {1} +maxLength = mus ma\u0165 maximlne {0} znakov +maxProperties = mus ma\u0165 najviac {0} vlastnost +maximum = mus ma\u0165 maximlnu hodnotu {0} +minContains = mus by\u0165 nezporn cel \u010Dslo v {0} +minContainsVsMaxContains = minContains mus by\u0165 men\u0161 alebo rovn maxContains v {0} +minItems = mus ma\u0165 aspo\u0148 {0} polo\u017Eiek, ale njdench {1} +minLength = mus ma\u0165 aspo\u0148 {0} znakov +minProperties = mus ma\u0165 aspo\u0148 {0} vlastnost +minimum = mus ma\u0165 minimlnu hodnotu {0} +multipleOf = mus by\u0165 nsobkom {0} +not = nesmie by\u0165 platn pre schmu {0} +notAllowed = vlastnos\u0165 ''{0}'' nie je povolen, ale je v dajoch +oneOf = mus by\u0165 platn pre jednu a iba jednu schmu, ale {0} s platn +oneOf.indexes = mus by\u0165 platn pre jednu a iba jednu schmu, ale {0} s platn s indexmi ''{1}'' +pattern = nezodpoved vzoru regulrneho vrazu {0} +patternProperties = obsahuje nejak chybu s ''vlastnos\u0165ami vzoru'' +prefixItems = v tomto indexe sa nena\u0161iel \u017Eiadny validtor +properties = obsahuje chybu s ''vlastnosti'' +propertyNames = nzov vlastnosti ''{0}'' nie je platn: {1} +readOnly = je pole len na \u010Dtanie, nemo\u017Eno ho zmeni\u0165 +required = po\u017Eadovan vlastnos\u0165 ''{0}'' sa nena\u0161la +type = njdench {0}, o\u010Dakvanch {1} +unevaluatedItems = index ''{0}'' nie je vyhodnoten a schma nepovo\u013Euje nehodnoten polo\u017Eky +unevaluatedProperties = vlastnos\u0165 ''{0}'' nie je vyhodnoten a schma nepovo\u013Euje nehodnoten vlastnosti +unionType = njdench {0}, o\u010Dakvanch {1} +uniqueItems = v poli musia by\u0165 iba jedine\u010Dn polo\u017Eky +writeOnly = je pole ur\u010Den len na zpis, nem\u017Ee sa objavi\u0165 v dajoch +contentEncoding = nezhoduje sa s kdovanm obsahu {0} +contentMediaType = nie je obsah ja diff --git a/src/main/resources/jsv-messages_sv.properties b/src/main/resources/jsv-messages_sv.properties new file mode 100644 index 000000000..7d969a797 --- /dev/null +++ b/src/main/resources/jsv-messages_sv.properties @@ -0,0 +1,70 @@ +$ref = har ett fel med ''refs'' +additionalItems = index ''{0}'' r inte definierat i schemat och schemat tillter inte ytterligare objekt +additionalProperties = egenskapen ''{0}'' r inte definierad i schemat och schemat tillter inte ytterligare egenskaper +allOf = mste vara giltig fr alla scheman {0} +anyOf = mste vara giltigt fr ngot av schemana {0} +const = mste vara det konstanta vrdet ''{0}'' +contains = innehller inte ett element som klarar dessa valideringar: {1} +contains.max = mste innehlla hgst {0} element som klarar dessa valideringar: {1} +contains.min = mste innehlla minst {0} element som klarar dessa valideringar: {1} +dependencies = har ett fel med beroenden {0} +dependentRequired = har en saknad egenskap ''{0}'' som r beroende krvs eftersom ''{1}'' r nrvarande +dependentSchemas = har ett fel med dependentSchemas {0} +enum = har inget vrde i upprkningen {0} +exclusiveMaximum = mste ha ett exklusivt maxvrde p {0} +exclusiveMinimum = mste ha ett exklusivt lgsta vrde p {0} +false = schemat fr ''{0}'' r falskt +format = matchar inte {0}-mnstret +format.date = matchar inte {0}-mnstret mste vara ett giltigt RFC 3339 fulldatum +format.date-time = matchar inte {0}-mnstret mste vara ett giltigt RFC 3339 datum-tid +format.duration = matchar inte {0}-mnstret mste vara en giltig ISO 8601-varaktighet +format.email = matchar inte {0}-mnstret mste vara en giltig RFC 5321-postlda +format.ipv4 = matchar inte mnstret {0} mste vara en giltig RFC 2673 IP-adress +format.ipv6 = matchar inte {0}-mnstret mste vara en giltig RFC 4291 IP-adress +format.idn-email = matchar inte {0}-mnstret mste vara en giltig RFC 6531-postlda +format.idn-hostname = matchar inte mnstret {0} mste vara ett giltigt RFC 5890 internationaliserat vrdnamn +format.iri = matchar inte {0}-mnstret mste vara en giltig RFC 3987 IRI +format.iri-reference = matchar inte {0}-mnstret mste vara en giltig RFC 3987 IRI-referens +format.uri = matchar inte {0}-mnstret mste vara en giltig RFC 3986 URI +format.uri-reference = matchar inte {0}-mnstret mste vara en giltig RFC 3986 URI-referens +format.uri-template = matchar inte {0}-mnstret mste vara en giltig RFC 6570 URI-mall +format.uuid = matchar inte {0}-mnstret mste vara ett giltigt RFC 4122 UUID +format.regex = matchar inte {0}-mnstret mste vara ett giltigt ECMA-262 reguljrt uttryck +format.time = matchar inte {0}-mnstret mste vara en giltig RFC 3339-tid +format.hostname = matchar inte {0}-mnstret mste vara ett giltigt RFC 1123-vrdnamn +format.json-pointer = matchar inte {0}-mnstret mste vara en giltig RFC 6901 JSON-pekare +format.relative-json-pointer = matchar inte {0}-mnstret mste vara en giltig IETF Relativ JSON-pekare +format.unknown = har ett oknt format ''{0}'' +id = ''{0}'' r inte en giltig {1} +items = index ''{0}'' r inte definierat i schemat och schemat tillter inte ytterligare objekt +maxContains = mste vara ett icke-negativt heltal i {0} +maxItems = mste ha hgst {0} objekt men hittade {1} +maxLength = fr vara hgst {0} tecken lng +maxProperties = mste ha hgst {0} egenskaper +maximum = mste ha ett maximalt vrde p {0} +minContains = mste vara ett icke-negativt heltal i {0} +minContainsVsMaxContains = minContains mste vara mindre n eller lika med maxContains i {0} +minItems = mste ha minst {0} objekt men hittade {1} +minLength = mste vara minst {0} tecken lng +minProperties = mste ha minst {0} egenskaper +minimum = mste ha ett lgsta vrde p {0} +multipleOf = mste vara multipel av {0} +not = fr inte vara giltigt fr schemat {0} +notAllowed = egenskapen ''{0}'' r inte tillten men den finns i data +oneOf = mste vara giltigt fr ett och endast ett schema, men {0} r giltiga +oneOf.indexes = mste vara giltigt fr ett och endast ett schema, men {0} r giltiga med index ''{1}'' +pattern = matchar inte regexmnstret {0} +patternProperties = har ngot fel med ''mnsteregenskaper'' +prefixItems = ingen validator hittades i detta index +properties = har ett fel med ''egenskaper'' +propertyNames = egenskapen ''{0}'' namn r inte giltigt: {1} +readOnly = r ett skrivskyddat flt, det kan inte ndras +required = den obligatoriska egenskapen ''{0}'' hittades inte +type = {0} hittades, {1} frvntas +unevaluatedItems = index ''{0}'' utvrderas inte och schemat tillter inte oevaluerade objekt +unevaluatedProperties = egenskapen ''{0}'' utvrderas inte och schemat tillter inte oevaluerade egenskaper +unionType = {0} hittades, {1} frvntas +uniqueItems = fr endast ha unika objekt i arrayen +writeOnly = r ett skrivskyddat flt, det kan inte visas i data +contentEncoding = matchar inte innehllskodning {0} +contentMediaType = r inte ett innehll jag diff --git a/src/main/resources/jsv-messages_th.properties b/src/main/resources/jsv-messages_th.properties new file mode 100644 index 000000000..214954654 --- /dev/null +++ b/src/main/resources/jsv-messages_th.properties @@ -0,0 +1,70 @@ +$ref = \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A ''refs'' +additionalItems = \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{0}'' \u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E16\u0E39\u0E01\u0E01\u0E33\u0E2B\u0E19\u0E14\u0E44\u0E27\u0E49\u0E43\u0E19\u0E2A\u0E04\u0E35\u0E21\u0E32 \u0E41\u0E25\u0E30\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E21\u0E35\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21 +additionalProperties = \u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{0}'' \u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E16\u0E39\u0E01\u0E01\u0E33\u0E2B\u0E19\u0E14\u0E44\u0E27\u0E49\u0E43\u0E19\u0E2A\u0E04\u0E35\u0E21\u0E32 \u0E41\u0E25\u0E30\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21 +allOf = \u0E15\u0E49\u0E2D\u0E07\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14 {0} +anyOf = \u0E15\u0E49\u0E2D\u0E07\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E43\u0E14\u0E46 {0} +const = \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E04\u0E48\u0E32\u0E04\u0E07\u0E17\u0E35\u0E48 ''{0}'' +contains = \u0E44\u0E21\u0E48\u0E21\u0E35\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E17\u0E35\u0E48\u0E1C\u0E48\u0E32\u0E19\u0E01\u0E32\u0E23\u0E15\u0E23\u0E27\u0E08\u0E2A\u0E2D\u0E1A\u0E40\u0E2B\u0E25\u0E48\u0E32\u0E19\u0E35\u0E49: {1} +contains.max = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E21\u0E32\u0E01\u0E17\u0E35\u0E48\u0E2A\u0E38\u0E14 {0} \u0E17\u0E35\u0E48\u0E1C\u0E48\u0E32\u0E19\u0E01\u0E32\u0E23\u0E15\u0E23\u0E27\u0E08\u0E2A\u0E2D\u0E1A\u0E40\u0E2B\u0E25\u0E48\u0E32\u0E19\u0E35\u0E49: {1} +contains.min = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {0} \u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E17\u0E35\u0E48\u0E1C\u0E48\u0E32\u0E19\u0E01\u0E32\u0E23\u0E15\u0E23\u0E27\u0E08\u0E2A\u0E2D\u0E1A\u0E40\u0E2B\u0E25\u0E48\u0E32\u0E19\u0E35\u0E49: {1} +dependencies = \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E02\u0E36\u0E49\u0E19\u0E15\u0E48\u0E2D\u0E01\u0E31\u0E19 {0} +dependentRequired = \u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E02\u0E32\u0E14\u0E2B\u0E32\u0E22\u0E44\u0E1B ''{0}'' \u0E0B\u0E36\u0E48\u0E07\u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E49\u0E2D\u0E07\u0E1E\u0E36\u0E48\u0E07\u0E1E\u0E32\u0E40\u0E19\u0E37\u0E48\u0E2D\u0E07\u0E08\u0E32\u0E01\u0E21\u0E35 ''{1}'' \u0E2D\u0E22\u0E39\u0E48 +dependentSchemas = \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A dependentSchemas {0} +enum = \u0E44\u0E21\u0E48\u0E21\u0E35\u0E04\u0E48\u0E32\u0E43\u0E19\u0E01\u0E32\u0E23\u0E41\u0E08\u0E07\u0E19\u0E31\u0E1A {0} +exclusiveMaximum = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14\u0E1E\u0E34\u0E40\u0E28\u0E29\u0E40\u0E1B\u0E47\u0E19 {0} +exclusiveMinimum = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E15\u0E48\u0E33\u0E2A\u0E38\u0E14\u0E1E\u0E34\u0E40\u0E28\u0E29\u0E40\u0E1B\u0E47\u0E19 {0} +false = \u0E2A\u0E04\u0E35\u0E21\u0E32\u0E2A\u0E33\u0E2B\u0E23\u0E31\u0E1A ''{0}'' \u0E40\u0E1B\u0E47\u0E19\u0E40\u0E17\u0E47\u0E08 +format = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} +format.date = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E27\u0E31\u0E19\u0E17\u0E35\u0E48\u0E40\u0E15\u0E47\u0E21 RFC 3339 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.date-time = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E27\u0E31\u0E19\u0E17\u0E35\u0E48-\u0E40\u0E27\u0E25\u0E32 RFC 3339 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.duration = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E08\u0E30\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E23\u0E30\u0E22\u0E30\u0E40\u0E27\u0E25\u0E32 ISO 8601 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.email = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E01\u0E25\u0E48\u0E2D\u0E07\u0E08\u0E14\u0E2B\u0E21\u0E32\u0E22 RFC 5321 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.ipv4 = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E17\u0E35\u0E48\u0E2D\u0E22\u0E39\u0E48 IP RFC 2673 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.ipv6 = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E17\u0E35\u0E48\u0E2D\u0E22\u0E39\u0E48 IP RFC 4291 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.idn-email = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E01\u0E25\u0E48\u0E2D\u0E07\u0E08\u0E14\u0E2B\u0E21\u0E32\u0E22 RFC 6531 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.idn-hostname = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E0A\u0E37\u0E48\u0E2D\u0E42\u0E2E\u0E2A\u0E15\u0E4C\u0E2A\u0E32\u0E01\u0E25 RFC 5890 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.iri = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3987 IRI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.iri-reference = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3987 IRI-reference \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.uri = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3986 URI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.uri-reference = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E01\u0E32\u0E23\u0E2D\u0E49\u0E32\u0E07\u0E2D\u0E34\u0E07 RFC 3986 URI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.uri-template = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E40\u0E17\u0E21\u0E40\u0E1E\u0E25\u0E15 RFC 6570 URI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.uuid = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 4122 UUID \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.regex = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E19\u0E34\u0E1E\u0E08\u0E19\u0E4C\u0E17\u0E31\u0E48\u0E27\u0E44\u0E1B ECMA-262 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.time = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E40\u0E27\u0E25\u0E32 RFC 3339 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.hostname = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E0A\u0E37\u0E48\u0E2D\u0E42\u0E2E\u0E2A\u0E15\u0E4C RFC 1123 \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.json-pointer = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E08\u0E30\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E31\u0E27\u0E0A\u0E35\u0E49 RFC 6901 JSON \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.relative-json-pointer = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {0} \u0E08\u0E30\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E31\u0E27\u0E0A\u0E35\u0E49 JSON \u0E41\u0E1A\u0E1A\u0E2A\u0E31\u0E21\u0E1E\u0E31\u0E19\u0E18\u0E4C\u0E02\u0E2D\u0E07 IETF \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +format.unknown = \u0E21\u0E35\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E23\u0E39\u0E49\u0E08\u0E31\u0E01 ''{0}'' +id = ''{0}'' \u0E44\u0E21\u0E48\u0E43\u0E0A\u0E48 {1} \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +items = \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{0}'' \u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E16\u0E39\u0E01\u0E01\u0E33\u0E2B\u0E19\u0E14\u0E44\u0E27\u0E49\u0E43\u0E19\u0E2A\u0E04\u0E35\u0E21\u0E32 \u0E41\u0E25\u0E30\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E21\u0E35\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21 +maxContains = \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E08\u0E33\u0E19\u0E27\u0E19\u0E40\u0E15\u0E47\u0E21\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E40\u0E1B\u0E47\u0E19\u0E25\u0E1A\u0E43\u0E19 {0} +maxItems = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E21\u0E32\u0E01\u0E17\u0E35\u0E48\u0E2A\u0E38\u0E14 {0} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23 \u0E41\u0E15\u0E48\u0E1E\u0E1A {1} +maxLength = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E27\u0E32\u0E21\u0E22\u0E32\u0E27\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 {0} \u0E2D\u0E31\u0E01\u0E02\u0E23\u0E30 +maxProperties = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 {0} +maximum = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14\u0E40\u0E1B\u0E47\u0E19 {0} +minContains = \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E08\u0E33\u0E19\u0E27\u0E19\u0E40\u0E15\u0E47\u0E21\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E40\u0E1B\u0E47\u0E19\u0E25\u0E1A\u0E43\u0E19 {0} +minContainsVsMaxContains = minContains \u0E15\u0E49\u0E2D\u0E07\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32\u0E2B\u0E23\u0E37\u0E2D\u0E40\u0E17\u0E48\u0E32\u0E01\u0E31\u0E1A maxContains \u0E43\u0E19 {0} +minItems = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {0} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23 \u0E41\u0E15\u0E48\u0E1E\u0E1A {1} +minLength = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E27\u0E32\u0E21\u0E22\u0E32\u0E27\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {0} \u0E2D\u0E31\u0E01\u0E02\u0E23\u0E30 +minProperties = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {0} +minimum = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E15\u0E48\u0E33\u0E2A\u0E38\u0E14\u0E40\u0E1B\u0E47\u0E19 {0} +multipleOf = \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E1C\u0E25\u0E04\u0E39\u0E13\u0E02\u0E2D\u0E07 {0} +not = \u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32 {0} +notAllowed = \u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E43\u0E0A\u0E49\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{0}'' \u0E41\u0E15\u0E48\u0E2D\u0E22\u0E39\u0E48\u0E43\u0E19\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25 +oneOf = \u0E15\u0E49\u0E2D\u0E07\u0E43\u0E0A\u0E49\u0E44\u0E14\u0E49\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E40\u0E14\u0E35\u0E22\u0E27\u0E40\u0E17\u0E48\u0E32\u0E19\u0E31\u0E49\u0E19 \u0E41\u0E15\u0E48 {0} \u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 +oneOf.indexes = \u0E15\u0E49\u0E2D\u0E07\u0E43\u0E0A\u0E49\u0E44\u0E14\u0E49\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E40\u0E14\u0E35\u0E22\u0E27\u0E40\u0E17\u0E48\u0E32\u0E19\u0E31\u0E49\u0E19 \u0E41\u0E15\u0E48 {0} \u0E43\u0E0A\u0E49\u0E44\u0E14\u0E49\u0E01\u0E31\u0E1A\u0E14\u0E31\u0E0A\u0E19\u0E35 ''{1}'' +pattern = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A regex {0} +patternProperties = \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E1A\u0E32\u0E07\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E01\u0E31\u0E1A ''\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A'' +prefixItems = \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E40\u0E04\u0E23\u0E37\u0E48\u0E2D\u0E07\u0E21\u0E37\u0E2D\u0E15\u0E23\u0E27\u0E08\u0E2A\u0E2D\u0E1A\u0E04\u0E27\u0E32\u0E21\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E43\u0E19\u0E14\u0E31\u0E0A\u0E19\u0E35\u0E19\u0E35\u0E49 +properties = \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A ''\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34'' +propertyNames = \u0E0A\u0E37\u0E48\u0E2D\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{0}'' \u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07: {1} +readOnly = \u0E40\u0E1B\u0E47\u0E19\u0E1F\u0E34\u0E25\u0E14\u0E4C\u0E41\u0E1A\u0E1A\u0E2D\u0E48\u0E32\u0E19\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E40\u0E14\u0E35\u0E22\u0E27 \u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E40\u0E1B\u0E25\u0E35\u0E48\u0E22\u0E19\u0E41\u0E1B\u0E25\u0E07\u0E44\u0E14\u0E49 +required = \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E17\u0E35\u0E48\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23 ''{0}'' +type = \u0E1E\u0E1A {0}, \u0E04\u0E32\u0E14\u0E2B\u0E27\u0E31\u0E07 {1} +unevaluatedItems = \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{0}'' \u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E1B\u0E23\u0E30\u0E40\u0E21\u0E34\u0E19 \u0E41\u0E25\u0E30\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E1B\u0E23\u0E30\u0E40\u0E21\u0E34\u0E19 +unevaluatedProperties = \u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{0}'' \u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E1B\u0E23\u0E30\u0E40\u0E21\u0E34\u0E19 \u0E41\u0E25\u0E30\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E1B\u0E23\u0E30\u0E40\u0E21\u0E34\u0E19 +unionType = \u0E1E\u0E1A {0}, \u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23 {1} +uniqueItems = \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E40\u0E09\u0E1E\u0E32\u0E30\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E0B\u0E49\u0E33\u0E43\u0E19\u0E2D\u0E32\u0E23\u0E4C\u0E40\u0E23\u0E22\u0E4C +writeOnly = \u0E40\u0E1B\u0E47\u0E19\u0E1F\u0E34\u0E25\u0E14\u0E4C\u0E41\u0E1A\u0E1A\u0E40\u0E02\u0E35\u0E22\u0E19\u0E40\u0E17\u0E48\u0E32\u0E19\u0E31\u0E49\u0E19 \u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E1B\u0E23\u0E32\u0E01\u0E0F\u0E43\u0E19\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25\u0E44\u0E14\u0E49 +contentEncoding = \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E23\u0E2B\u0E31\u0E2A\u0E40\u0E19\u0E37\u0E49\u0E2D\u0E2B\u0E32 {0} +contentMediaType = \u0E44\u0E21\u0E48\u0E43\u0E0A\u0E48\u0E40\u0E19\u0E37\u0E49\u0E2D\u0E2B\u0E32\u0E02\u0E2D\u0E07\u0E09\u0E31\u0E19 diff --git a/src/main/resources/jsv-messages_tr.properties b/src/main/resources/jsv-messages_tr.properties new file mode 100644 index 000000000..f9e69bf7a --- /dev/null +++ b/src/main/resources/jsv-messages_tr.properties @@ -0,0 +1,70 @@ +$ref = ''refs'' ile ilgili bir hata var +additionalItems = ''{0}'' dizini \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek \u011Felere izin vermiyor +additionalProperties = ''{0}'' zelli\u011Fi \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek zelliklere izin vermiyor +allOf = tm {0} \u015Femalar\u0131 iin geerli olmal\u0131d\u0131r +anyOf = {0} \u015Femalar\u0131ndan herhangi biri iin geerli olmal\u0131d\u0131r +const = ''{0}'' sabit de\u011Feri olmal\u0131d\u0131r +contains = bu do\u011Frulamalar\u0131 geen bir \u011Fe iermiyor: {1} +contains.max = \u015Fu do\u011Frulamalar\u0131 geen en fazla {0} \u011Fe iermelidir: {1} +contains.min = \u015Fu do\u011Frulamalar\u0131 geen en az {0} \u011Fe iermelidir: {1} +dependencies = ba\u011F\u0131ml\u0131l\u0131klarda hata var {0} +dependentRequired = ''{0}'' eksik bir zelli\u011Fi var ve ''{1}'' mevcut oldu\u011Fundan ba\u011F\u0131ml\u0131 gerekli +dependentSchemas = DependedSchemas {0} ile ilgili bir hata var +enum = {0} numaraland\u0131rmas\u0131nda bir de\u011Fer yok +exclusiveMaximum = zel maksimum de\u011Feri {0} olmal\u0131d\u0131r +exclusiveMinimum = zel minimum de\u011Feri {0} olmal\u0131d\u0131r +false = ''{0}'' \u015Femas\u0131 yanl\u0131\u015F +format = {0} modeliyle e\u015Fle\u015Fmiyor +format.date = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3339 tam tarihi olmal\u0131d\u0131r +format.date-time = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3339 tarih-saat olmal\u0131d\u0131r +format.duration = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir ISO 8601 sresi olmal\u0131d\u0131r +format.email = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 5321 Posta Kutusu olmal\u0131d\u0131r +format.ipv4 = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 2673 IP adresi olmal\u0131d\u0131r +format.ipv6 = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 4291 IP adresi olmal\u0131d\u0131r +format.idn-email = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 6531 Posta Kutusu olmal\u0131d\u0131r +format.idn-hostname = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 5890 uluslararas\u0131la\u015Ft\u0131r\u0131lm\u0131\u015F ana bilgisayar ad\u0131 olmal\u0131d\u0131r +format.iri = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3987 IRI olmal\u0131d\u0131r +format.iri-reference = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3987 IRI referans\u0131 olmal\u0131d\u0131r +format.uri = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3986 URI olmal\u0131d\u0131r +format.uri-reference = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3986 URI referans\u0131 olmal\u0131d\u0131r +format.uri-template = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 6570 URI \u015Eablonu olmal\u0131d\u0131r +format.uuid = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 4122 UUID olmal\u0131d\u0131r +format.regex = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir ECMA-262 normal ifadesi olmal\u0131d\u0131r +format.time = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 3339 saati olmal\u0131d\u0131r +format.hostname = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 1123 ana bilgisayar ad\u0131 olmal\u0131d\u0131r +format.json-pointer = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir RFC 6901 JSON \u0130\u015Faretisi olmal\u0131d\u0131r +format.relative-json-pointer = {0} modeliyle e\u015Fle\u015Fmiyor, geerli bir IETF Greli JSON \u0130\u015Faretisi olmal\u0131d\u0131r +format.unknown = bilinmeyen bir formata sahip ''{0}'' +id = ''{0}'' geerli bir {1} de\u011Fil +items = ''{0}'' dizini \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek \u011Felere izin vermiyor +maxContains = {0}''de negatif olmayan bir tamsay\u0131 olmal\u0131d\u0131r +maxItems = en fazla {0} \u011Feye sahip olmal\u0131 ancak {1} bulundu +maxLength = en fazla {0} karakter uzunlu\u011Funda olmal\u0131d\u0131r +maxProperties = en fazla {0} zelli\u011Fe sahip olmal\u0131d\u0131r +maximum = maksimum de\u011Feri {0} olmal\u0131d\u0131r +minContains = {0}''de negatif olmayan bir tam say\u0131 olmal\u0131d\u0131r +minContainsVsMaxContains = minContains, {0} iindeki maxContains de\u011Ferinden kk veya ona e\u015Fit olmal\u0131d\u0131r +minItems = en az {0} \u011Feye sahip olmal\u0131 ancak {1} bulundu +minLength = en az {0} karakter uzunlu\u011Funda olmal\u0131d\u0131r +minProperties = en az {0} zelli\u011Fe sahip olmal\u0131d\u0131r +minimum = minimum de\u011Feri {0} olmal\u0131d\u0131r +multipleOf = {0}''\u0131n kat\u0131 olmal\u0131d\u0131r +not = {0} \u015Femas\u0131 iin geerli olmamal\u0131d\u0131r +notAllowed = ''{0}'' zelli\u011Fine izin verilmiyor ancak verilerde mevcut +oneOf = yaln\u0131zca bir \u015Fema iin geerli olmal\u0131d\u0131r, ancak {0} geerlidir +oneOf.indexes = yaln\u0131zca bir \u015Fema iin geerli olmal\u0131d\u0131r, ancak {0} ''{1}'' dizinleriyle geerlidir +pattern = normal ifade modeli {0} ile e\u015Fle\u015Fmiyor +patternProperties = ''desen zelliklerinde'' baz\u0131 hatalar var +prefixItems = bu dizinde do\u011Frulay\u0131c\u0131 bulunamad\u0131 +properties = ''zellikler'' ile ilgili bir hata var +propertyNames = ''{0}'' zelli\u011Finin ad\u0131 geerli de\u011Fil: {1} +readOnly = salt okunur bir aland\u0131r, de\u011Fi\u015Ftirilemez +required = gerekli ''{0}'' zelli\u011Fi bulunamad\u0131 +type = {0} bulundu, {1} bekleniyor +unevaluatedItems = ''{0}'' dizini de\u011Ferlendirilmez ve \u015Fema, de\u011Ferlendirilmemi\u015F \u011Felere izin vermez +unevaluatedProperties = ''{0}'' zelli\u011Fi de\u011Ferlendirilmez ve \u015Fema, de\u011Ferlendirilmemi\u015F zelliklere izin vermez +unionType = {0} bulundu, {1} bekleniyor +uniqueItems = dizide yaln\u0131zca benzersiz \u011Feler bulunmal\u0131d\u0131r +writeOnly = salt yaz\u0131l\u0131r bir aland\u0131r, verilerde grnemez +contentEncoding = ierik kodlamas\u0131 {0} ile e\u015Fle\u015Fmiyor +contentMediaType = bir ierik de\u011Fil diff --git a/src/main/resources/jsv-messages_uk.properties b/src/main/resources/jsv-messages_uk.properties new file mode 100644 index 000000000..0a2734405 --- /dev/null +++ b/src/main/resources/jsv-messages_uk.properties @@ -0,0 +1,70 @@ +$ref = \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043C\u0438\u043B\u043A\u0443 \u0437 ''refs'' +additionalItems = \u0456\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u0432\u0438\u0437\u043D\u0430\u0447\u0435\u043D\u043E \u0432 \u0441\u0445\u0435\u043C\u0456, \u0456 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u044F\u0454 \u0434\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0438 +additionalProperties = \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{0}'' \u043D\u0435 \u0432\u0438\u0437\u043D\u0430\u0447\u0435\u043D\u043E \u0432 \u0441\u0445\u0435\u043C\u0456, \u0456 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u044F\u0454 \u0434\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456 +allOf = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u0432\u0441\u0456\u0445 \u0441\u0445\u0435\u043C {0} +anyOf = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u0431\u0443\u0434\u044C-\u044F\u043A\u043E\u0457 \u0437\u0456 \u0441\u0445\u0435\u043C {0} +const = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043F\u043E\u0441\u0442\u0456\u0439\u043D\u0438\u043C \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F\u043C ''{0}'' +contains = \u043D\u0435 \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u0435\u043B\u0435\u043C\u0435\u043D\u0442, \u044F\u043A\u0438\u0439 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442\u044C \u0446\u0456 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u043A\u0438: {1} +contains.max = \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {0} \u0435\u043B\u0435\u043C\u0435\u043D\u0442(\u0456\u0432), \u044F\u043A\u0456 \u043F\u0440\u043E\u0445\u043E\u0434\u044F\u0442\u044C \u0446\u0456 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u043A\u0438: {1} +contains.min = \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {0} \u0435\u043B\u0435\u043C\u0435\u043D\u0442(\u0456\u0432), \u044F\u043A\u0456 \u043F\u0440\u043E\u0445\u043E\u0434\u044F\u0442\u044C \u0446\u0456 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u043A\u0438: {1} +dependencies = \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043C\u0438\u043B\u043A\u0443 \u0456\u0437 \u0437\u0430\u043B\u0435\u0436\u043D\u043E\u0441\u0442\u044F\u043C\u0438 {0} +dependentRequired = \u043C\u0430\u0454 \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u044E \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{0}'', \u044F\u043A\u0430 \u0454 \u0437\u0430\u043B\u0435\u0436\u043D\u043E\u044E, \u043E\u0441\u043A\u0456\u043B\u044C\u043A\u0438 \u043F\u0440\u0438\u0441\u0443\u0442\u043D\u044F ''{1}'' +dependentSchemas = \u0454 \u043F\u043E\u043C\u0438\u043B\u043A\u0430 \u0437 dependentSchemas {0} +enum = \u043D\u0435 \u043C\u0430\u0454 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F \u0432 \u043F\u0435\u0440\u0435\u043B\u0456\u043A\u0443 {0} +exclusiveMaximum = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u0432\u0438\u043D\u044F\u0442\u043A\u043E\u0432\u0435 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F {0} +exclusiveMinimum = \u043F\u043E\u0432\u0438\u043D\u043D\u043E \u043C\u0430\u0442\u0438 \u0432\u0438\u043D\u044F\u0442\u043A\u043E\u0432\u0435 \u043C\u0456\u043D\u0456\u043C\u0430\u043B\u044C\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F {0} +false = \u0441\u0445\u0435\u043C\u0430 \u0434\u043B\u044F ''{0}'' \u043D\u0435\u0432\u0456\u0440\u043D\u0430 +format = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} +format.date = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E \u043F\u043E\u0432\u043D\u043E\u044E \u0434\u0430\u0442\u043E\u044E RFC 3339 +format.date-time = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E \u0434\u0430\u0442\u043E\u044E-\u0447\u0430\u0441\u043E\u043C RFC 3339 +format.duration = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E \u0442\u0440\u0438\u0432\u0430\u043B\u0456\u0441\u0442\u044E ISO 8601 +format.email = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E \u043F\u043E\u0448\u0442\u043E\u0432\u043E\u044E \u0441\u043A\u0440\u0438\u043D\u044C\u043A\u043E\u044E RFC 5321 +format.ipv4 = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E IP-\u0430\u0434\u0440\u0435\u0441\u043E\u044E RFC 2673 +format.ipv6 = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E IP-\u0430\u0434\u0440\u0435\u0441\u043E\u044E RFC 4291 +format.idn-email = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u043E\u044E \u043F\u043E\u0448\u0442\u043E\u0432\u043E\u044E \u0441\u043A\u0440\u0438\u043D\u044C\u043A\u043E\u044E RFC 6531 +format.idn-hostname = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0456\u043D\u0442\u0435\u0440\u043D\u0430\u0446\u0456\u043E\u043D\u0430\u043B\u0456\u0437\u043E\u0432\u0430\u043D\u0438\u043C \u0456\u043C\u2019\u044F\u043C \u0445\u043E\u0441\u0442\u0430 RFC 5890 +format.iri = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C RFC 3987 IRI +format.iri-reference = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F\u043C IRI RFC 3987 +format.uri = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C URI RFC 3986 +format.uri-reference = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F\u043C URI RFC 3986 +format.uri-template = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0448\u0430\u0431\u043B\u043E\u043D\u043E\u043C URI RFC 6570 +format.uuid = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C UUID RFC 4122 +format.regex = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u0438\u043C \u0432\u0438\u0440\u0430\u0437\u043E\u043C ECMA-262 +format.time = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0447\u0430\u0441\u043E\u043C RFC 3339 +format.hostname = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0} \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0456\u043C\u2019\u044F\u043C \u0445\u043E\u0441\u0442\u0430 RFC 1123 +format.json-pointer = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C RFC 6901 JSON Pointer +format.relative-json-pointer = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {0}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0432\u0456\u0434\u043D\u043E\u0441\u043D\u0438\u043C \u043F\u043E\u043A\u0430\u0436\u0447\u0438\u043A\u043E\u043C JSON IETF +format.unknown = \u043C\u0430\u0454 \u043D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ''{0}'' +id = ''{0}'' \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0438\u0439 {1} +items = \u0456\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u0432\u0438\u0437\u043D\u0430\u0447\u0435\u043D\u043E \u0432 \u0441\u0445\u0435\u043C\u0456, \u0456 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u044F\u0454 \u0434\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0438\u0445 \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0456\u0432 +maxContains = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043D\u0435\u0432\u0456\u0434\u2019\u0454\u043C\u043D\u0438\u043C \u0446\u0456\u043B\u0438\u043C \u0447\u0438\u0441\u043B\u043E\u043C \u0443 {0} +maxItems = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {0} \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0456\u0432, \u0430\u043B\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {1} +maxLength = \u043D\u0435 \u0431\u0456\u043B\u044C\u0448\u0435 \u043D\u0456\u0436 {0} \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432 +maxProperties = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {0} \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0435\u0439 +maximum = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F {0} +minContains = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043D\u0435\u0432\u0456\u0434\u2019\u0454\u043C\u043D\u0438\u043C \u0446\u0456\u043B\u0438\u043C \u0447\u0438\u0441\u043B\u043E\u043C \u0443 {0} +minContainsVsMaxContains = minContains \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043C\u0435\u043D\u0448\u0438\u043C \u0430\u0431\u043E \u0434\u043E\u0440\u0456\u0432\u043D\u044E\u0432\u0430\u0442\u0438 maxContains \u0443 {0} +minItems = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {0} \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0456\u0432, \u0430\u043B\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {1} +minLength = \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {0} \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432 +minProperties = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {0} \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0435\u0439 +minimum = \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u043C\u0456\u043D\u0456\u043C\u0430\u043B\u044C\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F {0} +multipleOf = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043A\u0440\u0430\u0442\u043D\u0438\u043C {0} +not = \u043D\u0435 \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u0441\u0445\u0435\u043C\u0438 {0} +notAllowed = \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{0}'' \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u0435\u043D\u0430, \u0430\u043B\u0435 \u0432\u043E\u043D\u0430 \u0454 \u0432 \u0434\u0430\u043D\u0438\u0445 +oneOf = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u043E\u0434\u043D\u0456\u0454\u0457 \u0439 \u043B\u0438\u0448\u0435 \u043E\u0434\u043D\u0456\u0454\u0457 \u0441\u0445\u0435\u043C\u0438, \u0430\u043B\u0435 {0} \u0454 \u0434\u0456\u0439\u0441\u043D\u0438\u043C\u0438 +oneOf.indexes = \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u043E\u0434\u043D\u0456\u0454\u0457 \u0439 \u043B\u0438\u0448\u0435 \u043E\u0434\u043D\u0456\u0454\u0457 \u0441\u0445\u0435\u043C\u0438, \u0430\u043B\u0435 {0} \u0454 \u0434\u0456\u0439\u0441\u043D\u0438\u043C\u0438 \u0437 \u0456\u043D\u0434\u0435\u043A\u0441\u0430\u043C\u0438 ''{1}'' +pattern = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u043E\u0433\u043E \u0432\u0438\u0440\u0430\u0437\u0443 {0} +patternProperties = \u0454 \u0434\u0435\u044F\u043A\u0430 \u043F\u043E\u043C\u0438\u043B\u043A\u0430 \u0437 ''\u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456 \u0448\u0430\u0431\u043B\u043E\u043D\u0443'' +prefixItems = \u0437\u0430 \u0446\u0438\u043C \u0456\u043D\u0434\u0435\u043A\u0441\u043E\u043C \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0432\u0430\u043B\u0456\u0434\u0430\u0442\u043E\u0440\u0430 +properties = \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043C\u0438\u043B\u043A\u0443 \u0437 ''\u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456'' +propertyNames = \u043D\u0430\u0437\u0432\u0430 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456 ''{0}'' \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0430: {1} +readOnly = \u0446\u0435 \u043F\u043E\u043B\u0435 \u043B\u0438\u0448\u0435 \u0434\u043B\u044F \u0447\u0438\u0442\u0430\u043D\u043D\u044F, \u0439\u043E\u0433\u043E \u043D\u0435 \u043C\u043E\u0436\u043D\u0430 \u0437\u043C\u0456\u043D\u0438\u0442\u0438 +required = \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u0430 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{0}'' \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u0430 +type = \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {0}, \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F {1} +unevaluatedItems = \u0456\u043D\u0434\u0435\u043A\u0441 ''{0}'' \u043D\u0435 \u043E\u0446\u0456\u043D\u044E\u0454\u0442\u044C\u0441\u044F, \u0456 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u044F\u0454 \u043D\u0435 \u043E\u0446\u0456\u043D\u0435\u043D\u0456 \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0438 +unevaluatedProperties = \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{0}'' \u043D\u0435 \u043E\u0446\u0456\u043D\u044E\u0454\u0442\u044C\u0441\u044F, \u0430 \u0441\u0445\u0435\u043C\u0430 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043B\u044F\u0454 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456 \u0431\u0435\u0437 \u043E\u0446\u0456\u043D\u043A\u0438 +unionType = \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {0}, \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F {1} +uniqueItems = \u0443 \u043C\u0430\u0441\u0438\u0432\u0456 \u043F\u043E\u0432\u0438\u043D\u043D\u0456 \u0431\u0443\u0442\u0438 \u043B\u0438\u0448\u0435 \u0443\u043D\u0456\u043A\u0430\u043B\u044C\u043D\u0456 \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0438 +writeOnly = \u0446\u0435 \u043F\u043E\u043B\u0435 \u043B\u0438\u0448\u0435 \u0434\u043B\u044F \u0437\u0430\u043F\u0438\u0441\u0443, \u0432\u043E\u043D\u043E \u043D\u0435 \u043C\u043E\u0436\u0435 \u0432\u0456\u0434\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u0438\u0441\u044F \u0432 \u0434\u0430\u043D\u0438\u0445 +contentEncoding = \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u043A\u043E\u0434\u0443\u0432\u0430\u043D\u043D\u044E \u0432\u043C\u0456\u0441\u0442\u0443 {0} +contentMediaType = \u043D\u0435 \u0454 \u0432\u043C\u0456\u0441\u0442\u043E\u043C me diff --git a/src/main/resources/jsv-messages_vi.properties b/src/main/resources/jsv-messages_vi.properties new file mode 100644 index 000000000..7e7f33ec5 --- /dev/null +++ b/src/main/resources/jsv-messages_vi.properties @@ -0,0 +1,70 @@ +$ref = c l\u1ED7i ''refs'' +additionalItems = ch\u1EC9 m\u1EE5c ''{0}'' khng \u0111\u01B0\u1EE3c xc \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 v l\u01B0\u1EE3c \u0111\u1ED3 khng cho php cc m\u1EE5c b\u1ED5 sung +additionalProperties = thu\u1ED9c tnh ''{0}'' khng \u0111\u01B0\u1EE3c xc \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 v l\u01B0\u1EE3c \u0111\u1ED3 khng cho php cc thu\u1ED9c tnh b\u1ED5 sung +allOf = ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi t\u1EA5t c\u1EA3 l\u01B0\u1EE3c \u0111\u1ED3 {0} +anyOf = ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi b\u1EA5t k\u1EF3 l\u01B0\u1EE3c \u0111\u1ED3 no {0} +const = ph\u1EA3i l gi tr\u1ECB khng \u0111\u1ED5i ''{0}'' +contains = khng ch\u1EE9a ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua cc xc nh\u1EADn ny: {1} +contains.max = ph\u1EA3i ch\u1EE9a t\u1ED1i \u0111a {0} ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua cc xc th\u1EF1c ny: {1} +contains.min = ph\u1EA3i ch\u1EE9a t nh\u1EA5t {0} ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua cc xc nh\u1EADn ny: {1} +dependencies = c l\u1ED7i v\u1EDBi ph\u1EE5 thu\u1ED9c {0} +dependentRequired = thi\u1EBFu thu\u1ED9c tnh ''{0}'' thu\u1ED9c tnh ph\u1EE5 thu\u1ED9c b\u1EAFt bu\u1ED9c v ''{1}'' hi\u1EC7n di\u1EC7n +dependentSchemas = c l\u1ED7i v\u1EDBi dependencySchemas {0} +enum = khng c gi tr\u1ECB trong b\u1EA3ng li\u1EC7t k {0} +exclusiveMaximum = ph\u1EA3i c gi tr\u1ECB t\u1ED1i \u0111a \u0111\u1ED9c quy\u1EC1n l {0} +exclusiveMinimum = ph\u1EA3i c gi tr\u1ECB t\u1ED1i thi\u1EC3u duy nh\u1EA5t l {0} +false = l\u01B0\u1EE3c \u0111\u1ED3 cho ''{0}'' l sai +format = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} +format.date = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l ngy \u0111\u1EA7y \u0111\u1EE7 RFC 3339 h\u1EE3p l\u1EC7 +format.date-time = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l ngy gi\u1EDD h\u1EE3p l\u1EC7 RFC 3339 +format.duration = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i c th\u1EDDi l\u01B0\u1EE3ng ISO 8601 h\u1EE3p l\u1EC7 +format.email = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l H\u1ED9p th\u01B0 RFC 5321 h\u1EE3p l\u1EC7 +format.ipv4 = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l \u0111\u1ECBa ch\u1EC9 IP RFC 2673 h\u1EE3p l\u1EC7 +format.ipv6 = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l \u0111\u1ECBa ch\u1EC9 IP RFC 4291 h\u1EE3p l\u1EC7 +format.idn-email = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l H\u1ED9p th\u01B0 RFC 6531 h\u1EE3p l\u1EC7 +format.idn-hostname = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l tn my ch\u1EE7 \u0111\u01B0\u1EE3c qu\u1ED1c t\u1EBF ha RFC 5890 h\u1EE3p l\u1EC7 +format.iri = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l RFC 3987 IRI h\u1EE3p l\u1EC7 +format.iri-reference = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l tham chi\u1EBFu IRI RFC 3987 h\u1EE3p l\u1EC7 +format.uri = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l URI RFC 3986 h\u1EE3p l\u1EC7 +format.uri-reference = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l tham chi\u1EBFu URI RFC 3986 h\u1EE3p l\u1EC7 +format.uri-template = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l M\u1EABu URI RFC 6570 h\u1EE3p l\u1EC7 +format.uuid = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l UUID RFC 4122 h\u1EE3p l\u1EC7 +format.regex = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l bi\u1EC3u th\u1EE9c chnh quy ECMA-262 h\u1EE3p l\u1EC7 +format.time = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l th\u1EDDi gian RFC 3339 h\u1EE3p l\u1EC7 +format.hostname = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l tn my ch\u1EE7 RFC 1123 h\u1EE3p l\u1EC7 +format.json-pointer = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l Con tr\u1ECF JSON RFC 6901 h\u1EE3p l\u1EC7 +format.relative-json-pointer = khng kh\u1EDBp v\u1EDBi m\u1EABu {0} ph\u1EA3i l Con tr\u1ECF JSON t\u01B0\u01A1ng \u0111\u1ED1i c\u1EE7a IETF h\u1EE3p l\u1EC7 +format.unknown = c \u0111\u1ECBnh d\u1EA1ng khng xc \u0111\u1ECBnh ''{0}'' +id = ''{0}'' khng ph\u1EA3i l {1} h\u1EE3p l\u1EC7 +items = ch\u1EC9 m\u1EE5c ''{0}'' khng \u0111\u01B0\u1EE3c xc \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 v l\u01B0\u1EE3c \u0111\u1ED3 khng cho php cc m\u1EE5c b\u1ED5 sung +maxContains = ph\u1EA3i l s\u1ED1 nguyn khng m trong {0} +maxItems = ph\u1EA3i c t\u1ED1i \u0111a {0} m\u1EE5c nh\u01B0ng \u0111 tm th\u1EA5y {1} +maxLength = ph\u1EA3i di t\u1ED1i \u0111a {0} k t\u1EF1 +maxProperties = ph\u1EA3i c t\u1ED1i \u0111a {0} thu\u1ED9c tnh +maximum = ph\u1EA3i c gi tr\u1ECB t\u1ED1i \u0111a l {0} +minContains = ph\u1EA3i l s\u1ED1 nguyn khng m trong {0} +minContainsVsMaxContains = minContains ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng maxContains trong {0} +minItems = ph\u1EA3i c t nh\u1EA5t {0} m\u1EE5c nh\u01B0ng \u0111 tm th\u1EA5y {1} +minLength = ph\u1EA3i di t nh\u1EA5t {0} k t\u1EF1 +minProperties = ph\u1EA3i c t nh\u1EA5t {0} thu\u1ED9c tnh +minimum = ph\u1EA3i c gi tr\u1ECB t\u1ED1i thi\u1EC3u l {0} +multipleOf = ph\u1EA3i l b\u1ED9i s\u1ED1 c\u1EE7a {0} +not = khng \u0111\u01B0\u1EE3c h\u1EE3p l\u1EC7 \u0111\u1ED1i v\u1EDBi l\u01B0\u1EE3c \u0111\u1ED3 {0} +notAllowed = thu\u1ED9c tnh ''{0}'' khng \u0111\u01B0\u1EE3c php nh\u01B0ng n c trong d\u1EEF li\u1EC7u +oneOf = ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi m\u1ED9t v ch\u1EC9 m\u1ED9t l\u01B0\u1EE3c \u0111\u1ED3, nh\u01B0ng {0} h\u1EE3p l\u1EC7 +oneOf.indexes = ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi m\u1ED9t v ch\u1EC9 m\u1ED9t l\u01B0\u1EE3c \u0111\u1ED3, nh\u01B0ng {0} h\u1EE3p l\u1EC7 v\u1EDBi cc ch\u1EC9 m\u1EE5c ''{1}'' +pattern = khng kh\u1EDBp v\u1EDBi m\u1EABu bi\u1EC3u th\u1EE9c chnh quy {0} +patternProperties = c m\u1ED9t s\u1ED1 l\u1ED7i v\u1EDBi ''thu\u1ED9c tnh m\u1EABu'' +prefixItems = khng tm th\u1EA5y trnh xc th\u1EF1c no t\u1EA1i ch\u1EC9 m\u1EE5c ny +properties = c l\u1ED7i ''thu\u1ED9c tnh'' +propertyNames = tn thu\u1ED9c tnh ''{0}'' khng h\u1EE3p l\u1EC7: {1} +readOnly = l tr\u01B0\u1EDDng ch\u1EC9 \u0111\u1ECDc, khng th\u1EC3 thay \u0111\u1ED5i +required = khng tm th\u1EA5y thu\u1ED9c tnh b\u1EAFt bu\u1ED9c ''{0}'' +type = \u0111 tm th\u1EA5y {0}, mong \u0111\u1EE3i {1} +unevaluatedItems = ch\u1EC9 m\u1EE5c ''{0}'' khng \u0111\u01B0\u1EE3c \u0111nh gi v l\u01B0\u1EE3c \u0111\u1ED3 khng cho php cc m\u1EE5c khng \u0111\u01B0\u1EE3c \u0111nh gi +unevaluatedProperties = thu\u1ED9c tnh ''{0}'' khng \u0111\u01B0\u1EE3c \u0111nh gi v l\u01B0\u1EE3c \u0111\u1ED3 khng cho php cc thu\u1ED9c tnh khng \u0111\u01B0\u1EE3c \u0111nh gi +unionType = \u0111 tm th\u1EA5y {0}, mong \u0111\u1EE3i {1} +uniqueItems = ch\u1EC9 \u0111\u01B0\u1EE3c c cc m\u1EE5c duy nh\u1EA5t trong m\u1EA3ng +writeOnly = l tr\u01B0\u1EDDng ch\u1EC9 ghi, khng xu\u1EA5t hi\u1EC7n trong d\u1EEF li\u1EC7u +contentEncoding = khng kh\u1EDBp v\u1EDBi m ha n\u1ED9i dung {0} +contentMediaType = khng ph\u1EA3i l n\u1ED9i dung c\u1EE7a ti diff --git a/src/main/resources/jsv-messages_zh_CN.properties b/src/main/resources/jsv-messages_zh_CN.properties index 7b5e63608..be6defbbe 100644 --- a/src/main/resources/jsv-messages_zh_CN.properties +++ b/src/main/resources/jsv-messages_zh_CN.properties @@ -1,40 +1,70 @@ -additionalProperties = {0}.{1}\uFF1A\u672A\u5728\u67B6\u6784\u4E2D\u5B9A\u4E49\u4E14\u67B6\u6784\u4E0D\u5141\u8BB8\u9644\u52A0\u5C5E\u6027 -allOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u6240\u6709\u6A21\u5F0F {1} \u90FD\u6709\u6548 -anyOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u4EFB\u4F55\u67B6\u6784 {1} \u90FD\u6709\u6548 -crossEdits = {0}\uFF1A\u201C\u4EA4\u53C9\u7F16\u8F91\u201D\u6709\u9519\u8BEF -dependencies = {0}\uFF1A\u4F9D\u8D56\u9879 {1} \u6709\u9519\u8BEF -dependentRequired = {0}\u7f3a\u5c11\u4f9d\u8d56\u9879\u6240\u9700\u7684\u5c5e\u6027 {1} -dependentSchemas = {0}\u4f9d\u8d56\u6a21\u5f0f {1} \u6709\u9519\u8BEF -edits = {0}\uFF1A\u201C\u7F16\u8F91\u201D\u6709\u9519\u8BEF -enum = {0}\uFF1A\u679A\u4E3E {1} \u4E2D\u6CA1\u6709\u503C -format = {0}\uFF1A\u4E0E {1} \u6A21\u5F0F {2} \u4E0D\u5339\u914D -items = {0}[{1}]\uFF1A\u5728\u6B64\u7D22\u5F15\u4E2D\u627E\u4E0D\u5230\u9A8C\u8BC1\u5668 -maximum = {0}\uFF1A\u6700\u5927\u503C\u5FC5\u987B\u4E3A {1} -maxItems = {0}\uFF1A\u6570\u7EC4\u4E2D\u6700\u591A\u5FC5\u987B\u6709 {1} \u4E2A\u9879\u76EE -maxLength = {0}\uFF1A\u53EF\u80FD\u53EA\u6709 {1} \u4E2A\u5B57\u7B26\u957F -maxProperties = {0}\uFF1A\u6700\u591A\u53EA\u80FD\u6709 {1} \u4E2A\u5C5E\u6027 -minimum = {0}\uFF1A\u6700\u5C0F\u503C\u5FC5\u987B\u4E3A {1} -minItems = {0}\uFF1A\u6570\u7EC4\u4E2D\u5FC5\u987B\u81F3\u5C11\u6709 {1} \u4E2A\u9879\u76EE -minLength = {0}\uFF1A\u5FC5\u987B\u81F3\u5C11\u4E3A {1} \u4E2A\u5B57\u7B26\u957F -minProperties = {0}\uFF1A\u5E94\u8BE5\u81F3\u5C11\u6709 {1} \u4E2A\u5C5E\u6027 -multipleOf = {0}\uFF1A\u5FC5\u987B\u662F {1} \u7684\u500D\u6570 -notAllowed = {0}.{1}\uFF1A\u4E0D\u88AB\u5141\u8BB8\u4F46\u5728\u6570\u636E\u4E2D -not = {0}\uFF1A\u4E0D\u5E94\u5BF9\u67B6\u6784 {1} \u6709\u6548 -oneOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u4E00\u79CD\u4E14\u4EC5\u4E00\u79CD\u6A21\u5F0F\u6709\u6548 {1} -patternProperties = {0}\uFF1A\u201C\u6A21\u5F0F\u5C5E\u6027\u201D\u6709\u4E00\u4E9B\u9519\u8BEF -pattern = {0}\uFF1A\u4E0E\u6B63\u5219\u8868\u8FBE\u5F0F\u6A21\u5F0F {1} \u4E0D\u5339\u914D -properties = {0}\uFF1A\u201C\u5C5E\u6027\u201D\u6709\u9519\u8BEF -readOnly = {0}\uFF1A\u662F\u53EA\u8BFB\u5B57\u6BB5\uFF0C\u4E0D\u80FD\u66F4\u6539 -$ref = {0}\uFF1A'refs' \u6709\u9519\u8BEF -required = {0}.{1}\uFF1A\u7F3A\u5C11\u4F46\u5B83\u662F\u5FC5\u9700\u7684 -type = {0}\uFF1A\u627E\u5230 {1}\uFF0C\u9884\u671F\u4E3A {2} -unionType = {0}\uFF1A\u627E\u5230 {1}\uFF0C\u4F46\u9700\u8981 {2} -uniqueItems = {0}\uFF1A\u6570\u7EC4\u4E2D\u7684\u9879\u76EE\u5FC5\u987B\u662F\u552F\u4E00\u7684 -uuid = {0}\uFF1A{1} \u662F\u65E0\u6548\u7684 {2} -id = {0}\uFF1A{1} \u662F URI {2} \u7684\u65E0\u6548\u6BB5 -exclusiveMaximum = {0}\uFF1A\u5FC5\u987B\u5177\u6709 {1} \u7684\u72EC\u5360\u6700\u5927\u503C -exclusiveMinimum = {0}\uFF1A\u5FC5\u987B\u5177\u6709 {1} \u7684\u552F\u4E00\u6700\u5C0F\u503C -false = \u5E03\u5C14\u67B6\u6784 false \u65E0\u6548 -const = {0}\uFF1A\u5FC5\u987B\u662F\u5E38\u91CF\u503C {1} -contains = {0}\uFF1A\u4E0D\u5305\u542B\u901A\u8FC7\u8FD9\u4E9B\u9A8C\u8BC1\u7684\u5143\u7D20\uFF1A{1} -propertyNames = \u5C5E\u6027\u540D\u79F0 {0} \u5BF9\u9A8C\u8BC1\u65E0\u6548\uFF1A{1} +$ref = \u201Crefs\u201D\u6709\u9519\u8BEF +additionalItems = \u7D22\u5F15\u201C{0}\u201D\u672A\u5728\u67B6\u6784\u4E2D\u5B9A\u4E49\uFF0C\u5E76\u4E14\u8BE5\u67B6\u6784\u4E0D\u5141\u8BB8\u9644\u52A0\u9879\u76EE +additionalProperties = \u67B6\u6784\u4E2D\u672A\u5B9A\u4E49\u5C5E\u6027\u201C{0}\u201D\uFF0C\u5E76\u4E14\u67B6\u6784\u4E0D\u5141\u8BB8\u9644\u52A0\u5C5E\u6027 +allOf = \u5FC5\u987B\u5BF9\u6240\u6709\u67B6\u6784 {0} \u6709\u6548 +anyOf = \u5FC5\u987B\u5BF9\u4EFB\u4F55\u67B6\u6784 {0} \u6709\u6548 +const = \u5FC5\u987B\u662F\u5E38\u91CF\u503C\u201C{0}\u201D +contains = \u4E0D\u5305\u542B\u901A\u8FC7\u8FD9\u4E9B\u9A8C\u8BC1\u7684\u5143\u7D20: {1} +contains.max = \u5FC5\u987B\u5305\u542B\u6700\u591A {0} \u4E2A\u901A\u8FC7\u4EE5\u4E0B\u9A8C\u8BC1\u7684\u5143\u7D20: {1} +contains.min = \u5FC5\u987B\u5305\u542B\u81F3\u5C11 {0} \u4E2A\u901A\u8FC7\u8FD9\u4E9B\u9A8C\u8BC1\u7684\u5143\u7D20: {1} +dependencies = \u4F9D\u8D56\u9879 {0} \u5B58\u5728\u9519\u8BEF +dependentRequired = \u7F3A\u5C11\u5C5E\u6027\u201C{0}\u201D\uFF0C\u8BE5\u5C5E\u6027\u662F\u4F9D\u8D56\u5FC5\u9700\u7684\uFF0C\u56E0\u4E3A\u5B58\u5728\u201C{1}\u201D +dependentSchemas = dependentSchemas {0} \u5B58\u5728\u9519\u8BEF +enum = \u679A\u4E3E {0} \u4E2D\u6CA1\u6709\u503C +exclusiveMaximum = \u5FC5\u987B\u5177\u6709\u72EC\u5360\u6700\u5927\u503C {0} +exclusiveMinimum = \u5FC5\u987B\u5177\u6709\u72EC\u5360\u6700\u5C0F\u503C {0} +false = \u201C{0}\u201D\u7684\u67B6\u6784\u4E3A false +format = \u4E0E {0} \u6A21\u5F0F \u4E0D\u5339\u914D +format.date = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u5B8C\u6574\u65E5\u671F +format.date-time = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u65E5\u671F\u65F6\u95F4 +format.duration = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 ISO 8601 \u6301\u7EED\u65F6\u95F4 +format.email = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 5321 \u90AE\u7BB1 +format.ipv4 = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 2673 IP \u5730\u5740 +format.ipv6 = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 4291 IP \u5730\u5740 +format.idn-email = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6531 \u90AE\u7BB1 +format.idn-hostname = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 5890 \u56FD\u9645\u5316\u4E3B\u673A\u540D +format.iri = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3987 IRI +format.iri-reference = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3987 IRI-reference +format.uri = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3986 URI +format.uri-reference = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3986 URI \u5F15\u7528 +format.uri-template = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6570 URI \u6A21\u677F +format.uuid = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 4122 UUID +format.regex = \u4E0D\u5339\u914D {0} \u6A21\u5F0F\u5FC5\u987B\u662F\u6709\u6548\u7684 ECMA-262 \u6B63\u5219\u8868\u8FBE\u5F0F +format.time = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u65F6\u95F4 +format.hostname = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 1123 \u4E3B\u673A\u540D +format.json-pointer = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6901 JSON \u6307\u9488 +format.relative-json-pointer = \u4E0E {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 IETF \u76F8\u5BF9 JSON \u6307\u9488 +format.unknown = \u683C\u5F0F\u672A\u77E5\u201C{0}\u201D +id = \u201C{0}\u201D\u4E0D\u662F\u6709\u6548\u7684 {1} +items = \u7D22\u5F15\u201C{0}\u201D\u672A\u5728\u67B6\u6784\u4E2D\u5B9A\u4E49\uFF0C\u5E76\u4E14\u8BE5\u67B6\u6784\u4E0D\u5141\u8BB8\u6DFB\u52A0\u5176\u4ED6\u9879\u76EE +maxContains = \u5FC5\u987B\u662F {0} \u4E2D\u7684\u975E\u8D1F\u6574\u6570 +maxItems = \u6700\u591A\u5FC5\u987B\u6709 {0} \u4E2A\u9879\u76EE\uFF0C\u4F46\u627E\u5230\u4E86 {1} \u4E2A +maxLength = \u957F\u5EA6\u4E0D\u5F97\u8D85\u8FC7 {0} \u4E2A\u5B57\u7B26 +maxProperties = \u6700\u591A\u5FC5\u987B\u6709 {0} \u4E2A\u5C5E\u6027 +maximum = \u6700\u5927\u503C\u5FC5\u987B\u4E3A {0} +minContains = \u5FC5\u987B\u662F {0} \u4E2D\u7684\u975E\u8D1F\u6574\u6570 +minContainsVsMaxContains = minContains \u5FC5\u987B\u5C0F\u4E8E\u6216\u7B49\u4E8E {0} \u4E2D\u7684 maxContains +minItems = \u5FC5\u987B\u81F3\u5C11\u6709 {0} \u4E2A\u9879\u76EE\uFF0C\u4F46\u5DF2\u627E\u5230 {1} \u4E2A +minLength = \u957F\u5EA6\u5FC5\u987B\u81F3\u5C11\u4E3A {0} \u4E2A\u5B57\u7B26 +minProperties = \u5FC5\u987B\u81F3\u5C11\u5177\u6709 {0} \u4E2A\u5C5E\u6027 +minimum = \u6700\u5C0F\u503C\u5FC5\u987B\u4E3A {0} +multipleOf = \u5FC5\u987B\u662F {0} \u7684\u500D\u6570 +not = \u5BF9\u4E8E\u67B6\u6784 {0} \u5FC5\u987B\u65E0\u6548 +notAllowed = \u4E0D\u5141\u8BB8\u4F7F\u7528\u5C5E\u6027\u201C{0}\u201D\uFF0C\u4F46\u5B83\u5B58\u5728\u4E8E\u6570\u636E\u4E2D +oneOf = \u5FC5\u987B\u5BF9\u4E00\u4E2A\u4E14\u4EC5\u4E00\u4E2A\u67B6\u6784\u6709\u6548\uFF0C\u4F46 {0} \u6709\u6548 +oneOf.indexes = \u5FC5\u987B\u5BF9\u4E00\u4E2A\u4E14\u4EC5\u4E00\u4E2A\u67B6\u6784\u6709\u6548\uFF0C\u4F46 {0} \u5BF9\u7D22\u5F15\u201C{1}\u201D\u6709\u6548 +pattern = \u4E0E\u6B63\u5219\u8868\u8FBE\u5F0F\u6A21\u5F0F {0} \u4E0D\u5339\u914D +patternProperties = \u201C\u6A21\u5F0F\u5C5E\u6027\u201D\u6709\u4E00\u4E9B\u9519\u8BEF +prefixItems = \u5728\u6B64\u7D22\u5F15\u5904\u627E\u4E0D\u5230\u9A8C\u8BC1\u5668 +properties = \u201C\u5C5E\u6027\u201D\u6709\u9519\u8BEF +propertyNames = \u5C5E\u6027\u201C{0}\u201D\u540D\u79F0\u65E0\u6548: {1} +readOnly = \u662F\u53EA\u8BFB\u5B57\u6BB5\uFF0C\u65E0\u6CD5\u66F4\u6539 +required = \u672A\u627E\u5230\u6240\u9700\u5C5E\u6027\u201C{0}\u201D +type = \u5DF2\u627E\u5230 {0}\uFF0C\u5FC5\u987B\u662F {1} +unevaluatedItems = \u672A\u8BC4\u4F30\u7D22\u5F15\u201C{0}\u201D\uFF0C\u67B6\u6784\u4E0D\u5141\u8BB8\u672A\u8BC4\u4F30\u7684\u9879\u76EE +unevaluatedProperties = \u672A\u8BC4\u4F30\u5C5E\u6027\u201C{0}\u201D\uFF0C\u5E76\u4E14\u67B6\u6784\u4E0D\u5141\u8BB8\u672A\u8BC4\u4F30\u7684\u5C5E\u6027 +unionType = \u5DF2\u627E\u5230 {0}\uFF0C\u5FC5\u987B\u662F {1} +uniqueItems = \u6570\u7EC4\u4E2D\u5FC5\u987B\u4EC5\u5305\u542B\u552F\u4E00\u9879 +writeOnly = \u662F\u53EA\u5199\u5B57\u6BB5\uFF0C\u4E0D\u80FD\u51FA\u73B0\u5728\u6570\u636E\u4E2D +contentEncoding = \u4E0E\u5185\u5BB9\u7F16\u7801 {0} \u4E0D\u5339\u914D +contentMediaType = \u4E0D\u662F\u5185\u5BB9\u6211 diff --git a/src/main/resources/jsv-messages_zh_TW.properties b/src/main/resources/jsv-messages_zh_TW.properties new file mode 100644 index 000000000..122306ed3 --- /dev/null +++ b/src/main/resources/jsv-messages_zh_TW.properties @@ -0,0 +1,70 @@ +$ref = \u300Crefs\u300D\u6709\u932F\u8AA4 +additionalItems = \u7D22\u5F15\u300C{0}\u300D\u672A\u5728\u67B6\u69CB\u4E2D\u5B9A\u7FA9\uFF0C\u4E14\u8A72\u67B6\u69CB\u4E0D\u5141\u8A31\u9644\u52A0\u9805\u76EE +additionalProperties = \u67B6\u69CB\u4E2D\u672A\u5B9A\u7FA9\u5C6C\u6027\u201C{0}\u201D\uFF0C\u4E14\u67B6\u69CB\u4E0D\u5141\u8A31\u9644\u52A0\u5C6C\u6027 +allOf = \u5FC5\u9808\u5C0D\u6240\u6709\u67B6\u69CB {0} \u6709\u6548 +anyOf = \u5FC5\u9808\u5C0D\u4EFB\u4F55\u67B6\u69CB {0} \u6709\u6548 +const = \u5FC5\u9808\u662F\u5E38\u6578\u503C\u201C{0}\u201D +contains = \u4E0D\u5305\u542B\u901A\u904E\u9019\u4E9B\u9A57\u8B49\u7684\u5143\u7D20: {1} +contains.max = \u5FC5\u9808\u5305\u542B\u6700\u591A {0} \u500B\u901A\u904E\u4EE5\u4E0B\u9A57\u8B49\u7684\u5143\u7D20: {1} +contains.min = \u5FC5\u9808\u5305\u542B\u81F3\u5C11 {0} \u500B\u901A\u904E\u9019\u4E9B\u9A57\u8B49\u7684\u5143\u7D20: {1} +dependencies = \u4F9D\u8CF4\u9805 {0} \u5B58\u5728\u932F\u8AA4 +dependentRequired = \u7F3A\u5C11\u5C6C\u6027\u201C{0}\u201D\uFF0C\u8A72\u5C6C\u6027\u662F\u4F9D\u8CF4\u5FC5\u9700\u7684\uFF0C\u56E0\u70BA\u5B58\u5728\u201C{1}\u201D +dependentSchemas = dependentSchemas {0} \u5B58\u5728\u932F\u8AA4 +enum = \u679A\u8209 {0} \u4E2D\u6C92\u6709\u503C +exclusiveMaximum = \u5FC5\u9808\u5177\u6709\u7368\u4F54\u6700\u5927\u503C {0} +exclusiveMinimum = \u5FC5\u9808\u5177\u6709\u7368\u4F54\u6700\u5C0F\u503C {0} +false = \u300C{0}\u300D\u7684\u67B6\u69CB\u70BA false +format = \u8207 {0} \u6A21\u5F0F \u4E0D\u5339\u914D +format.date = \u8207 {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u5B8C\u6574\u65E5\u671F +format.date-time = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u65E5\u671F\u6642\u9593 +format.duration = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 ISO 8601 \u6301\u7E8C\u6642\u9593 +format.email = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 5321 \u90F5\u7BB1 +format.ipv4 = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 2673 IP \u4F4D\u5740 +format.ipv6 = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 4291 IP \u4F4D\u5740 +format.idn-email = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6531 \u90F5\u7BB1 +format.idn-hostname = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 5890 \u570B\u969B\u5316\u4E3B\u6A5F\u540D +format.iri = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3987 IRI +format.iri-reference = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3987 IRI-reference +format.uri = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3986 URI +format.uri-reference = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3986 URI \u5F15\u7528 +format.uri-template = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6570 URI \u6A21\u677F +format.uuid = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 4122 UUID +format.regex = \u4E0D\u7B26\u5408 {0} \u6A21\u5F0F\u5FC5\u9808\u662F\u6709\u6548\u7684 ECMA-262 \u6B63\u898F\u8868\u793A\u5F0F +format.time = \u8207 {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u6642\u9593 +format.hostname = \u8207 {0} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 1123 \u4E3B\u6A5F\u540D +format.json-pointer = \u8207 {0} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6901 JSON \u6307\u91DD +format.relative-json-pointer = \u8207 {0} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u9808\u662F\u6709\u6548\u7684 IETF \u76F8\u5C0D JSON \u6307\u91DD +format.unknown = \u683C\u5F0F\u672A\u77E5\u201C{0}\u201D +id = \u300C{0}\u300D\u4E0D\u662F\u6709\u6548\u7684 {1} +items = \u7D22\u5F15\u300C{0}\u300D\u672A\u5728\u67B6\u69CB\u4E2D\u5B9A\u7FA9\uFF0C\u4E14\u8A72\u67B6\u69CB\u4E0D\u5141\u8A31\u65B0\u589E\u5176\u4ED6\u9805\u76EE +maxContains = \u5FC5\u9808\u662F {0} \u4E2D\u7684\u975E\u8CA0\u6574\u6578 +maxItems = \u6700\u591A\u5FC5\u9808\u6709 {0} \u500B\u9805\u76EE\uFF0C\u4F46\u627E\u5230\u4E86 {1} \u500B +maxLength = \u9577\u5EA6\u4E0D\u5F97\u8D85\u904E {0} \u500B\u5B57\u5143 +maxProperties = \u6700\u591A\u5FC5\u9808\u6709 {0} \u500B\u5C6C\u6027 +maximum = \u6700\u5927\u503C\u5FC5\u9808\u70BA {0} +minContains = \u5FC5\u9808\u662F {0} \u4E2D\u7684\u975E\u8CA0\u6574\u6578 +minContainsVsMaxContains = minContains \u5FC5\u9808\u5C0F\u65BC\u6216\u7B49\u65BC {0} \u4E2D\u7684 maxContains +minItems = \u5FC5\u9808\u81F3\u5C11\u6709 {0} \u500B\u9805\u76EE\uFF0C\u4F46\u5DF2\u627E\u5230 {1} \u500B +minLength = \u9577\u5EA6\u5FC5\u9808\u81F3\u5C11\u70BA {0} \u500B\u5B57\u5143 +minProperties = \u5FC5\u9808\u81F3\u5C11\u5177\u6709 {0} \u500B\u5C6C\u6027 +minimum = \u6700\u5C0F\u503C\u5FC5\u9808\u70BA {0} +multipleOf = \u5FC5\u9808\u662F {0} \u7684\u500D\u6578 +not = \u5C0D\u65BC\u67B6\u69CB {0} \u5FC5\u9808\u7121\u6548 +notAllowed = \u4E0D\u5141\u8A31\u4F7F\u7528\u5C6C\u6027\u201C{0}\u201D\uFF0C\u4F46\u5B83\u5B58\u5728\u65BC\u8CC7\u6599\u4E2D +oneOf = \u5FC5\u9808\u5C0D\u4E00\u500B\u4E14\u50C5\u4E00\u500B\u67B6\u69CB\u6709\u6548\uFF0C\u4F46 {0} \u6709\u6548 +oneOf.indexes = \u5FC5\u9808\u5C0D\u4E00\u500B\u4E14\u50C5\u4E00\u500B\u67B6\u69CB\u6709\u6548\uFF0C\u4F46 {0} \u5C0D\u7D22\u5F15\u300C{1}\u300D\u6709\u6548 +pattern = \u8207\u6B63\u898F\u8868\u793A\u5F0F\u6A21\u5F0F {0} \u4E0D\u5339\u914D +patternProperties = \u300C\u6A21\u5F0F\u5C6C\u6027\u300D\u6709\u4E00\u4E9B\u932F\u8AA4 +prefixItems = \u5728\u6B64\u7D22\u5F15\u8655\u627E\u4E0D\u5230\u9A57\u8B49\u5668 +properties = \u300C\u5C6C\u6027\u300D\u6709\u932F\u8AA4 +propertyNames = \u5C6C\u6027\u300C{0}\u300D\u540D\u7A31\u7121\u6548: {1} +readOnly = \u662F\u552F\u8B80\u5B57\u6BB5\uFF0C\u7121\u6CD5\u66F4\u6539 +required = \u672A\u627E\u5230\u6240\u9700\u5C6C\u6027\u201C{0}\u201D +type = \u5DF2\u627E\u5230 {0}\uFF0C\u5FC5\u9808\u662F {1} +unevaluatedItems = \u672A\u8A55\u4F30\u7D22\u5F15\u201C{0}\u201D\uFF0C\u67B6\u69CB\u4E0D\u5141\u8A31\u672A\u8A55\u4F30\u7684\u9805\u76EE +unevaluatedProperties = \u672A\u8A55\u4F30\u5C6C\u6027\u201C{0}\u201D\uFF0C\u4E14\u67B6\u69CB\u4E0D\u5141\u8A31\u672A\u8A55\u4F30\u7684\u5C6C\u6027 +unionType = \u5DF2\u627E\u5230 {0}\uFF0C\u5FC5\u9808\u662F {1} +uniqueItems = \u6578\u7D44\u4E2D\u5FC5\u9808\u53EA\u5305\u542B\u552F\u4E00\u9805 +writeOnly = \u662F\u53EA\u5BEB\u5B57\u6BB5\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8CC7\u6599\u4E2D +contentEncoding = \u8207\u5167\u5BB9\u7DE8\u78BC {0} \u4E0D\u7B26 +contentMediaType = \u4E0D\u662F\u5167\u5BB9\u6211 diff --git a/src/main/resources/ucd/RFC5892-appendix-B.txt b/src/main/resources/ucd/RFC5892-appendix-B.txt new file mode 100644 index 000000000..2ac7f8a3e --- /dev/null +++ b/src/main/resources/ucd/RFC5892-appendix-B.txt @@ -0,0 +1,2321 @@ +0000..002C ; DISALLOWED # ..COMMA +002D ; PVALID # HYPHEN-MINUS +002E..002F ; DISALLOWED # FULL STOP..SOLIDUS +0030..0039 ; PVALID # DIGIT ZERO..DIGIT NINE +003A..0060 ; DISALLOWED # COLON..GRAVE ACCENT +0061..007A ; PVALID # LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B..00B6 ; DISALLOWED # LEFT CURLY BRACKET..PILCROW SIGN +00B7 ; CONTEXTO # MIDDLE DOT +00B8..00DE ; DISALLOWED # CEDILLA..LATIN CAPITAL LETTER THORN +00DF..00F6 ; PVALID # LATIN SMALL LETTER SHARP S..LATIN SMALL LETT +00F7 ; DISALLOWED # DIVISION SIGN +00F8..00FF ; PVALID # LATIN SMALL LETTER O WITH STROKE..LATIN SMAL +0100 ; DISALLOWED # LATIN CAPITAL LETTER A WITH MACRON +0101 ; PVALID # LATIN SMALL LETTER A WITH MACRON +0102 ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE +0103 ; PVALID # LATIN SMALL LETTER A WITH BREVE +0104 ; DISALLOWED # LATIN CAPITAL LETTER A WITH OGONEK +0105 ; PVALID # LATIN SMALL LETTER A WITH OGONEK +0106 ; DISALLOWED # LATIN CAPITAL LETTER C WITH ACUTE +0107 ; PVALID # LATIN SMALL LETTER C WITH ACUTE +0108 ; DISALLOWED # LATIN CAPITAL LETTER C WITH CIRCUMFLEX +0109 ; PVALID # LATIN SMALL LETTER C WITH CIRCUMFLEX +010A ; DISALLOWED # LATIN CAPITAL LETTER C WITH DOT ABOVE +010B ; PVALID # LATIN SMALL LETTER C WITH DOT ABOVE +010C ; DISALLOWED # LATIN CAPITAL LETTER C WITH CARON +010D ; PVALID # LATIN SMALL LETTER C WITH CARON +010E ; DISALLOWED # LATIN CAPITAL LETTER D WITH CARON +010F ; PVALID # LATIN SMALL LETTER D WITH CARON +0110 ; DISALLOWED # LATIN CAPITAL LETTER D WITH STROKE +0111 ; PVALID # LATIN SMALL LETTER D WITH STROKE +0112 ; DISALLOWED # LATIN CAPITAL LETTER E WITH MACRON +0113 ; PVALID # LATIN SMALL LETTER E WITH MACRON +0114 ; DISALLOWED # LATIN CAPITAL LETTER E WITH BREVE +0115 ; PVALID # LATIN SMALL LETTER E WITH BREVE +0116 ; DISALLOWED # LATIN CAPITAL LETTER E WITH DOT ABOVE +0117 ; PVALID # LATIN SMALL LETTER E WITH DOT ABOVE +0118 ; DISALLOWED # LATIN CAPITAL LETTER E WITH OGONEK +0119 ; PVALID # LATIN SMALL LETTER E WITH OGONEK +011A ; DISALLOWED # LATIN CAPITAL LETTER E WITH CARON +011B ; PVALID # LATIN SMALL LETTER E WITH CARON +011C ; DISALLOWED # LATIN CAPITAL LETTER G WITH CIRCUMFLEX +011D ; PVALID # LATIN SMALL LETTER G WITH CIRCUMFLEX +011E ; DISALLOWED # LATIN CAPITAL LETTER G WITH BREVE +011F ; PVALID # LATIN SMALL LETTER G WITH BREVE +0120 ; DISALLOWED # LATIN CAPITAL LETTER G WITH DOT ABOVE +0121 ; PVALID # LATIN SMALL LETTER G WITH DOT ABOVE +0122 ; DISALLOWED # LATIN CAPITAL LETTER G WITH CEDILLA +0123 ; PVALID # LATIN SMALL LETTER G WITH CEDILLA +0124 ; DISALLOWED # LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0125 ; PVALID # LATIN SMALL LETTER H WITH CIRCUMFLEX +0126 ; DISALLOWED # LATIN CAPITAL LETTER H WITH STROKE +0127 ; PVALID # LATIN SMALL LETTER H WITH STROKE +0128 ; DISALLOWED # LATIN CAPITAL LETTER I WITH TILDE +0129 ; PVALID # LATIN SMALL LETTER I WITH TILDE +012A ; DISALLOWED # LATIN CAPITAL LETTER I WITH MACRON +012B ; PVALID # LATIN SMALL LETTER I WITH MACRON +012C ; DISALLOWED # LATIN CAPITAL LETTER I WITH BREVE +012D ; PVALID # LATIN SMALL LETTER I WITH BREVE +012E ; DISALLOWED # LATIN CAPITAL LETTER I WITH OGONEK +012F ; PVALID # LATIN SMALL LETTER I WITH OGONEK +0130 ; DISALLOWED # LATIN CAPITAL LETTER I WITH DOT ABOVE +0131 ; PVALID # LATIN SMALL LETTER DOTLESS I +0132..0134 ; DISALLOWED # LATIN CAPITAL LIGATURE IJ..LATIN CAPITAL LET +0135 ; PVALID # LATIN SMALL LETTER J WITH CIRCUMFLEX +0136 ; DISALLOWED # LATIN CAPITAL LETTER K WITH CEDILLA +0137..0138 ; PVALID # LATIN SMALL LETTER K WITH CEDILLA..LATIN SMA +0139 ; DISALLOWED # LATIN CAPITAL LETTER L WITH ACUTE +013A ; PVALID # LATIN SMALL LETTER L WITH ACUTE +013B ; DISALLOWED # LATIN CAPITAL LETTER L WITH CEDILLA +013C ; PVALID # LATIN SMALL LETTER L WITH CEDILLA +013D ; DISALLOWED # LATIN CAPITAL LETTER L WITH CARON +013E ; PVALID # LATIN SMALL LETTER L WITH CARON +013F..0141 ; DISALLOWED # LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATI +0142 ; PVALID # LATIN SMALL LETTER L WITH STROKE +0143 ; DISALLOWED # LATIN CAPITAL LETTER N WITH ACUTE +0144 ; PVALID # LATIN SMALL LETTER N WITH ACUTE +0145 ; DISALLOWED # LATIN CAPITAL LETTER N WITH CEDILLA +0146 ; PVALID # LATIN SMALL LETTER N WITH CEDILLA +0147 ; DISALLOWED # LATIN CAPITAL LETTER N WITH CARON +0148 ; PVALID # LATIN SMALL LETTER N WITH CARON +0149..014A ; DISALLOWED # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE. +014B ; PVALID # LATIN SMALL LETTER ENG +014C ; DISALLOWED # LATIN CAPITAL LETTER O WITH MACRON +014D ; PVALID # LATIN SMALL LETTER O WITH MACRON +014E ; DISALLOWED # LATIN CAPITAL LETTER O WITH BREVE +014F ; PVALID # LATIN SMALL LETTER O WITH BREVE +0150 ; DISALLOWED # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0151 ; PVALID # LATIN SMALL LETTER O WITH DOUBLE ACUTE +0152 ; DISALLOWED # LATIN CAPITAL LIGATURE OE +0153 ; PVALID # LATIN SMALL LIGATURE OE +0154 ; DISALLOWED # LATIN CAPITAL LETTER R WITH ACUTE +0155 ; PVALID # LATIN SMALL LETTER R WITH ACUTE +0156 ; DISALLOWED # LATIN CAPITAL LETTER R WITH CEDILLA +0157 ; PVALID # LATIN SMALL LETTER R WITH CEDILLA +0158 ; DISALLOWED # LATIN CAPITAL LETTER R WITH CARON +0159 ; PVALID # LATIN SMALL LETTER R WITH CARON +015A ; DISALLOWED # LATIN CAPITAL LETTER S WITH ACUTE +015B ; PVALID # LATIN SMALL LETTER S WITH ACUTE +015C ; DISALLOWED # LATIN CAPITAL LETTER S WITH CIRCUMFLEX +015D ; PVALID # LATIN SMALL LETTER S WITH CIRCUMFLEX +015E ; DISALLOWED # LATIN CAPITAL LETTER S WITH CEDILLA +015F ; PVALID # LATIN SMALL LETTER S WITH CEDILLA +0160 ; DISALLOWED # LATIN CAPITAL LETTER S WITH CARON +0161 ; PVALID # LATIN SMALL LETTER S WITH CARON +0162 ; DISALLOWED # LATIN CAPITAL LETTER T WITH CEDILLA +0163 ; PVALID # LATIN SMALL LETTER T WITH CEDILLA +0164 ; DISALLOWED # LATIN CAPITAL LETTER T WITH CARON +0165 ; PVALID # LATIN SMALL LETTER T WITH CARON +0166 ; DISALLOWED # LATIN CAPITAL LETTER T WITH STROKE +0167 ; PVALID # LATIN SMALL LETTER T WITH STROKE +0168 ; DISALLOWED # LATIN CAPITAL LETTER U WITH TILDE +0169 ; PVALID # LATIN SMALL LETTER U WITH TILDE +016A ; DISALLOWED # LATIN CAPITAL LETTER U WITH MACRON +016B ; PVALID # LATIN SMALL LETTER U WITH MACRON +016C ; DISALLOWED # LATIN CAPITAL LETTER U WITH BREVE +016D ; PVALID # LATIN SMALL LETTER U WITH BREVE +016E ; DISALLOWED # LATIN CAPITAL LETTER U WITH RING ABOVE +016F ; PVALID # LATIN SMALL LETTER U WITH RING ABOVE +0170 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0171 ; PVALID # LATIN SMALL LETTER U WITH DOUBLE ACUTE +0172 ; DISALLOWED # LATIN CAPITAL LETTER U WITH OGONEK +0173 ; PVALID # LATIN SMALL LETTER U WITH OGONEK +0174 ; DISALLOWED # LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0175 ; PVALID # LATIN SMALL LETTER W WITH CIRCUMFLEX +0176 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0177 ; PVALID # LATIN SMALL LETTER Y WITH CIRCUMFLEX +0178..0179 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN +017A ; PVALID # LATIN SMALL LETTER Z WITH ACUTE +017B ; DISALLOWED # LATIN CAPITAL LETTER Z WITH DOT ABOVE +017C ; PVALID # LATIN SMALL LETTER Z WITH DOT ABOVE +017D ; DISALLOWED # LATIN CAPITAL LETTER Z WITH CARON +017E ; PVALID # LATIN SMALL LETTER Z WITH CARON +017F ; DISALLOWED # LATIN SMALL LETTER LONG S +0180 ; PVALID # LATIN SMALL LETTER B WITH STROKE +0181..0182 ; DISALLOWED # LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPI +0183 ; PVALID # LATIN SMALL LETTER B WITH TOPBAR +0184 ; DISALLOWED # LATIN CAPITAL LETTER TONE SIX +0185 ; PVALID # LATIN SMALL LETTER TONE SIX +0186..0187 ; DISALLOWED # LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL L +0188 ; PVALID # LATIN SMALL LETTER C WITH HOOK +0189..018B ; DISALLOWED # LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITA +018C..018D ; PVALID # LATIN SMALL LETTER D WITH TOPBAR..LATIN SMAL +018E..0191 ; DISALLOWED # LATIN CAPITAL LETTER REVERSED E..LATIN CAPIT +0192 ; PVALID # LATIN SMALL LETTER F WITH HOOK +0193..0194 ; DISALLOWED # LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPI +0195 ; PVALID # LATIN SMALL LETTER HV +0196..0198 ; DISALLOWED # LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LET +0199..019B ; PVALID # LATIN SMALL LETTER K WITH HOOK..LATIN SMALL +019C..019D ; DISALLOWED # LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL +019E ; PVALID # LATIN SMALL LETTER N WITH LONG RIGHT LEG +019F..01A0 ; DISALLOWED # LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LA +01A1 ; PVALID # LATIN SMALL LETTER O WITH HORN +01A2 ; DISALLOWED # LATIN CAPITAL LETTER OI +01A3 ; PVALID # LATIN SMALL LETTER OI +01A4 ; DISALLOWED # LATIN CAPITAL LETTER P WITH HOOK +01A5 ; PVALID # LATIN SMALL LETTER P WITH HOOK +01A6..01A7 ; DISALLOWED # LATIN LETTER YR..LATIN CAPITAL LETTER TONE T +01A8 ; PVALID # LATIN SMALL LETTER TONE TWO +01A9 ; DISALLOWED # LATIN CAPITAL LETTER ESH +01AA..01AB ; PVALID # LATIN LETTER REVERSED ESH LOOP..LATIN SMALL +01AC ; DISALLOWED # LATIN CAPITAL LETTER T WITH HOOK +01AD ; PVALID # LATIN SMALL LETTER T WITH HOOK +01AE..01AF ; DISALLOWED # LATIN CAPITAL LETTER T WITH RETROFLEX HOOK.. +01B0 ; PVALID # LATIN SMALL LETTER U WITH HORN +01B1..01B3 ; DISALLOWED # LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL +01B4 ; PVALID # LATIN SMALL LETTER Y WITH HOOK +01B5 ; DISALLOWED # LATIN CAPITAL LETTER Z WITH STROKE +01B6 ; PVALID # LATIN SMALL LETTER Z WITH STROKE +01B7..01B8 ; DISALLOWED # LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETT +01B9..01BB ; PVALID # LATIN SMALL LETTER EZH REVERSED..LATIN LETTE +01BC ; DISALLOWED # LATIN CAPITAL LETTER TONE FIVE +01BD..01C3 ; PVALID # LATIN SMALL LETTER TONE FIVE..LATIN LETTER R +01C4..01CD ; DISALLOWED # LATIN CAPITAL LETTER DZ WITH CARON..LATIN CA +01CE ; PVALID # LATIN SMALL LETTER A WITH CARON +01CF ; DISALLOWED # LATIN CAPITAL LETTER I WITH CARON +01D0 ; PVALID # LATIN SMALL LETTER I WITH CARON +01D1 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CARON +01D2 ; PVALID # LATIN SMALL LETTER O WITH CARON +01D3 ; DISALLOWED # LATIN CAPITAL LETTER U WITH CARON +01D4 ; PVALID # LATIN SMALL LETTER U WITH CARON +01D5 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DIAERESIS AND MA +01D6 ; PVALID # LATIN SMALL LETTER U WITH DIAERESIS AND MACR +01D7 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DIAERESIS AND AC +01D8 ; PVALID # LATIN SMALL LETTER U WITH DIAERESIS AND ACUT +01D9 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DIAERESIS AND CA +01DA ; PVALID # LATIN SMALL LETTER U WITH DIAERESIS AND CARO +01DB ; DISALLOWED # LATIN CAPITAL LETTER U WITH DIAERESIS AND GR +01DC..01DD ; PVALID # LATIN SMALL LETTER U WITH DIAERESIS AND GRAV +01DE ; DISALLOWED # LATIN CAPITAL LETTER A WITH DIAERESIS AND MA +01DF ; PVALID # LATIN SMALL LETTER A WITH DIAERESIS AND MACR +01E0 ; DISALLOWED # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MA +01E1 ; PVALID # LATIN SMALL LETTER A WITH DOT ABOVE AND MACR +01E2 ; DISALLOWED # LATIN CAPITAL LETTER AE WITH MACRON +01E3 ; PVALID # LATIN SMALL LETTER AE WITH MACRON +01E4 ; DISALLOWED # LATIN CAPITAL LETTER G WITH STROKE +01E5 ; PVALID # LATIN SMALL LETTER G WITH STROKE +01E6 ; DISALLOWED # LATIN CAPITAL LETTER G WITH CARON +01E7 ; PVALID # LATIN SMALL LETTER G WITH CARON +01E8 ; DISALLOWED # LATIN CAPITAL LETTER K WITH CARON +01E9 ; PVALID # LATIN SMALL LETTER K WITH CARON +01EA ; DISALLOWED # LATIN CAPITAL LETTER O WITH OGONEK +01EB ; PVALID # LATIN SMALL LETTER O WITH OGONEK +01EC ; DISALLOWED # LATIN CAPITAL LETTER O WITH OGONEK AND MACRO +01ED ; PVALID # LATIN SMALL LETTER O WITH OGONEK AND MACRON +01EE ; DISALLOWED # LATIN CAPITAL LETTER EZH WITH CARON +01EF..01F0 ; PVALID # LATIN SMALL LETTER EZH WITH CARON..LATIN SMA +01F1..01F4 ; DISALLOWED # LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTE +01F5 ; PVALID # LATIN SMALL LETTER G WITH ACUTE +01F6..01F8 ; DISALLOWED # LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LE +01F9 ; PVALID # LATIN SMALL LETTER N WITH GRAVE +01FA ; DISALLOWED # LATIN CAPITAL LETTER A WITH RING ABOVE AND A +01FB ; PVALID # LATIN SMALL LETTER A WITH RING ABOVE AND ACU +01FC ; DISALLOWED # LATIN CAPITAL LETTER AE WITH ACUTE +01FD ; PVALID # LATIN SMALL LETTER AE WITH ACUTE +01FE ; DISALLOWED # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +01FF ; PVALID # LATIN SMALL LETTER O WITH STROKE AND ACUTE +0200 ; DISALLOWED # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +0201 ; PVALID # LATIN SMALL LETTER A WITH DOUBLE GRAVE +0202 ; DISALLOWED # LATIN CAPITAL LETTER A WITH INVERTED BREVE +0203 ; PVALID # LATIN SMALL LETTER A WITH INVERTED BREVE +0204 ; DISALLOWED # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +0205 ; PVALID # LATIN SMALL LETTER E WITH DOUBLE GRAVE +0206 ; DISALLOWED # LATIN CAPITAL LETTER E WITH INVERTED BREVE +0207 ; PVALID # LATIN SMALL LETTER E WITH INVERTED BREVE +0208 ; DISALLOWED # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +0209 ; PVALID # LATIN SMALL LETTER I WITH DOUBLE GRAVE +020A ; DISALLOWED # LATIN CAPITAL LETTER I WITH INVERTED BREVE +020B ; PVALID # LATIN SMALL LETTER I WITH INVERTED BREVE +020C ; DISALLOWED # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +020D ; PVALID # LATIN SMALL LETTER O WITH DOUBLE GRAVE +020E ; DISALLOWED # LATIN CAPITAL LETTER O WITH INVERTED BREVE +020F ; PVALID # LATIN SMALL LETTER O WITH INVERTED BREVE +0210 ; DISALLOWED # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +0211 ; PVALID # LATIN SMALL LETTER R WITH DOUBLE GRAVE +0212 ; DISALLOWED # LATIN CAPITAL LETTER R WITH INVERTED BREVE +0213 ; PVALID # LATIN SMALL LETTER R WITH INVERTED BREVE +0214 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +0215 ; PVALID # LATIN SMALL LETTER U WITH DOUBLE GRAVE +0216 ; DISALLOWED # LATIN CAPITAL LETTER U WITH INVERTED BREVE +0217 ; PVALID # LATIN SMALL LETTER U WITH INVERTED BREVE +0218 ; DISALLOWED # LATIN CAPITAL LETTER S WITH COMMA BELOW +0219 ; PVALID # LATIN SMALL LETTER S WITH COMMA BELOW +021A ; DISALLOWED # LATIN CAPITAL LETTER T WITH COMMA BELOW +021B ; PVALID # LATIN SMALL LETTER T WITH COMMA BELOW +021C ; DISALLOWED # LATIN CAPITAL LETTER YOGH +021D ; PVALID # LATIN SMALL LETTER YOGH +021E ; DISALLOWED # LATIN CAPITAL LETTER H WITH CARON +021F ; PVALID # LATIN SMALL LETTER H WITH CARON +0220 ; DISALLOWED # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG +0221 ; PVALID # LATIN SMALL LETTER D WITH CURL +0222 ; DISALLOWED # LATIN CAPITAL LETTER OU +0223 ; PVALID # LATIN SMALL LETTER OU +0224 ; DISALLOWED # LATIN CAPITAL LETTER Z WITH HOOK +0225 ; PVALID # LATIN SMALL LETTER Z WITH HOOK +0226 ; DISALLOWED # LATIN CAPITAL LETTER A WITH DOT ABOVE +0227 ; PVALID # LATIN SMALL LETTER A WITH DOT ABOVE +0228 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CEDILLA +0229 ; PVALID # LATIN SMALL LETTER E WITH CEDILLA +022A ; DISALLOWED # LATIN CAPITAL LETTER O WITH DIAERESIS AND MA +022B ; PVALID # LATIN SMALL LETTER O WITH DIAERESIS AND MACR +022C ; DISALLOWED # LATIN CAPITAL LETTER O WITH TILDE AND MACRON +022D ; PVALID # LATIN SMALL LETTER O WITH TILDE AND MACRON +022E ; DISALLOWED # LATIN CAPITAL LETTER O WITH DOT ABOVE +022F ; PVALID # LATIN SMALL LETTER O WITH DOT ABOVE +0230 ; DISALLOWED # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MA +0231 ; PVALID # LATIN SMALL LETTER O WITH DOT ABOVE AND MACR +0232 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH MACRON +0233..0239 ; PVALID # LATIN SMALL LETTER Y WITH MACRON..LATIN SMAL +023A..023B ; DISALLOWED # LATIN CAPITAL LETTER A WITH STROKE..LATIN CA +023C ; PVALID # LATIN SMALL LETTER C WITH STROKE +023D..023E ; DISALLOWED # LATIN CAPITAL LETTER L WITH BAR..LATIN CAPIT +023F..0240 ; PVALID # LATIN SMALL LETTER S WITH SWASH TAIL..LATIN +0241 ; DISALLOWED # LATIN CAPITAL LETTER GLOTTAL STOP +0242 ; PVALID # LATIN SMALL LETTER GLOTTAL STOP +0243..0246 ; DISALLOWED # LATIN CAPITAL LETTER B WITH STROKE..LATIN CA +0247 ; PVALID # LATIN SMALL LETTER E WITH STROKE +0248 ; DISALLOWED # LATIN CAPITAL LETTER J WITH STROKE +0249 ; PVALID # LATIN SMALL LETTER J WITH STROKE +024A ; DISALLOWED # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +024B ; PVALID # LATIN SMALL LETTER Q WITH HOOK TAIL +024C ; DISALLOWED # LATIN CAPITAL LETTER R WITH STROKE +024D ; PVALID # LATIN SMALL LETTER R WITH STROKE +024E ; DISALLOWED # LATIN CAPITAL LETTER Y WITH STROKE +024F..02AF ; PVALID # LATIN SMALL LETTER Y WITH STROKE..LATIN SMAL +02B0..02B8 ; DISALLOWED # MODIFIER LETTER SMALL H..MODIFIER LETTER SMA +02B9..02C1 ; PVALID # MODIFIER LETTER PRIME..MODIFIER LETTER REVER +02C2..02C5 ; DISALLOWED # MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LET +02C6..02D1 ; PVALID # MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER +02D2..02EB ; DISALLOWED # MODIFIER LETTER CENTRED RIGHT HALF RING..MOD +02EC ; PVALID # MODIFIER LETTER VOICING +02ED ; DISALLOWED # MODIFIER LETTER UNASPIRATED +02EE ; PVALID # MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; DISALLOWED # MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER +0300..033F ; PVALID # COMBINING GRAVE ACCENT..COMBINING DOUBLE OVE +0340..0341 ; DISALLOWED # COMBINING GRAVE TONE MARK..COMBINING ACUTE T +0342 ; PVALID # COMBINING GREEK PERISPOMENI +0343..0345 ; DISALLOWED # COMBINING GREEK KORONIS..COMBINING GREEK YPO +0346..034E ; PVALID # COMBINING BRIDGE ABOVE..COMBINING UPWARDS AR +034F ; DISALLOWED # COMBINING GRAPHEME JOINER +0350..036F ; PVALID # COMBINING RIGHT ARROWHEAD ABOVE..COMBINING L +0370 ; DISALLOWED # GREEK CAPITAL LETTER HETA +0371 ; PVALID # GREEK SMALL LETTER HETA +0372 ; DISALLOWED # GREEK CAPITAL LETTER ARCHAIC SAMPI +0373 ; PVALID # GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; DISALLOWED # GREEK NUMERAL SIGN +0375 ; CONTEXTO # GREEK LOWER NUMERAL SIGN +0376 ; DISALLOWED # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA +0377 ; PVALID # GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +0378..0379 ; UNASSIGNED # .. +037A ; DISALLOWED # GREEK YPOGEGRAMMENI +037B..037D ; PVALID # GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GR +037E ; DISALLOWED # GREEK QUESTION MARK +037F..0383 ; UNASSIGNED # .. +0384..038A ; DISALLOWED # GREEK TONOS..GREEK CAPITAL LETTER IOTA WITH +038B ; UNASSIGNED # +038C ; DISALLOWED # GREEK CAPITAL LETTER OMICRON WITH TONOS +038D ; UNASSIGNED # +038E..038F ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH TONOS..GRE +0390 ; PVALID # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND T +0391..03A1 ; DISALLOWED # GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LE +03A2 ; UNASSIGNED # +03A3..03AB ; DISALLOWED # GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LE +03AC..03CE ; PVALID # GREEK SMALL LETTER ALPHA WITH TONOS..GREEK S +03CF..03D6 ; DISALLOWED # GREEK CAPITAL KAI SYMBOL..GREEK PI SYMBOL +03D7 ; PVALID # GREEK KAI SYMBOL +03D8 ; DISALLOWED # GREEK LETTER ARCHAIC KOPPA +03D9 ; PVALID # GREEK SMALL LETTER ARCHAIC KOPPA +03DA ; DISALLOWED # GREEK LETTER STIGMA +03DB ; PVALID # GREEK SMALL LETTER STIGMA +03DC ; DISALLOWED # GREEK LETTER DIGAMMA +03DD ; PVALID # GREEK SMALL LETTER DIGAMMA +03DE ; DISALLOWED # GREEK LETTER KOPPA +03DF ; PVALID # GREEK SMALL LETTER KOPPA +03E0 ; DISALLOWED # GREEK LETTER SAMPI +03E1 ; PVALID # GREEK SMALL LETTER SAMPI +03E2 ; DISALLOWED # COPTIC CAPITAL LETTER SHEI +03E3 ; PVALID # COPTIC SMALL LETTER SHEI +03E4 ; DISALLOWED # COPTIC CAPITAL LETTER FEI +03E5 ; PVALID # COPTIC SMALL LETTER FEI +03E6 ; DISALLOWED # COPTIC CAPITAL LETTER KHEI +03E7 ; PVALID # COPTIC SMALL LETTER KHEI +03E8 ; DISALLOWED # COPTIC CAPITAL LETTER HORI +03E9 ; PVALID # COPTIC SMALL LETTER HORI +03EA ; DISALLOWED # COPTIC CAPITAL LETTER GANGIA +03EB ; PVALID # COPTIC SMALL LETTER GANGIA +03EC ; DISALLOWED # COPTIC CAPITAL LETTER SHIMA +03ED ; PVALID # COPTIC SMALL LETTER SHIMA +03EE ; DISALLOWED # COPTIC CAPITAL LETTER DEI +03EF ; PVALID # COPTIC SMALL LETTER DEI +03F0..03F2 ; DISALLOWED # GREEK KAPPA SYMBOL..GREEK LUNATE SIGMA SYMBO +03F3 ; PVALID # GREEK LETTER YOT +03F4..03F7 ; DISALLOWED # GREEK CAPITAL THETA SYMBOL..GREEK CAPITAL LE +03F8 ; PVALID # GREEK SMALL LETTER SHO +03F9..03FA ; DISALLOWED # GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAP +03FB..03FC ; PVALID # GREEK SMALL LETTER SAN..GREEK RHO WITH STROK +03FD..042F ; DISALLOWED # GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL.. +0430..045F ; PVALID # CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETT +0460 ; DISALLOWED # CYRILLIC CAPITAL LETTER OMEGA +0461 ; PVALID # CYRILLIC SMALL LETTER OMEGA +0462 ; DISALLOWED # CYRILLIC CAPITAL LETTER YAT +0463 ; PVALID # CYRILLIC SMALL LETTER YAT +0464 ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED E +0465 ; PVALID # CYRILLIC SMALL LETTER IOTIFIED E +0466 ; DISALLOWED # CYRILLIC CAPITAL LETTER LITTLE YUS +0467 ; PVALID # CYRILLIC SMALL LETTER LITTLE YUS +0468 ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS +0469 ; PVALID # CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS +046A ; DISALLOWED # CYRILLIC CAPITAL LETTER BIG YUS +046B ; PVALID # CYRILLIC SMALL LETTER BIG YUS +046C ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS +046D ; PVALID # CYRILLIC SMALL LETTER IOTIFIED BIG YUS +046E ; DISALLOWED # CYRILLIC CAPITAL LETTER KSI +046F ; PVALID # CYRILLIC SMALL LETTER KSI +0470 ; DISALLOWED # CYRILLIC CAPITAL LETTER PSI +0471 ; PVALID # CYRILLIC SMALL LETTER PSI +0472 ; DISALLOWED # CYRILLIC CAPITAL LETTER FITA +0473 ; PVALID # CYRILLIC SMALL LETTER FITA +0474 ; DISALLOWED # CYRILLIC CAPITAL LETTER IZHITSA +0475 ; PVALID # CYRILLIC SMALL LETTER IZHITSA +0476 ; DISALLOWED # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE +0477 ; PVALID # CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GR +0478 ; DISALLOWED # CYRILLIC CAPITAL LETTER UK +0479 ; PVALID # CYRILLIC SMALL LETTER UK +047A ; DISALLOWED # CYRILLIC CAPITAL LETTER ROUND OMEGA +047B ; PVALID # CYRILLIC SMALL LETTER ROUND OMEGA +047C ; DISALLOWED # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO +047D ; PVALID # CYRILLIC SMALL LETTER OMEGA WITH TITLO +047E ; DISALLOWED # CYRILLIC CAPITAL LETTER OT +047F ; PVALID # CYRILLIC SMALL LETTER OT +0480 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOPPA +0481 ; PVALID # CYRILLIC SMALL LETTER KOPPA +0482 ; DISALLOWED # CYRILLIC THOUSANDS SIGN +0483..0487 ; PVALID # COMBINING CYRILLIC TITLO..COMBINING CYRILLIC +0488..048A ; DISALLOWED # COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..C +048B ; PVALID # CYRILLIC SMALL LETTER SHORT I WITH TAIL +048C ; DISALLOWED # CYRILLIC CAPITAL LETTER SEMISOFT SIGN +048D ; PVALID # CYRILLIC SMALL LETTER SEMISOFT SIGN +048E ; DISALLOWED # CYRILLIC CAPITAL LETTER ER WITH TICK +048F ; PVALID # CYRILLIC SMALL LETTER ER WITH TICK +0490 ; DISALLOWED # CYRILLIC CAPITAL LETTER GHE WITH UPTURN +0491 ; PVALID # CYRILLIC SMALL LETTER GHE WITH UPTURN +0492 ; DISALLOWED # CYRILLIC CAPITAL LETTER GHE WITH STROKE +0493 ; PVALID # CYRILLIC SMALL LETTER GHE WITH STROKE +0494 ; DISALLOWED # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK +0495 ; PVALID # CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK +0496 ; DISALLOWED # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER +0497 ; PVALID # CYRILLIC SMALL LETTER ZHE WITH DESCENDER +0498 ; DISALLOWED # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER +0499 ; PVALID # CYRILLIC SMALL LETTER ZE WITH DESCENDER +049A ; DISALLOWED # CYRILLIC CAPITAL LETTER KA WITH DESCENDER +049B ; PVALID # CYRILLIC SMALL LETTER KA WITH DESCENDER +049C ; DISALLOWED # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STR +049D ; PVALID # CYRILLIC SMALL LETTER KA WITH VERTICAL STROK +049E ; DISALLOWED # CYRILLIC CAPITAL LETTER KA WITH STROKE +049F ; PVALID # CYRILLIC SMALL LETTER KA WITH STROKE +04A0 ; DISALLOWED # CYRILLIC CAPITAL LETTER BASHKIR KA +04A1 ; PVALID # CYRILLIC SMALL LETTER BASHKIR KA +04A2 ; DISALLOWED # CYRILLIC CAPITAL LETTER EN WITH DESCENDER +04A3 ; PVALID # CYRILLIC SMALL LETTER EN WITH DESCENDER +04A4 ; DISALLOWED # CYRILLIC CAPITAL LIGATURE EN GHE +04A5 ; PVALID # CYRILLIC SMALL LIGATURE EN GHE +04A6 ; DISALLOWED # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK +04A7 ; PVALID # CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK +04A8 ; DISALLOWED # CYRILLIC CAPITAL LETTER ABKHASIAN HA +04A9 ; PVALID # CYRILLIC SMALL LETTER ABKHASIAN HA +04AA ; DISALLOWED # CYRILLIC CAPITAL LETTER ES WITH DESCENDER +04AB ; PVALID # CYRILLIC SMALL LETTER ES WITH DESCENDER +04AC ; DISALLOWED # CYRILLIC CAPITAL LETTER TE WITH DESCENDER +04AD ; PVALID # CYRILLIC SMALL LETTER TE WITH DESCENDER +04AE ; DISALLOWED # CYRILLIC CAPITAL LETTER STRAIGHT U +04AF ; PVALID # CYRILLIC SMALL LETTER STRAIGHT U +04B0 ; DISALLOWED # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STRO +04B1 ; PVALID # CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE +04B2 ; DISALLOWED # CYRILLIC CAPITAL LETTER HA WITH DESCENDER +04B3 ; PVALID # CYRILLIC SMALL LETTER HA WITH DESCENDER +04B4 ; DISALLOWED # CYRILLIC CAPITAL LIGATURE TE TSE +04B5 ; PVALID # CYRILLIC SMALL LIGATURE TE TSE +04B6 ; DISALLOWED # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER +04B7 ; PVALID # CYRILLIC SMALL LETTER CHE WITH DESCENDER +04B8 ; DISALLOWED # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL ST +04B9 ; PVALID # CYRILLIC SMALL LETTER CHE WITH VERTICAL STRO +04BA ; DISALLOWED # CYRILLIC CAPITAL LETTER SHHA +04BB ; PVALID # CYRILLIC SMALL LETTER SHHA +04BC ; DISALLOWED # CYRILLIC CAPITAL LETTER ABKHASIAN CHE +04BD ; PVALID # CYRILLIC SMALL LETTER ABKHASIAN CHE +04BE ; DISALLOWED # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH D +04BF ; PVALID # CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DES +04C0..04C1 ; DISALLOWED # CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL L +04C2 ; PVALID # CYRILLIC SMALL LETTER ZHE WITH BREVE +04C3 ; DISALLOWED # CYRILLIC CAPITAL LETTER KA WITH HOOK +04C4 ; PVALID # CYRILLIC SMALL LETTER KA WITH HOOK +04C5 ; DISALLOWED # CYRILLIC CAPITAL LETTER EL WITH TAIL +04C6 ; PVALID # CYRILLIC SMALL LETTER EL WITH TAIL +04C7 ; DISALLOWED # CYRILLIC CAPITAL LETTER EN WITH HOOK +04C8 ; PVALID # CYRILLIC SMALL LETTER EN WITH HOOK +04C9 ; DISALLOWED # CYRILLIC CAPITAL LETTER EN WITH TAIL +04CA ; PVALID # CYRILLIC SMALL LETTER EN WITH TAIL +04CB ; DISALLOWED # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE +04CC ; PVALID # CYRILLIC SMALL LETTER KHAKASSIAN CHE +04CD ; DISALLOWED # CYRILLIC CAPITAL LETTER EM WITH TAIL +04CE..04CF ; PVALID # CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC +04D0 ; DISALLOWED # CYRILLIC CAPITAL LETTER A WITH BREVE +04D1 ; PVALID # CYRILLIC SMALL LETTER A WITH BREVE +04D2 ; DISALLOWED # CYRILLIC CAPITAL LETTER A WITH DIAERESIS +04D3 ; PVALID # CYRILLIC SMALL LETTER A WITH DIAERESIS +04D4 ; DISALLOWED # CYRILLIC CAPITAL LIGATURE A IE +04D5 ; PVALID # CYRILLIC SMALL LIGATURE A IE +04D6 ; DISALLOWED # CYRILLIC CAPITAL LETTER IE WITH BREVE +04D7 ; PVALID # CYRILLIC SMALL LETTER IE WITH BREVE +04D8 ; DISALLOWED # CYRILLIC CAPITAL LETTER SCHWA +04D9 ; PVALID # CYRILLIC SMALL LETTER SCHWA +04DA ; DISALLOWED # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS +04DB ; PVALID # CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS +04DC ; DISALLOWED # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS +04DD ; PVALID # CYRILLIC SMALL LETTER ZHE WITH DIAERESIS +04DE ; DISALLOWED # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS +04DF ; PVALID # CYRILLIC SMALL LETTER ZE WITH DIAERESIS +04E0 ; DISALLOWED # CYRILLIC CAPITAL LETTER ABKHASIAN DZE +04E1 ; PVALID # CYRILLIC SMALL LETTER ABKHASIAN DZE +04E2 ; DISALLOWED # CYRILLIC CAPITAL LETTER I WITH MACRON +04E3 ; PVALID # CYRILLIC SMALL LETTER I WITH MACRON +04E4 ; DISALLOWED # CYRILLIC CAPITAL LETTER I WITH DIAERESIS +04E5 ; PVALID # CYRILLIC SMALL LETTER I WITH DIAERESIS +04E6 ; DISALLOWED # CYRILLIC CAPITAL LETTER O WITH DIAERESIS +04E7 ; PVALID # CYRILLIC SMALL LETTER O WITH DIAERESIS +04E8 ; DISALLOWED # CYRILLIC CAPITAL LETTER BARRED O +04E9 ; PVALID # CYRILLIC SMALL LETTER BARRED O +04EA ; DISALLOWED # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERE +04EB ; PVALID # CYRILLIC SMALL LETTER BARRED O WITH DIAERESI +04EC ; DISALLOWED # CYRILLIC CAPITAL LETTER E WITH DIAERESIS +04ED ; PVALID # CYRILLIC SMALL LETTER E WITH DIAERESIS +04EE ; DISALLOWED # CYRILLIC CAPITAL LETTER U WITH MACRON +04EF ; PVALID # CYRILLIC SMALL LETTER U WITH MACRON +04F0 ; DISALLOWED # CYRILLIC CAPITAL LETTER U WITH DIAERESIS +04F1 ; PVALID # CYRILLIC SMALL LETTER U WITH DIAERESIS +04F2 ; DISALLOWED # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE +04F3 ; PVALID # CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE +04F4 ; DISALLOWED # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS +04F5 ; PVALID # CYRILLIC SMALL LETTER CHE WITH DIAERESIS +04F6 ; DISALLOWED # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER +04F7 ; PVALID # CYRILLIC SMALL LETTER GHE WITH DESCENDER +04F8 ; DISALLOWED # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS +04F9 ; PVALID # CYRILLIC SMALL LETTER YERU WITH DIAERESIS +04FA ; DISALLOWED # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND +04FB ; PVALID # CYRILLIC SMALL LETTER GHE WITH STROKE AND HO +04FC ; DISALLOWED # CYRILLIC CAPITAL LETTER HA WITH HOOK +04FD ; PVALID # CYRILLIC SMALL LETTER HA WITH HOOK +04FE ; DISALLOWED # CYRILLIC CAPITAL LETTER HA WITH STROKE +04FF ; PVALID # CYRILLIC SMALL LETTER HA WITH STROKE +0500 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI DE +0501 ; PVALID # CYRILLIC SMALL LETTER KOMI DE +0502 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI DJE +0503 ; PVALID # CYRILLIC SMALL LETTER KOMI DJE +0504 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI ZJE +0505 ; PVALID # CYRILLIC SMALL LETTER KOMI ZJE +0506 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI DZJE +0507 ; PVALID # CYRILLIC SMALL LETTER KOMI DZJE +0508 ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI LJE +0509 ; PVALID # CYRILLIC SMALL LETTER KOMI LJE +050A ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI NJE +050B ; PVALID # CYRILLIC SMALL LETTER KOMI NJE +050C ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI SJE +050D ; PVALID # CYRILLIC SMALL LETTER KOMI SJE +050E ; DISALLOWED # CYRILLIC CAPITAL LETTER KOMI TJE +050F ; PVALID # CYRILLIC SMALL LETTER KOMI TJE +0510 ; DISALLOWED # CYRILLIC CAPITAL LETTER REVERSED ZE +0511 ; PVALID # CYRILLIC SMALL LETTER REVERSED ZE +0512 ; DISALLOWED # CYRILLIC CAPITAL LETTER EL WITH HOOK +0513 ; PVALID # CYRILLIC SMALL LETTER EL WITH HOOK +0514 ; DISALLOWED # CYRILLIC CAPITAL LETTER LHA +0515 ; PVALID # CYRILLIC SMALL LETTER LHA +0516 ; DISALLOWED # CYRILLIC CAPITAL LETTER RHA +0517 ; PVALID # CYRILLIC SMALL LETTER RHA +0518 ; DISALLOWED # CYRILLIC CAPITAL LETTER YAE +0519 ; PVALID # CYRILLIC SMALL LETTER YAE +051A ; DISALLOWED # CYRILLIC CAPITAL LETTER QA +051B ; PVALID # CYRILLIC SMALL LETTER QA +051C ; DISALLOWED # CYRILLIC CAPITAL LETTER WE +051D ; PVALID # CYRILLIC SMALL LETTER WE +051E ; DISALLOWED # CYRILLIC CAPITAL LETTER ALEUT KA +051F ; PVALID # CYRILLIC SMALL LETTER ALEUT KA +0520 ; DISALLOWED # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK +0521 ; PVALID # CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK +0522 ; DISALLOWED # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK +0523 ; PVALID # CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK +0524 ; DISALLOWED # CYRILLIC CAPITAL LETTER PE WITH DESCENDER +0525 ; PVALID # CYRILLIC SMALL LETTER PE WITH DESCENDER +0526..0530 ; UNASSIGNED # .. +0531..0556 ; DISALLOWED # ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITA +0557..0558 ; UNASSIGNED # .. +0559 ; PVALID # ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055F ; DISALLOWED # ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION M +0560 ; UNASSIGNED # +0561..0586 ; PVALID # ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LE +0587 ; DISALLOWED # ARMENIAN SMALL LIGATURE ECH YIWN +0588 ; UNASSIGNED # +0589..058A ; DISALLOWED # ARMENIAN FULL STOP..ARMENIAN HYPHEN +058B..0590 ; UNASSIGNED # .. +0591..05BD ; PVALID # HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE ; DISALLOWED # HEBREW PUNCTUATION MAQAF +05BF ; PVALID # HEBREW POINT RAFE +05C0 ; DISALLOWED # HEBREW PUNCTUATION PASEQ +05C1..05C2 ; PVALID # HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3 ; DISALLOWED # HEBREW PUNCTUATION SOF PASUQ +05C4..05C5 ; PVALID # HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6 ; DISALLOWED # HEBREW PUNCTUATION NUN HAFUKHA +05C7 ; PVALID # HEBREW POINT QAMATS QATAN +05C8..05CF ; UNASSIGNED # .. +05D0..05EA ; PVALID # HEBREW LETTER ALEF..HEBREW LETTER TAV +05EB..05EF ; UNASSIGNED # .. +05F0..05F2 ; PVALID # HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW L +05F3..05F4 ; CONTEXTO # HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATIO +05F5..05FF ; UNASSIGNED # .. +0600..0603 ; DISALLOWED # ARABIC NUMBER SIGN..ARABIC SIGN SAFHA +0604..0605 ; UNASSIGNED # .. +0606..060F ; DISALLOWED # ARABIC-INDIC CUBE ROOT..ARABIC SIGN MISRA +0610..061A ; PVALID # ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..AR +061B ; DISALLOWED # ARABIC SEMICOLON +061C..061D ; UNASSIGNED # .. +061E..061F ; DISALLOWED # ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC Q +0620 ; UNASSIGNED # +0621..063F ; PVALID # ARABIC LETTER HAMZA..ARABIC LETTER FARSI YEH +0640 ; DISALLOWED # ARABIC TATWEEL +0641..065E ; PVALID # ARABIC LETTER FEH..ARABIC FATHA WITH TWO DOT +065F ; UNASSIGNED # +0660..0669 ; CONTEXTO # ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT +066A..066D ; DISALLOWED # ARABIC PERCENT SIGN..ARABIC FIVE POINTED STA +066E..0674 ; PVALID # ARABIC LETTER DOTLESS BEH..ARABIC LETTER HIG +0675..0678 ; DISALLOWED # ARABIC LETTER HIGH HAMZA ALEF..ARABIC LETTER +0679..06D3 ; PVALID # ARABIC LETTER TTEH..ARABIC LETTER YEH BARREE +06D4 ; DISALLOWED # ARABIC FULL STOP +06D5..06DC ; PVALID # ARABIC LETTER AE..ARABIC SMALL HIGH SEEN +06DD..06DE ; DISALLOWED # ARABIC END OF AYAH..ARABIC START OF RUB EL H +06DF..06E8 ; PVALID # ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL +06E9 ; DISALLOWED # ARABIC PLACE OF SAJDAH +06EA..06EF ; PVALID # ARABIC EMPTY CENTRE LOW STOP..ARABIC LETTER +06F0..06F9 ; CONTEXTO # EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED A +06FA..06FF ; PVALID # ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC L +0700..070D ; DISALLOWED # SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN AST +070E ; UNASSIGNED # +070F ; DISALLOWED # SYRIAC ABBREVIATION MARK +0710..074A ; PVALID # SYRIAC LETTER ALAPH..SYRIAC BARREKH +074B..074C ; UNASSIGNED # .. +074D..07B1 ; PVALID # SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER N +07B2..07BF ; UNASSIGNED # .. +07C0..07F5 ; PVALID # NKO DIGIT ZERO..NKO LOW TONE APOSTROPHE +07F6..07FA ; DISALLOWED # NKO SYMBOL OO DENNEN..NKO LAJANYALAN +07FB..07FF ; UNASSIGNED # .. +0800..082D ; PVALID # SAMARITAN LETTER ALAF..SAMARITAN MARK NEQUDA +082E..082F ; UNASSIGNED # .. +0830..083E ; DISALLOWED # SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUN +083F..08FF ; UNASSIGNED # .. +0900..0939 ; PVALID # DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANA +093A..093B ; UNASSIGNED # .. +093C..094E ; PVALID # DEVANAGARI SIGN NUKTA..DEVANAGARI VOWEL SIGN +094F ; UNASSIGNED # +0950..0955 ; PVALID # DEVANAGARI OM..DEVANAGARI VOWEL SIGN CANDRA +0956..0957 ; UNASSIGNED # .. +0958..095F ; DISALLOWED # DEVANAGARI LETTER QA..DEVANAGARI LETTER YYA +0960..0963 ; PVALID # DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOW +0964..0965 ; DISALLOWED # DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966..096F ; PVALID # DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970 ; DISALLOWED # DEVANAGARI ABBREVIATION SIGN +0971..0972 ; PVALID # DEVANAGARI SIGN HIGH SPACING DOT..DEVANAGARI +0973..0978 ; UNASSIGNED # .. +0979..097F ; PVALID # DEVANAGARI LETTER ZHA..DEVANAGARI LETTER BBA +0980 ; UNASSIGNED # +0981..0983 ; PVALID # BENGALI SIGN CANDRABINDU..BENGALI SIGN VISAR +0984 ; UNASSIGNED # +0985..098C ; PVALID # BENGALI LETTER A..BENGALI LETTER VOCALIC L +098D..098E ; UNASSIGNED # .. +098F..0990 ; PVALID # BENGALI LETTER E..BENGALI LETTER AI +0991..0992 ; UNASSIGNED # .. +0993..09A8 ; PVALID # BENGALI LETTER O..BENGALI LETTER NA +09A9 ; UNASSIGNED # +09AA..09B0 ; PVALID # BENGALI LETTER PA..BENGALI LETTER RA +09B1 ; UNASSIGNED # +09B2 ; PVALID # BENGALI LETTER LA +09B3..09B5 ; UNASSIGNED # .. +09B6..09B9 ; PVALID # BENGALI LETTER SHA..BENGALI LETTER HA +09BA..09BB ; UNASSIGNED # .. +09BC..09C4 ; PVALID # BENGALI SIGN NUKTA..BENGALI VOWEL SIGN VOCAL +09C5..09C6 ; UNASSIGNED # .. +09C7..09C8 ; PVALID # BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09C9..09CA ; UNASSIGNED # .. +09CB..09CE ; PVALID # BENGALI VOWEL SIGN O..BENGALI LETTER KHANDA +09CF..09D6 ; UNASSIGNED # .. +09D7 ; PVALID # BENGALI AU LENGTH MARK +09D8..09DB ; UNASSIGNED # .. +09DC..09DD ; DISALLOWED # BENGALI LETTER RRA..BENGALI LETTER RHA +09DE ; UNASSIGNED # +09DF ; DISALLOWED # BENGALI LETTER YYA +09E0..09E3 ; PVALID # BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIG +09E4..09E5 ; UNASSIGNED # .. +09E6..09F1 ; PVALID # BENGALI DIGIT ZERO..BENGALI LETTER RA WITH L +09F2..09FB ; DISALLOWED # BENGALI RUPEE MARK..BENGALI GANDA MARK +09FC..0A00 ; UNASSIGNED # .. +0A01..0A03 ; PVALID # GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN VISA +0A04 ; UNASSIGNED # +0A05..0A0A ; PVALID # GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0B..0A0E ; UNASSIGNED # .. +0A0F..0A10 ; PVALID # GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A11..0A12 ; UNASSIGNED # .. +0A13..0A28 ; PVALID # GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A29 ; UNASSIGNED # +0A2A..0A30 ; PVALID # GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A31 ; UNASSIGNED # +0A32 ; PVALID # GURMUKHI LETTER LA +0A33 ; DISALLOWED # GURMUKHI LETTER LLA +0A34 ; UNASSIGNED # +0A35 ; PVALID # GURMUKHI LETTER VA +0A36 ; DISALLOWED # GURMUKHI LETTER SHA +0A37 ; UNASSIGNED # +0A38..0A39 ; PVALID # GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3A..0A3B ; UNASSIGNED # .. +0A3C ; PVALID # GURMUKHI SIGN NUKTA +0A3D ; UNASSIGNED # +0A3E..0A42 ; PVALID # GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN +0A43..0A46 ; UNASSIGNED # .. +0A47..0A48 ; PVALID # GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN +0A49..0A4A ; UNASSIGNED # .. +0A4B..0A4D ; PVALID # GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A4E..0A50 ; UNASSIGNED # .. +0A51 ; PVALID # GURMUKHI SIGN UDAAT +0A52..0A58 ; UNASSIGNED # .. +0A59..0A5B ; DISALLOWED # GURMUKHI LETTER KHHA..GURMUKHI LETTER ZA +0A5C ; PVALID # GURMUKHI LETTER RRA +0A5D ; UNASSIGNED # +0A5E ; DISALLOWED # GURMUKHI LETTER FA +0A5F..0A65 ; UNASSIGNED # .. +0A66..0A75 ; PVALID # GURMUKHI DIGIT ZERO..GURMUKHI SIGN YAKASH +0A76..0A80 ; UNASSIGNED # .. +0A81..0A83 ; PVALID # GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VIS +0A84 ; UNASSIGNED # +0A85..0A8D ; PVALID # GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8E ; UNASSIGNED # +0A8F..0A91 ; PVALID # GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A92 ; UNASSIGNED # +0A93..0AA8 ; PVALID # GUJARATI LETTER O..GUJARATI LETTER NA +0AA9 ; UNASSIGNED # +0AAA..0AB0 ; PVALID # GUJARATI LETTER PA..GUJARATI LETTER RA +0AB1 ; UNASSIGNED # +0AB2..0AB3 ; PVALID # GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB4 ; UNASSIGNED # +0AB5..0AB9 ; PVALID # GUJARATI LETTER VA..GUJARATI LETTER HA +0ABA..0ABB ; UNASSIGNED # .. +0ABC..0AC5 ; PVALID # GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CAN +0AC6 ; UNASSIGNED # +0AC7..0AC9 ; PVALID # GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN C +0ACA ; UNASSIGNED # +0ACB..0ACD ; PVALID # GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA +0ACE..0ACF ; UNASSIGNED # .. +0AD0 ; PVALID # GUJARATI OM +0AD1..0ADF ; UNASSIGNED # .. +0AE0..0AE3 ; PVALID # GUJARATI LETTER VOCALIC RR..GUJARATI VOWEL S +0AE4..0AE5 ; UNASSIGNED # .. +0AE6..0AEF ; PVALID # GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0 ; UNASSIGNED # +0AF1 ; DISALLOWED # GUJARATI RUPEE SIGN +0AF2..0B00 ; UNASSIGNED # .. +0B01..0B03 ; PVALID # ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA +0B04 ; UNASSIGNED # +0B05..0B0C ; PVALID # ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0D..0B0E ; UNASSIGNED # .. +0B0F..0B10 ; PVALID # ORIYA LETTER E..ORIYA LETTER AI +0B11..0B12 ; UNASSIGNED # .. +0B13..0B28 ; PVALID # ORIYA LETTER O..ORIYA LETTER NA +0B29 ; UNASSIGNED # +0B2A..0B30 ; PVALID # ORIYA LETTER PA..ORIYA LETTER RA +0B31 ; UNASSIGNED # +0B32..0B33 ; PVALID # ORIYA LETTER LA..ORIYA LETTER LLA +0B34 ; UNASSIGNED # +0B35..0B39 ; PVALID # ORIYA LETTER VA..ORIYA LETTER HA +0B3A..0B3B ; UNASSIGNED # .. +0B3C..0B44 ; PVALID # ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC R +0B45..0B46 ; UNASSIGNED # .. +0B47..0B48 ; PVALID # ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B49..0B4A ; UNASSIGNED # .. +0B4B..0B4D ; PVALID # ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA +0B4E..0B55 ; UNASSIGNED # .. +0B56..0B57 ; PVALID # ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK +0B58..0B5B ; UNASSIGNED # .. +0B5C..0B5D ; DISALLOWED # ORIYA LETTER RRA..ORIYA LETTER RHA +0B5E ; UNASSIGNED # +0B5F..0B63 ; PVALID # ORIYA LETTER YYA..ORIYA VOWEL SIGN VOCALIC L +0B64..0B65 ; UNASSIGNED # .. +0B66..0B6F ; PVALID # ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70 ; DISALLOWED # ORIYA ISSHAR +0B71 ; PVALID # ORIYA LETTER WA +0B72..0B81 ; UNASSIGNED # .. +0B82..0B83 ; PVALID # TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA +0B84 ; UNASSIGNED # +0B85..0B8A ; PVALID # TAMIL LETTER A..TAMIL LETTER UU +0B8B..0B8D ; UNASSIGNED # .. +0B8E..0B90 ; PVALID # TAMIL LETTER E..TAMIL LETTER AI +0B91 ; UNASSIGNED # +0B92..0B95 ; PVALID # TAMIL LETTER O..TAMIL LETTER KA +0B96..0B98 ; UNASSIGNED # .. +0B99..0B9A ; PVALID # TAMIL LETTER NGA..TAMIL LETTER CA +0B9B ; UNASSIGNED # +0B9C ; PVALID # TAMIL LETTER JA +0B9D ; UNASSIGNED # +0B9E..0B9F ; PVALID # TAMIL LETTER NYA..TAMIL LETTER TTA +0BA0..0BA2 ; UNASSIGNED # .. +0BA3..0BA4 ; PVALID # TAMIL LETTER NNA..TAMIL LETTER TA +0BA5..0BA7 ; UNASSIGNED # .. +0BA8..0BAA ; PVALID # TAMIL LETTER NA..TAMIL LETTER PA +0BAB..0BAD ; UNASSIGNED # .. +0BAE..0BB9 ; PVALID # TAMIL LETTER MA..TAMIL LETTER HA +0BBA..0BBD ; UNASSIGNED # .. +0BBE..0BC2 ; PVALID # TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU +0BC3..0BC5 ; UNASSIGNED # .. +0BC6..0BC8 ; PVALID # TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BC9 ; UNASSIGNED # +0BCA..0BCD ; PVALID # TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA +0BCE..0BCF ; UNASSIGNED # .. +0BD0 ; PVALID # TAMIL OM +0BD1..0BD6 ; UNASSIGNED # .. +0BD7 ; PVALID # TAMIL AU LENGTH MARK +0BD8..0BE5 ; UNASSIGNED # .. +0BE6..0BEF ; PVALID # TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0..0BFA ; DISALLOWED # TAMIL NUMBER TEN..TAMIL NUMBER SIGN +0BFB..0C00 ; UNASSIGNED # .. +0C01..0C03 ; PVALID # TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; UNASSIGNED # +0C05..0C0C ; PVALID # TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0D ; UNASSIGNED # +0C0E..0C10 ; PVALID # TELUGU LETTER E..TELUGU LETTER AI +0C11 ; UNASSIGNED # +0C12..0C28 ; PVALID # TELUGU LETTER O..TELUGU LETTER NA +0C29 ; UNASSIGNED # +0C2A..0C33 ; PVALID # TELUGU LETTER PA..TELUGU LETTER LLA +0C34 ; UNASSIGNED # +0C35..0C39 ; PVALID # TELUGU LETTER VA..TELUGU LETTER HA +0C3A..0C3C ; UNASSIGNED # .. +0C3D..0C44 ; PVALID # TELUGU SIGN AVAGRAHA..TELUGU VOWEL SIGN VOCA +0C45 ; UNASSIGNED # +0C46..0C48 ; PVALID # TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C49 ; UNASSIGNED # +0C4A..0C4D ; PVALID # TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C4E..0C54 ; UNASSIGNED # .. +0C55..0C56 ; PVALID # TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C57 ; UNASSIGNED # +0C58..0C59 ; PVALID # TELUGU LETTER TSA..TELUGU LETTER DZA +0C5A..0C5F ; UNASSIGNED # .. +0C60..0C63 ; PVALID # TELUGU LETTER VOCALIC RR..TELUGU VOWEL SIGN +0C64..0C65 ; UNASSIGNED # .. +0C66..0C6F ; PVALID # TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C70..0C77 ; UNASSIGNED # .. +0C78..0C7F ; DISALLOWED # TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF +0C80..0C81 ; UNASSIGNED # .. +0C82..0C83 ; PVALID # KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84 ; UNASSIGNED # +0C85..0C8C ; PVALID # KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8D ; UNASSIGNED # +0C8E..0C90 ; PVALID # KANNADA LETTER E..KANNADA LETTER AI +0C91 ; UNASSIGNED # +0C92..0CA8 ; PVALID # KANNADA LETTER O..KANNADA LETTER NA +0CA9 ; UNASSIGNED # +0CAA..0CB3 ; PVALID # KANNADA LETTER PA..KANNADA LETTER LLA +0CB4 ; UNASSIGNED # +0CB5..0CB9 ; PVALID # KANNADA LETTER VA..KANNADA LETTER HA +0CBA..0CBB ; UNASSIGNED # .. +0CBC..0CC4 ; PVALID # KANNADA SIGN NUKTA..KANNADA VOWEL SIGN VOCAL +0CC5 ; UNASSIGNED # +0CC6..0CC8 ; PVALID # KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI +0CC9 ; UNASSIGNED # +0CCA..0CCD ; PVALID # KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA +0CCE..0CD4 ; UNASSIGNED # .. +0CD5..0CD6 ; PVALID # KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CD7..0CDD ; UNASSIGNED # .. +0CDE ; PVALID # KANNADA LETTER FA +0CDF ; UNASSIGNED # +0CE0..0CE3 ; PVALID # KANNADA LETTER VOCALIC RR..KANNADA VOWEL SIG +0CE4..0CE5 ; UNASSIGNED # .. +0CE6..0CEF ; PVALID # KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF0 ; UNASSIGNED # +0CF1..0CF2 ; DISALLOWED # KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADH +0CF3..0D01 ; UNASSIGNED # .. +0D02..0D03 ; PVALID # MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISA +0D04 ; UNASSIGNED # +0D05..0D0C ; PVALID # MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC +0D0D ; UNASSIGNED # +0D0E..0D10 ; PVALID # MALAYALAM LETTER E..MALAYALAM LETTER AI +0D11 ; UNASSIGNED # +0D12..0D28 ; PVALID # MALAYALAM LETTER O..MALAYALAM LETTER NA +0D29 ; UNASSIGNED # +0D2A..0D39 ; PVALID # MALAYALAM LETTER PA..MALAYALAM LETTER HA +0D3A..0D3C ; UNASSIGNED # .. +0D3D..0D44 ; PVALID # MALAYALAM SIGN AVAGRAHA..MALAYALAM VOWEL SIG +0D45 ; UNASSIGNED # +0D46..0D48 ; PVALID # MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN +0D49 ; UNASSIGNED # +0D4A..0D4D ; PVALID # MALAYALAM VOWEL SIGN O..MALAYALAM SIGN VIRAM +0D4E..0D56 ; UNASSIGNED # .. +0D57 ; PVALID # MALAYALAM AU LENGTH MARK +0D58..0D5F ; UNASSIGNED # .. +0D60..0D63 ; PVALID # MALAYALAM LETTER VOCALIC RR..MALAYALAM VOWEL +0D64..0D65 ; UNASSIGNED # .. +0D66..0D6F ; PVALID # MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70..0D75 ; DISALLOWED # MALAYALAM NUMBER TEN..MALAYALAM FRACTION THR +0D76..0D78 ; UNASSIGNED # .. +0D79 ; DISALLOWED # MALAYALAM DATE MARK +0D7A..0D7F ; PVALID # MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER +0D80..0D81 ; UNASSIGNED # .. +0D82..0D83 ; PVALID # SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARG +0D84 ; UNASSIGNED # +0D85..0D96 ; PVALID # SINHALA LETTER AYANNA..SINHALA LETTER AUYANN +0D97..0D99 ; UNASSIGNED # .. +0D9A..0DB1 ; PVALID # SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA L +0DB2 ; UNASSIGNED # +0DB3..0DBB ; PVALID # SINHALA LETTER SANYAKA DAYANNA..SINHALA LETT +0DBC ; UNASSIGNED # +0DBD ; PVALID # SINHALA LETTER DANTAJA LAYANNA +0DBE..0DBF ; UNASSIGNED # .. +0DC0..0DC6 ; PVALID # SINHALA LETTER VAYANNA..SINHALA LETTER FAYAN +0DC7..0DC9 ; UNASSIGNED # .. +0DCA ; PVALID # SINHALA SIGN AL-LAKUNA +0DCB..0DCE ; UNASSIGNED # .. +0DCF..0DD4 ; PVALID # SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL +0DD5 ; UNASSIGNED # +0DD6 ; PVALID # SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD7 ; UNASSIGNED # +0DD8..0DDF ; PVALID # SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOW +0DE0..0DF1 ; UNASSIGNED # .. +0DF2..0DF3 ; PVALID # SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHAL +0DF4 ; DISALLOWED # SINHALA PUNCTUATION KUNDDALIYA +0DF5..0E00 ; UNASSIGNED # .. +0E01..0E32 ; PVALID # THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E33 ; DISALLOWED # THAI CHARACTER SARA AM +0E34..0E3A ; PVALID # THAI CHARACTER SARA I..THAI CHARACTER PHINTH +0E3B..0E3E ; UNASSIGNED # .. +0E3F ; DISALLOWED # THAI CURRENCY SYMBOL BAHT +0E40..0E4E ; PVALID # THAI CHARACTER SARA E..THAI CHARACTER YAMAKK +0E4F ; DISALLOWED # THAI CHARACTER FONGMAN +0E50..0E59 ; PVALID # THAI DIGIT ZERO..THAI DIGIT NINE +0E5A..0E5B ; DISALLOWED # THAI CHARACTER ANGKHANKHU..THAI CHARACTER KH +0E5C..0E80 ; UNASSIGNED # .. +0E81..0E82 ; PVALID # LAO LETTER KO..LAO LETTER KHO SUNG +0E83 ; UNASSIGNED # +0E84 ; PVALID # LAO LETTER KHO TAM +0E85..0E86 ; UNASSIGNED # .. +0E87..0E88 ; PVALID # LAO LETTER NGO..LAO LETTER CO +0E89 ; UNASSIGNED # +0E8A ; PVALID # LAO LETTER SO TAM +0E8B..0E8C ; UNASSIGNED # .. +0E8D ; PVALID # LAO LETTER NYO +0E8E..0E93 ; UNASSIGNED # .. +0E94..0E97 ; PVALID # LAO LETTER DO..LAO LETTER THO TAM +0E98 ; UNASSIGNED # +0E99..0E9F ; PVALID # LAO LETTER NO..LAO LETTER FO SUNG +0EA0 ; UNASSIGNED # +0EA1..0EA3 ; PVALID # LAO LETTER MO..LAO LETTER LO LING +0EA4 ; UNASSIGNED # +0EA5 ; PVALID # LAO LETTER LO LOOT +0EA6 ; UNASSIGNED # +0EA7 ; PVALID # LAO LETTER WO +0EA8..0EA9 ; UNASSIGNED # .. +0EAA..0EAB ; PVALID # LAO LETTER SO SUNG..LAO LETTER HO SUNG +0EAC ; UNASSIGNED # +0EAD..0EB2 ; PVALID # LAO LETTER O..LAO VOWEL SIGN AA +0EB3 ; DISALLOWED # LAO VOWEL SIGN AM +0EB4..0EB9 ; PVALID # LAO VOWEL SIGN I..LAO VOWEL SIGN UU +0EBA ; UNASSIGNED # +0EBB..0EBD ; PVALID # LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN N +0EBE..0EBF ; UNASSIGNED # .. +0EC0..0EC4 ; PVALID # LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC5 ; UNASSIGNED # +0EC6 ; PVALID # LAO KO LA +0EC7 ; UNASSIGNED # +0EC8..0ECD ; PVALID # LAO TONE MAI EK..LAO NIGGAHITA +0ECE..0ECF ; UNASSIGNED # .. +0ED0..0ED9 ; PVALID # LAO DIGIT ZERO..LAO DIGIT NINE +0EDA..0EDB ; UNASSIGNED # .. +0EDC..0EDD ; DISALLOWED # LAO HO NO..LAO HO MO +0EDE..0EFF ; UNASSIGNED # .. +0F00 ; PVALID # TIBETAN SYLLABLE OM +0F01..0F0A ; DISALLOWED # TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBET +0F0B ; PVALID # TIBETAN MARK INTERSYLLABIC TSHEG +0F0C..0F17 ; DISALLOWED # TIBETAN MARK DELIMITER TSHEG BSTAR..TIBETAN +0F18..0F19 ; PVALID # TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN +0F1A..0F1F ; DISALLOWED # TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RD +0F20..0F29 ; PVALID # TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A..0F34 ; DISALLOWED # TIBETAN DIGIT HALF ONE..TIBETAN MARK BSDUS R +0F35 ; PVALID # TIBETAN MARK NGAS BZUNG NYI ZLA +0F36 ; DISALLOWED # TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37 ; PVALID # TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38 ; DISALLOWED # TIBETAN MARK CHE MGO +0F39 ; PVALID # TIBETAN MARK TSA -PHRU +0F3A..0F3D ; DISALLOWED # TIBETAN MARK GUG RTAGS GYON..TIBETAN MARK AN +0F3E..0F42 ; PVALID # TIBETAN SIGN YAR TSHES..TIBETAN LETTER GA +0F43 ; DISALLOWED # TIBETAN LETTER GHA +0F44..0F47 ; PVALID # TIBETAN LETTER NGA..TIBETAN LETTER JA +0F48 ; UNASSIGNED # +0F49..0F4C ; PVALID # TIBETAN LETTER NYA..TIBETAN LETTER DDA +0F4D ; DISALLOWED # TIBETAN LETTER DDHA +0F4E..0F51 ; PVALID # TIBETAN LETTER NNA..TIBETAN LETTER DA +0F52 ; DISALLOWED # TIBETAN LETTER DHA +0F53..0F56 ; PVALID # TIBETAN LETTER NA..TIBETAN LETTER BA +0F57 ; DISALLOWED # TIBETAN LETTER BHA +0F58..0F5B ; PVALID # TIBETAN LETTER MA..TIBETAN LETTER DZA +0F5C ; DISALLOWED # TIBETAN LETTER DZHA +0F5D..0F68 ; PVALID # TIBETAN LETTER WA..TIBETAN LETTER A +0F69 ; DISALLOWED # TIBETAN LETTER KSSA +0F6A..0F6C ; PVALID # TIBETAN LETTER FIXED-FORM RA..TIBETAN LETTER +0F6D..0F70 ; UNASSIGNED # .. +0F71..0F72 ; PVALID # TIBETAN VOWEL SIGN AA..TIBETAN VOWEL SIGN I +0F73 ; DISALLOWED # TIBETAN VOWEL SIGN II +0F74 ; PVALID # TIBETAN VOWEL SIGN U +0F75..0F79 ; DISALLOWED # TIBETAN VOWEL SIGN UU..TIBETAN VOWEL SIGN VO +0F7A..0F80 ; PVALID # TIBETAN VOWEL SIGN E..TIBETAN VOWEL SIGN REV +0F81 ; DISALLOWED # TIBETAN VOWEL SIGN REVERSED II +0F82..0F84 ; PVALID # TIBETAN SIGN NYI ZLA NAA DA..TIBETAN MARK HA +0F85 ; DISALLOWED # TIBETAN MARK PALUTA +0F86..0F8B ; PVALID # TIBETAN SIGN LCI RTAGS..TIBETAN SIGN GRU MED +0F8C..0F8F ; UNASSIGNED # .. +0F90..0F92 ; PVALID # TIBETAN SUBJOINED LETTER KA..TIBETAN SUBJOIN +0F93 ; DISALLOWED # TIBETAN SUBJOINED LETTER GHA +0F94..0F97 ; PVALID # TIBETAN SUBJOINED LETTER NGA..TIBETAN SUBJOI +0F98 ; UNASSIGNED # +0F99..0F9C ; PVALID # TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOI +0F9D ; DISALLOWED # TIBETAN SUBJOINED LETTER DDHA +0F9E..0FA1 ; PVALID # TIBETAN SUBJOINED LETTER NNA..TIBETAN SUBJOI +0FA2 ; DISALLOWED # TIBETAN SUBJOINED LETTER DHA +0FA3..0FA6 ; PVALID # TIBETAN SUBJOINED LETTER NA..TIBETAN SUBJOIN +0FA7 ; DISALLOWED # TIBETAN SUBJOINED LETTER BHA +0FA8..0FAB ; PVALID # TIBETAN SUBJOINED LETTER MA..TIBETAN SUBJOIN +0FAC ; DISALLOWED # TIBETAN SUBJOINED LETTER DZHA +0FAD..0FB8 ; PVALID # TIBETAN SUBJOINED LETTER WA..TIBETAN SUBJOIN +0FB9 ; DISALLOWED # TIBETAN SUBJOINED LETTER KSSA +0FBA..0FBC ; PVALID # TIBETAN SUBJOINED LETTER FIXED-FORM WA..TIBE +0FBD ; UNASSIGNED # +0FBE..0FC5 ; DISALLOWED # TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC6 ; PVALID # TIBETAN SYMBOL PADMA GDAN +0FC7..0FCC ; DISALLOWED # TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SY +0FCD ; UNASSIGNED # +0FCE..0FD8 ; DISALLOWED # TIBETAN SIGN RDEL NAG RDEL DKAR..LEFT-FACING +0FD9..0FFF ; UNASSIGNED # .. +1000..1049 ; PVALID # MYANMAR LETTER KA..MYANMAR DIGIT NINE +10000..1000B; PVALID # LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE +1000C ; UNASSIGNED # +1000D..10026; PVALID # LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE +10027 ; UNASSIGNED # +10028..1003A; PVALID # LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE +1003B ; UNASSIGNED # +1003C..1003D; PVALID # LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE +1003E ; UNASSIGNED # +1003F..1004D; PVALID # LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE +1004E..1004F; UNASSIGNED # .. +10050..1005D; PVALID # LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +1005E..1007F; UNASSIGNED # .. +10080..100FA; PVALID # LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRA +100FB..100FF; UNASSIGNED # .. +10100..10102; DISALLOWED # AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MAR +10103..10106; UNASSIGNED # .. +10107..10133; DISALLOWED # AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOU +10134..10136; UNASSIGNED # .. +10137..1018A; DISALLOWED # AEGEAN WEIGHT BASE UNIT..GREEK ZERO SIGN +1018B..1018F; UNASSIGNED # .. +10190..1019B; DISALLOWED # ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN +1019C..101CF; UNASSIGNED # .. +101D0..101FC; DISALLOWED # PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC +101FD ; PVALID # PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +101FE..1027F; UNASSIGNED # .. +10280..1029C; PVALID # LYCIAN LETTER A..LYCIAN LETTER X +1029D..1029F; UNASSIGNED # .. +102A0..102D0; PVALID # CARIAN LETTER A..CARIAN LETTER UUU3 +102D1..102FF; UNASSIGNED # .. +10300..1031E; PVALID # OLD ITALIC LETTER A..OLD ITALIC LETTER UU +1031F ; UNASSIGNED # +10320..10323; DISALLOWED # OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL F +10324..1032F; UNASSIGNED # .. +10330..10340; PVALID # GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341 ; DISALLOWED # GOTHIC LETTER NINETY +10342..10349; PVALID # GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; DISALLOWED # GOTHIC LETTER NINE HUNDRED +1034B..1037F; UNASSIGNED # .. +10380..1039D; PVALID # UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039E ; UNASSIGNED # +1039F ; DISALLOWED # UGARITIC WORD DIVIDER +103A0..103C3; PVALID # OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C4..103C7; UNASSIGNED # .. +103C8..103CF; PVALID # OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIG +103D0..103D5; DISALLOWED # OLD PERSIAN WORD DIVIDER..OLD PERSIAN NUMBER +103D6..103FF; UNASSIGNED # .. +10400..10427; DISALLOWED # DESERET CAPITAL LETTER LONG I..DESERET CAPIT +10428..1049D; PVALID # DESERET SMALL LETTER LONG I..OSMANYA LETTER +1049E..1049F; UNASSIGNED # .. +104A..104F ; DISALLOWED # MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL +104A0..104A9; PVALID # OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104AA..107FF; UNASSIGNED # .. +1050..109D ; PVALID # MYANMAR LETTER SHA..MYANMAR VOWEL SIGN AITON +10800..10805; PVALID # CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10806..10807; UNASSIGNED # .. +10808 ; PVALID # CYPRIOT SYLLABLE JO +10809 ; UNASSIGNED # +1080A..10835; PVALID # CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10836 ; UNASSIGNED # +10837..10838; PVALID # CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +10839..1083B; UNASSIGNED # .. +1083C ; PVALID # CYPRIOT SYLLABLE ZA +1083D..1083E; UNASSIGNED # .. +1083F..10855; PVALID # CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER +10856 ; UNASSIGNED # +10857..1085F; DISALLOWED # IMPERIAL ARAMAIC SECTION SIGN..IMPERIAL ARAM +10860..108FF; UNASSIGNED # .. +10900..10915; PVALID # PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916..1091B; DISALLOWED # PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THR +1091C..1091E; UNASSIGNED # .. +1091F ; DISALLOWED # PHOENICIAN WORD SEPARATOR +10920..10939; PVALID # LYDIAN LETTER A..LYDIAN LETTER C +1093A..1093E; UNASSIGNED # .. +1093F ; DISALLOWED # LYDIAN TRIANGULAR MARK +10940..109FF; UNASSIGNED # .. +109E..10C5 ; DISALLOWED # MYANMAR SYMBOL SHAN ONE..GEORGIAN CAPITAL LE +10A00..10A03; PVALID # KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN V +10A04 ; UNASSIGNED # +10A05..10A06; PVALID # KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SI +10A07..10A0B; UNASSIGNED # .. +10A0C..10A13; PVALID # KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI LET +10A14 ; UNASSIGNED # +10A15..10A17; PVALID # KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A18 ; UNASSIGNED # +10A19..10A33; PVALID # KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER TTT +10A34..10A37; UNASSIGNED # .. +10A38..10A3A; PVALID # KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN D +10A3B..10A3E; UNASSIGNED # .. +10A3F ; PVALID # KHAROSHTHI VIRAMA +10A40..10A47; DISALLOWED # KHAROSHTHI DIGIT ONE..KHAROSHTHI NUMBER ONE +10A48..10A4F; UNASSIGNED # .. +10A50..10A58; DISALLOWED # KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCT +10A59..10A5F; UNASSIGNED # .. +10A60..10A7C; PVALID # OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABI +10A7D..10A7F; DISALLOWED # OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARAB +10A80..10AFF; UNASSIGNED # .. +10B00..10B35; PVALID # AVESTAN LETTER A..AVESTAN LETTER HE +10B36..10B38; UNASSIGNED # .. +10B39..10B3F; DISALLOWED # AVESTAN ABBREVIATION MARK..LARGE ONE RING OV +10B40..10B55; PVALID # INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIP +10B56..10B57; UNASSIGNED # .. +10B58..10B5F; DISALLOWED # INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTI +10B60..10B72; PVALID # INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPT +10B73..10B77; UNASSIGNED # .. +10B78..10B7F; DISALLOWED # INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIO +10B80..10BFF; UNASSIGNED # .. +10C00..10C48; PVALID # OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTE +10C49..10E5F; UNASSIGNED # .. +10C6..10CF ; UNASSIGNED # .. +10D0..10FA ; PVALID # GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10E60..10E7E; DISALLOWED # RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E7F..1107F; UNASSIGNED # .. +10FB..10FC ; DISALLOWED # GEORGIAN PARAGRAPH SEPARATOR..MODIFIER LETTE +10FD..10FF ; UNASSIGNED # .. +1100..11FF ; DISALLOWED # HANGUL CHOSEONG KIYEOK..HANGUL JONGSEONG SSA +11080..110BA; PVALID # KAITHI SIGN CANDRABINDU..KAITHI SIGN NUKTA +110BB..110C1; DISALLOWED # KAITHI ABBREVIATION SIGN..KAITHI DOUBLE DAND +110C2..11FFF; UNASSIGNED # .. +1200..1248 ; PVALID # ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +12000..1236E; PVALID # CUNEIFORM SIGN A..CUNEIFORM SIGN ZUM +1236F..123FF; UNASSIGNED # .. +12400..12462; DISALLOWED # CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NU +12463..1246F; UNASSIGNED # .. +12470..12473; DISALLOWED # CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD +12474..12FFF; UNASSIGNED # .. +1249 ; UNASSIGNED # +124A..124D ; PVALID # ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +124E..124F ; UNASSIGNED # .. +1250..1256 ; PVALID # ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1257 ; UNASSIGNED # +1258 ; PVALID # ETHIOPIC SYLLABLE QHWA +1259 ; UNASSIGNED # +125A..125D ; PVALID # ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QH +125E..125F ; UNASSIGNED # .. +1260..1288 ; PVALID # ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +1289 ; UNASSIGNED # +128A..128D ; PVALID # ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +128E..128F ; UNASSIGNED # .. +1290..12B0 ; PVALID # ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B1 ; UNASSIGNED # +12B2..12B5 ; PVALID # ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B6..12B7 ; UNASSIGNED # .. +12B8..12BE ; PVALID # ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12BF ; UNASSIGNED # +12C0 ; PVALID # ETHIOPIC SYLLABLE KXWA +12C1 ; UNASSIGNED # +12C2..12C5 ; PVALID # ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KX +12C6..12C7 ; UNASSIGNED # .. +12C8..12D6 ; PVALID # ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHAR +12D7 ; UNASSIGNED # +12D8..1310 ; PVALID # ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +13000..1342E; PVALID # EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYP +1311 ; UNASSIGNED # +1312..1315 ; PVALID # ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1316..1317 ; UNASSIGNED # .. +1318..135A ; PVALID # ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +1342F..1CFFF; UNASSIGNED # .. +135B..135E ; UNASSIGNED # .. +135F ; PVALID # ETHIOPIC COMBINING GEMINATION MARK +1360..137C ; DISALLOWED # ETHIOPIC SECTION MARK..ETHIOPIC NUMBER TEN T +137D..137F ; UNASSIGNED # .. +1380..138F ; PVALID # ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SY +1390..1399 ; DISALLOWED # ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MA +139A..139F ; UNASSIGNED # .. +13A0..13F4 ; PVALID # CHEROKEE LETTER A..CHEROKEE LETTER YV +13F5..13FF ; UNASSIGNED # .. +1400 ; DISALLOWED # CANADIAN SYLLABICS HYPHEN +1401..166C ; PVALID # CANADIAN SYLLABICS E..CANADIAN SYLLABICS CAR +166D..166E ; DISALLOWED # CANADIAN SYLLABICS CHI SIGN..CANADIAN SYLLAB +166F..167F ; PVALID # CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS B +1680 ; DISALLOWED # OGHAM SPACE MARK +1681..169A ; PVALID # OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B..169C ; DISALLOWED # OGHAM FEATHER MARK..OGHAM REVERSED FEATHER M +169D..169F ; UNASSIGNED # .. +16A0..16EA ; PVALID # RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB..16F0 ; DISALLOWED # RUNIC SINGLE PUNCTUATION..RUNIC BELGTHOR SYM +16F1..16FF ; UNASSIGNED # .. +1700..170C ; PVALID # TAGALOG LETTER A..TAGALOG LETTER YA +170D ; UNASSIGNED # +170E..1714 ; PVALID # TAGALOG LETTER LA..TAGALOG SIGN VIRAMA +1715..171F ; UNASSIGNED # .. +1720..1734 ; PVALID # HANUNOO LETTER A..HANUNOO SIGN PAMUDPOD +1735..1736 ; DISALLOWED # PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DO +1737..173F ; UNASSIGNED # .. +1740..1753 ; PVALID # BUHID LETTER A..BUHID VOWEL SIGN U +1754..175F ; UNASSIGNED # .. +1760..176C ; PVALID # TAGBANWA LETTER A..TAGBANWA LETTER YA +176D ; UNASSIGNED # +176E..1770 ; PVALID # TAGBANWA LETTER LA..TAGBANWA LETTER SA +1771 ; UNASSIGNED # +1772..1773 ; PVALID # TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1774..177F ; UNASSIGNED # .. +1780..17B3 ; PVALID # KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4..17B5 ; DISALLOWED # KHMER VOWEL INHERENT AQ..KHMER VOWEL INHEREN +17B6..17D3 ; PVALID # KHMER VOWEL SIGN AA..KHMER SIGN BATHAMASAT +17D4..17D6 ; DISALLOWED # KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D7 ; PVALID # KHMER SIGN LEK TOO +17D8..17DB ; DISALLOWED # KHMER SIGN BEYYAL..KHMER CURRENCY SYMBOL RIE +17DC..17DD ; PVALID # KHMER SIGN AVAKRAHASANYA..KHMER SIGN ATTHACA +17DE..17DF ; UNASSIGNED # .. +17E0..17E9 ; PVALID # KHMER DIGIT ZERO..KHMER DIGIT NINE +17EA..17EF ; UNASSIGNED # .. +17F0..17F9 ; DISALLOWED # KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK +17FA..17FF ; UNASSIGNED # .. +1800..180E ; DISALLOWED # MONGOLIAN BIRGA..MONGOLIAN VOWEL SEPARATOR +180F ; UNASSIGNED # +1810..1819 ; PVALID # MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +181A..181F ; UNASSIGNED # .. +1820..1877 ; PVALID # MONGOLIAN LETTER A..MONGOLIAN LETTER MANCHU +1878..187F ; UNASSIGNED # .. +1880..18AA ; PVALID # MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONG +18AB..18AF ; UNASSIGNED # .. +18B0..18F5 ; PVALID # CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CA +18F6..18FF ; UNASSIGNED # .. +1900..191C ; PVALID # LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER HA +191D..191F ; UNASSIGNED # .. +1920..192B ; PVALID # LIMBU VOWEL SIGN A..LIMBU SUBJOINED LETTER W +192C..192F ; UNASSIGNED # .. +1930..193B ; PVALID # LIMBU SMALL LETTER KA..LIMBU SIGN SA-I +193C..193F ; UNASSIGNED # .. +1940 ; DISALLOWED # LIMBU SIGN LOO +1941..1943 ; UNASSIGNED # .. +1944..1945 ; DISALLOWED # LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946..196D ; PVALID # LIMBU DIGIT ZERO..TAI LE LETTER AI +196E..196F ; UNASSIGNED # .. +1970..1974 ; PVALID # TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1975..197F ; UNASSIGNED # .. +1980..19AB ; PVALID # NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETT +19AC..19AF ; UNASSIGNED # .. +19B0..19C9 ; PVALID # NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW +19CA..19CF ; UNASSIGNED # .. +19D0..19DA ; PVALID # NEW TAI LUE DIGIT ZERO..NEW TAI LUE THAM DIG +19DB..19DD ; UNASSIGNED # .. +19DE..19FF ; DISALLOWED # NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM +1A00..1A1B ; PVALID # BUGINESE LETTER KA..BUGINESE VOWEL SIGN AE +1A1C..1A1D ; UNASSIGNED # .. +1A1E..1A1F ; DISALLOWED # BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20..1A5E ; PVALID # TAI THAM LETTER HIGH KA..TAI THAM CONSONANT +1A5F ; UNASSIGNED # +1A60..1A7C ; PVALID # TAI THAM SIGN SAKOT..TAI THAM SIGN KHUEN-LUE +1A7D..1A7E ; UNASSIGNED # .. +1A7F..1A89 ; PVALID # TAI THAM COMBINING CRYPTOGRAMMIC DOT..TAI TH +1A8A..1A8F ; UNASSIGNED # .. +1A90..1A99 ; PVALID # TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGI +1A9A..1A9F ; UNASSIGNED # .. +1AA0..1AA6 ; DISALLOWED # TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED +1AA7 ; PVALID # TAI THAM SIGN MAI YAMOK +1AA8..1AAD ; DISALLOWED # TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AAE..1AFF ; UNASSIGNED # .. +1B00..1B4B ; PVALID # BALINESE SIGN ULU RICEM..BALINESE LETTER ASY +1B4C..1B4F ; UNASSIGNED # .. +1B50..1B59 ; PVALID # BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A..1B6A ; DISALLOWED # BALINESE PANTI..BALINESE MUSICAL SYMBOL DANG +1B6B..1B73 ; PVALID # BALINESE MUSICAL SYMBOL COMBINING TEGEH..BAL +1B74..1B7C ; DISALLOWED # BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG. +1B7D..1B7F ; UNASSIGNED # .. +1B80..1BAA ; PVALID # SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PAMA +1BAB..1BAD ; UNASSIGNED # .. +1BAE..1BB9 ; PVALID # SUNDANESE LETTER KHA..SUNDANESE DIGIT NINE +1BBA..1BFF ; UNASSIGNED # .. +1C00..1C37 ; PVALID # LEPCHA LETTER KA..LEPCHA SIGN NUKTA +1C38..1C3A ; UNASSIGNED # .. +1C3B..1C3F ; DISALLOWED # LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATIO +1C40..1C49 ; PVALID # LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4A..1C4C ; UNASSIGNED # .. +1C4D..1C7D ; PVALID # LEPCHA LETTER TTA..OL CHIKI AHAD +1C7E..1C7F ; DISALLOWED # OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTU +1C80..1CCF ; UNASSIGNED # .. +1CD0..1CD2 ; PVALID # VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3 ; DISALLOWED # VEDIC SIGN NIHSHVASA +1CD4..1CF2 ; PVALID # VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC +1CF3..1CFF ; UNASSIGNED # .. +1D00..1D2B ; PVALID # LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTE +1D000..1D0F5; DISALLOWED # BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MU +1D0F6..1D0FF; UNASSIGNED # .. +1D100..1D126; DISALLOWED # MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBO +1D127..1D128; UNASSIGNED # .. +1D129..1D1DD; DISALLOWED # MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICA +1D1DE..1D1FF; UNASSIGNED # .. +1D200..1D245; DISALLOWED # GREEK VOCAL NOTATION SYMBOL-1..GREEK MUSICAL +1D246..1D2FF; UNASSIGNED # .. +1D2C..1D2E ; DISALLOWED # MODIFIER LETTER CAPITAL A..MODIFIER LETTER C +1D2F ; PVALID # MODIFIER LETTER CAPITAL BARRED B +1D30..1D3A ; DISALLOWED # MODIFIER LETTER CAPITAL D..MODIFIER LETTER C +1D300..1D356; DISALLOWED # MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D357..1D35F; UNASSIGNED # .. +1D360..1D371; DISALLOWED # COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TE +1D372..1D3FF; UNASSIGNED # .. +1D3B ; PVALID # MODIFIER LETTER CAPITAL REVERSED N +1D3C..1D4D ; DISALLOWED # MODIFIER LETTER CAPITAL O..MODIFIER LETTER S +1D400..1D454; DISALLOWED # MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL IT +1D455 ; UNASSIGNED # +1D456..1D49C; DISALLOWED # MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SC +1D49D ; UNASSIGNED # +1D49E..1D49F; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL +1D4A0..1D4A1; UNASSIGNED # .. +1D4A2 ; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL G +1D4A3..1D4A4; UNASSIGNED # .. +1D4A5..1D4A6; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL +1D4A7..1D4A8; UNASSIGNED # .. +1D4A9..1D4AC; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL +1D4AD ; UNASSIGNED # +1D4AE..1D4B9; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL +1D4BA ; UNASSIGNED # +1D4BB ; DISALLOWED # MATHEMATICAL SCRIPT SMALL F +1D4BC ; UNASSIGNED # +1D4BD..1D4C3; DISALLOWED # MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SC +1D4C4 ; UNASSIGNED # +1D4C5..1D505; DISALLOWED # MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FR +1D4E ; PVALID # MODIFIER LETTER SMALL TURNED I +1D4F..1D6A ; DISALLOWED # MODIFIER LETTER SMALL K..GREEK SUBSCRIPT SMA +1D506 ; UNASSIGNED # +1D507..1D50A; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL +1D50B..1D50C; UNASSIGNED # .. +1D50D..1D514; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL +1D515 ; UNASSIGNED # +1D516..1D51C; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL +1D51D ; UNASSIGNED # +1D51E..1D539; DISALLOWED # MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL D +1D53A ; UNASSIGNED # +1D53B..1D53E; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEM +1D53F ; UNASSIGNED # +1D540..1D544; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEM +1D545 ; UNASSIGNED # +1D546 ; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D547..1D549; UNASSIGNED # .. +1D54A..1D550; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEM +1D551 ; UNASSIGNED # +1D552..1D6A5; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMAT +1D6A6..1D6A7; UNASSIGNED # .. +1D6A8..1D7CB; DISALLOWED # MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICA +1D6B..1D77 ; PVALID # LATIN SMALL LETTER UE..LATIN SMALL LETTER TU +1D78 ; DISALLOWED # MODIFIER LETTER CYRILLIC EN +1D79..1D9A ; PVALID # LATIN SMALL LETTER INSULAR G..LATIN SMALL LE +1D7CC..1D7CD; UNASSIGNED # .. +1D7CE..1D7FF; DISALLOWED # MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL M +1D800..1EFFF; UNASSIGNED # .. +1D9B..1DBF ; DISALLOWED # MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER +1DC0..1DE6 ; PVALID # COMBINING DOTTED GRAVE ACCENT..COMBINING LAT +1DE7..1DFC ; UNASSIGNED # .. +1DFD..1DFF ; PVALID # COMBINING ALMOST EQUAL TO BELOW..COMBINING R +1E00 ; DISALLOWED # LATIN CAPITAL LETTER A WITH RING BELOW +1E01 ; PVALID # LATIN SMALL LETTER A WITH RING BELOW +1E02 ; DISALLOWED # LATIN CAPITAL LETTER B WITH DOT ABOVE +1E03 ; PVALID # LATIN SMALL LETTER B WITH DOT ABOVE +1E04 ; DISALLOWED # LATIN CAPITAL LETTER B WITH DOT BELOW +1E05 ; PVALID # LATIN SMALL LETTER B WITH DOT BELOW +1E06 ; DISALLOWED # LATIN CAPITAL LETTER B WITH LINE BELOW +1E07 ; PVALID # LATIN SMALL LETTER B WITH LINE BELOW +1E08 ; DISALLOWED # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUT +1E09 ; PVALID # LATIN SMALL LETTER C WITH CEDILLA AND ACUTE +1E0A ; DISALLOWED # LATIN CAPITAL LETTER D WITH DOT ABOVE +1E0B ; PVALID # LATIN SMALL LETTER D WITH DOT ABOVE +1E0C ; DISALLOWED # LATIN CAPITAL LETTER D WITH DOT BELOW +1E0D ; PVALID # LATIN SMALL LETTER D WITH DOT BELOW +1E0E ; DISALLOWED # LATIN CAPITAL LETTER D WITH LINE BELOW +1E0F ; PVALID # LATIN SMALL LETTER D WITH LINE BELOW +1E10 ; DISALLOWED # LATIN CAPITAL LETTER D WITH CEDILLA +1E11 ; PVALID # LATIN SMALL LETTER D WITH CEDILLA +1E12 ; DISALLOWED # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +1E13 ; PVALID # LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E14 ; DISALLOWED # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +1E15 ; PVALID # LATIN SMALL LETTER E WITH MACRON AND GRAVE +1E16 ; DISALLOWED # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +1E17 ; PVALID # LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E18 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +1E19 ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW +1E1A ; DISALLOWED # LATIN CAPITAL LETTER E WITH TILDE BELOW +1E1B ; PVALID # LATIN SMALL LETTER E WITH TILDE BELOW +1E1C ; DISALLOWED # LATIN CAPITAL LETTER E WITH CEDILLA AND BREV +1E1D ; PVALID # LATIN SMALL LETTER E WITH CEDILLA AND BREVE +1E1E ; DISALLOWED # LATIN CAPITAL LETTER F WITH DOT ABOVE +1E1F ; PVALID # LATIN SMALL LETTER F WITH DOT ABOVE +1E20 ; DISALLOWED # LATIN CAPITAL LETTER G WITH MACRON +1E21 ; PVALID # LATIN SMALL LETTER G WITH MACRON +1E22 ; DISALLOWED # LATIN CAPITAL LETTER H WITH DOT ABOVE +1E23 ; PVALID # LATIN SMALL LETTER H WITH DOT ABOVE +1E24 ; DISALLOWED # LATIN CAPITAL LETTER H WITH DOT BELOW +1E25 ; PVALID # LATIN SMALL LETTER H WITH DOT BELOW +1E26 ; DISALLOWED # LATIN CAPITAL LETTER H WITH DIAERESIS +1E27 ; PVALID # LATIN SMALL LETTER H WITH DIAERESIS +1E28 ; DISALLOWED # LATIN CAPITAL LETTER H WITH CEDILLA +1E29 ; PVALID # LATIN SMALL LETTER H WITH CEDILLA +1E2A ; DISALLOWED # LATIN CAPITAL LETTER H WITH BREVE BELOW +1E2B ; PVALID # LATIN SMALL LETTER H WITH BREVE BELOW +1E2C ; DISALLOWED # LATIN CAPITAL LETTER I WITH TILDE BELOW +1E2D ; PVALID # LATIN SMALL LETTER I WITH TILDE BELOW +1E2E ; DISALLOWED # LATIN CAPITAL LETTER I WITH DIAERESIS AND AC +1E2F ; PVALID # LATIN SMALL LETTER I WITH DIAERESIS AND ACUT +1E30 ; DISALLOWED # LATIN CAPITAL LETTER K WITH ACUTE +1E31 ; PVALID # LATIN SMALL LETTER K WITH ACUTE +1E32 ; DISALLOWED # LATIN CAPITAL LETTER K WITH DOT BELOW +1E33 ; PVALID # LATIN SMALL LETTER K WITH DOT BELOW +1E34 ; DISALLOWED # LATIN CAPITAL LETTER K WITH LINE BELOW +1E35 ; PVALID # LATIN SMALL LETTER K WITH LINE BELOW +1E36 ; DISALLOWED # LATIN CAPITAL LETTER L WITH DOT BELOW +1E37 ; PVALID # LATIN SMALL LETTER L WITH DOT BELOW +1E38 ; DISALLOWED # LATIN CAPITAL LETTER L WITH DOT BELOW AND MA +1E39 ; PVALID # LATIN SMALL LETTER L WITH DOT BELOW AND MACR +1E3A ; DISALLOWED # LATIN CAPITAL LETTER L WITH LINE BELOW +1E3B ; PVALID # LATIN SMALL LETTER L WITH LINE BELOW +1E3C ; DISALLOWED # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +1E3D ; PVALID # LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW +1E3E ; DISALLOWED # LATIN CAPITAL LETTER M WITH ACUTE +1E3F ; PVALID # LATIN SMALL LETTER M WITH ACUTE +1E40 ; DISALLOWED # LATIN CAPITAL LETTER M WITH DOT ABOVE +1E41 ; PVALID # LATIN SMALL LETTER M WITH DOT ABOVE +1E42 ; DISALLOWED # LATIN CAPITAL LETTER M WITH DOT BELOW +1E43 ; PVALID # LATIN SMALL LETTER M WITH DOT BELOW +1E44 ; DISALLOWED # LATIN CAPITAL LETTER N WITH DOT ABOVE +1E45 ; PVALID # LATIN SMALL LETTER N WITH DOT ABOVE +1E46 ; DISALLOWED # LATIN CAPITAL LETTER N WITH DOT BELOW +1E47 ; PVALID # LATIN SMALL LETTER N WITH DOT BELOW +1E48 ; DISALLOWED # LATIN CAPITAL LETTER N WITH LINE BELOW +1E49 ; PVALID # LATIN SMALL LETTER N WITH LINE BELOW +1E4A ; DISALLOWED # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +1E4B ; PVALID # LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E4C ; DISALLOWED # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +1E4D ; PVALID # LATIN SMALL LETTER O WITH TILDE AND ACUTE +1E4E ; DISALLOWED # LATIN CAPITAL LETTER O WITH TILDE AND DIAERE +1E4F ; PVALID # LATIN SMALL LETTER O WITH TILDE AND DIAERESI +1E50 ; DISALLOWED # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +1E51 ; PVALID # LATIN SMALL LETTER O WITH MACRON AND GRAVE +1E52 ; DISALLOWED # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +1E53 ; PVALID # LATIN SMALL LETTER O WITH MACRON AND ACUTE +1E54 ; DISALLOWED # LATIN CAPITAL LETTER P WITH ACUTE +1E55 ; PVALID # LATIN SMALL LETTER P WITH ACUTE +1E56 ; DISALLOWED # LATIN CAPITAL LETTER P WITH DOT ABOVE +1E57 ; PVALID # LATIN SMALL LETTER P WITH DOT ABOVE +1E58 ; DISALLOWED # LATIN CAPITAL LETTER R WITH DOT ABOVE +1E59 ; PVALID # LATIN SMALL LETTER R WITH DOT ABOVE +1E5A ; DISALLOWED # LATIN CAPITAL LETTER R WITH DOT BELOW +1E5B ; PVALID # LATIN SMALL LETTER R WITH DOT BELOW +1E5C ; DISALLOWED # LATIN CAPITAL LETTER R WITH DOT BELOW AND MA +1E5D ; PVALID # LATIN SMALL LETTER R WITH DOT BELOW AND MACR +1E5E ; DISALLOWED # LATIN CAPITAL LETTER R WITH LINE BELOW +1E5F ; PVALID # LATIN SMALL LETTER R WITH LINE BELOW +1E60 ; DISALLOWED # LATIN CAPITAL LETTER S WITH DOT ABOVE +1E61 ; PVALID # LATIN SMALL LETTER S WITH DOT ABOVE +1E62 ; DISALLOWED # LATIN CAPITAL LETTER S WITH DOT BELOW +1E63 ; PVALID # LATIN SMALL LETTER S WITH DOT BELOW +1E64 ; DISALLOWED # LATIN CAPITAL LETTER S WITH ACUTE AND DOT AB +1E65 ; PVALID # LATIN SMALL LETTER S WITH ACUTE AND DOT ABOV +1E66 ; DISALLOWED # LATIN CAPITAL LETTER S WITH CARON AND DOT AB +1E67 ; PVALID # LATIN SMALL LETTER S WITH CARON AND DOT ABOV +1E68 ; DISALLOWED # LATIN CAPITAL LETTER S WITH DOT BELOW AND DO +1E69 ; PVALID # LATIN SMALL LETTER S WITH DOT BELOW AND DOT +1E6A ; DISALLOWED # LATIN CAPITAL LETTER T WITH DOT ABOVE +1E6B ; PVALID # LATIN SMALL LETTER T WITH DOT ABOVE +1E6C ; DISALLOWED # LATIN CAPITAL LETTER T WITH DOT BELOW +1E6D ; PVALID # LATIN SMALL LETTER T WITH DOT BELOW +1E6E ; DISALLOWED # LATIN CAPITAL LETTER T WITH LINE BELOW +1E6F ; PVALID # LATIN SMALL LETTER T WITH LINE BELOW +1E70 ; DISALLOWED # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +1E71 ; PVALID # LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E72 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +1E73 ; PVALID # LATIN SMALL LETTER U WITH DIAERESIS BELOW +1E74 ; DISALLOWED # LATIN CAPITAL LETTER U WITH TILDE BELOW +1E75 ; PVALID # LATIN SMALL LETTER U WITH TILDE BELOW +1E76 ; DISALLOWED # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +1E77 ; PVALID # LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW +1E78 ; DISALLOWED # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +1E79 ; PVALID # LATIN SMALL LETTER U WITH TILDE AND ACUTE +1E7A ; DISALLOWED # LATIN CAPITAL LETTER U WITH MACRON AND DIAER +1E7B ; PVALID # LATIN SMALL LETTER U WITH MACRON AND DIAERES +1E7C ; DISALLOWED # LATIN CAPITAL LETTER V WITH TILDE +1E7D ; PVALID # LATIN SMALL LETTER V WITH TILDE +1E7E ; DISALLOWED # LATIN CAPITAL LETTER V WITH DOT BELOW +1E7F ; PVALID # LATIN SMALL LETTER V WITH DOT BELOW +1E80 ; DISALLOWED # LATIN CAPITAL LETTER W WITH GRAVE +1E81 ; PVALID # LATIN SMALL LETTER W WITH GRAVE +1E82 ; DISALLOWED # LATIN CAPITAL LETTER W WITH ACUTE +1E83 ; PVALID # LATIN SMALL LETTER W WITH ACUTE +1E84 ; DISALLOWED # LATIN CAPITAL LETTER W WITH DIAERESIS +1E85 ; PVALID # LATIN SMALL LETTER W WITH DIAERESIS +1E86 ; DISALLOWED # LATIN CAPITAL LETTER W WITH DOT ABOVE +1E87 ; PVALID # LATIN SMALL LETTER W WITH DOT ABOVE +1E88 ; DISALLOWED # LATIN CAPITAL LETTER W WITH DOT BELOW +1E89 ; PVALID # LATIN SMALL LETTER W WITH DOT BELOW +1E8A ; DISALLOWED # LATIN CAPITAL LETTER X WITH DOT ABOVE +1E8B ; PVALID # LATIN SMALL LETTER X WITH DOT ABOVE +1E8C ; DISALLOWED # LATIN CAPITAL LETTER X WITH DIAERESIS +1E8D ; PVALID # LATIN SMALL LETTER X WITH DIAERESIS +1E8E ; DISALLOWED # LATIN CAPITAL LETTER Y WITH DOT ABOVE +1E8F ; PVALID # LATIN SMALL LETTER Y WITH DOT ABOVE +1E90 ; DISALLOWED # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +1E91 ; PVALID # LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E92 ; DISALLOWED # LATIN CAPITAL LETTER Z WITH DOT BELOW +1E93 ; PVALID # LATIN SMALL LETTER Z WITH DOT BELOW +1E94 ; DISALLOWED # LATIN CAPITAL LETTER Z WITH LINE BELOW +1E95..1E99 ; PVALID # LATIN SMALL LETTER Z WITH LINE BELOW..LATIN +1E9A..1E9B ; DISALLOWED # LATIN SMALL LETTER A WITH RIGHT HALF RING..L +1E9C..1E9D ; PVALID # LATIN SMALL LETTER LONG S WITH DIAGONAL STRO +1E9E ; DISALLOWED # LATIN CAPITAL LETTER SHARP S +1E9F ; PVALID # LATIN SMALL LETTER DELTA +1EA0 ; DISALLOWED # LATIN CAPITAL LETTER A WITH DOT BELOW +1EA1 ; PVALID # LATIN SMALL LETTER A WITH DOT BELOW +1EA2 ; DISALLOWED # LATIN CAPITAL LETTER A WITH HOOK ABOVE +1EA3 ; PVALID # LATIN SMALL LETTER A WITH HOOK ABOVE +1EA4 ; DISALLOWED # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND A +1EA5 ; PVALID # LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACU +1EA6 ; DISALLOWED # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND G +1EA7 ; PVALID # LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRA +1EA8 ; DISALLOWED # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND H +1EA9 ; PVALID # LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOO +1EAA ; DISALLOWED # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND T +1EAB ; PVALID # LATIN SMALL LETTER A WITH CIRCUMFLEX AND TIL +1EAC ; DISALLOWED # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND D +1EAD ; PVALID # LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT +1EAE ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +1EAF ; PVALID # LATIN SMALL LETTER A WITH BREVE AND ACUTE +1EB0 ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +1EB1 ; PVALID # LATIN SMALL LETTER A WITH BREVE AND GRAVE +1EB2 ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE AND HOOK A +1EB3 ; PVALID # LATIN SMALL LETTER A WITH BREVE AND HOOK ABO +1EB4 ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE AND TILDE +1EB5 ; PVALID # LATIN SMALL LETTER A WITH BREVE AND TILDE +1EB6 ; DISALLOWED # LATIN CAPITAL LETTER A WITH BREVE AND DOT BE +1EB7 ; PVALID # LATIN SMALL LETTER A WITH BREVE AND DOT BELO +1EB8 ; DISALLOWED # LATIN CAPITAL LETTER E WITH DOT BELOW +1EB9 ; PVALID # LATIN SMALL LETTER E WITH DOT BELOW +1EBA ; DISALLOWED # LATIN CAPITAL LETTER E WITH HOOK ABOVE +1EBB ; PVALID # LATIN SMALL LETTER E WITH HOOK ABOVE +1EBC ; DISALLOWED # LATIN CAPITAL LETTER E WITH TILDE +1EBD ; PVALID # LATIN SMALL LETTER E WITH TILDE +1EBE ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND A +1EBF ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACU +1EC0 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND G +1EC1 ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRA +1EC2 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND H +1EC3 ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOO +1EC4 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND T +1EC5 ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX AND TIL +1EC6 ; DISALLOWED # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND D +1EC7 ; PVALID # LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT +1EC8 ; DISALLOWED # LATIN CAPITAL LETTER I WITH HOOK ABOVE +1EC9 ; PVALID # LATIN SMALL LETTER I WITH HOOK ABOVE +1ECA ; DISALLOWED # LATIN CAPITAL LETTER I WITH DOT BELOW +1ECB ; PVALID # LATIN SMALL LETTER I WITH DOT BELOW +1ECC ; DISALLOWED # LATIN CAPITAL LETTER O WITH DOT BELOW +1ECD ; PVALID # LATIN SMALL LETTER O WITH DOT BELOW +1ECE ; DISALLOWED # LATIN CAPITAL LETTER O WITH HOOK ABOVE +1ECF ; PVALID # LATIN SMALL LETTER O WITH HOOK ABOVE +1ED0 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND A +1ED1 ; PVALID # LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACU +1ED2 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND G +1ED3 ; PVALID # LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRA +1ED4 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND H +1ED5 ; PVALID # LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOO +1ED6 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND T +1ED7 ; PVALID # LATIN SMALL LETTER O WITH CIRCUMFLEX AND TIL +1ED8 ; DISALLOWED # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND D +1ED9 ; PVALID # LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT +1EDA ; DISALLOWED # LATIN CAPITAL LETTER O WITH HORN AND ACUTE +1EDB ; PVALID # LATIN SMALL LETTER O WITH HORN AND ACUTE +1EDC ; DISALLOWED # LATIN CAPITAL LETTER O WITH HORN AND GRAVE +1EDD ; PVALID # LATIN SMALL LETTER O WITH HORN AND GRAVE +1EDE ; DISALLOWED # LATIN CAPITAL LETTER O WITH HORN AND HOOK AB +1EDF ; PVALID # LATIN SMALL LETTER O WITH HORN AND HOOK ABOV +1EE0 ; DISALLOWED # LATIN CAPITAL LETTER O WITH HORN AND TILDE +1EE1 ; PVALID # LATIN SMALL LETTER O WITH HORN AND TILDE +1EE2 ; DISALLOWED # LATIN CAPITAL LETTER O WITH HORN AND DOT BEL +1EE3 ; PVALID # LATIN SMALL LETTER O WITH HORN AND DOT BELOW +1EE4 ; DISALLOWED # LATIN CAPITAL LETTER U WITH DOT BELOW +1EE5 ; PVALID # LATIN SMALL LETTER U WITH DOT BELOW +1EE6 ; DISALLOWED # LATIN CAPITAL LETTER U WITH HOOK ABOVE +1EE7 ; PVALID # LATIN SMALL LETTER U WITH HOOK ABOVE +1EE8 ; DISALLOWED # LATIN CAPITAL LETTER U WITH HORN AND ACUTE +1EE9 ; PVALID # LATIN SMALL LETTER U WITH HORN AND ACUTE +1EEA ; DISALLOWED # LATIN CAPITAL LETTER U WITH HORN AND GRAVE +1EEB ; PVALID # LATIN SMALL LETTER U WITH HORN AND GRAVE +1EEC ; DISALLOWED # LATIN CAPITAL LETTER U WITH HORN AND HOOK AB +1EED ; PVALID # LATIN SMALL LETTER U WITH HORN AND HOOK ABOV +1EEE ; DISALLOWED # LATIN CAPITAL LETTER U WITH HORN AND TILDE +1EEF ; PVALID # LATIN SMALL LETTER U WITH HORN AND TILDE +1EF0 ; DISALLOWED # LATIN CAPITAL LETTER U WITH HORN AND DOT BEL +1EF1 ; PVALID # LATIN SMALL LETTER U WITH HORN AND DOT BELOW +1EF2 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH GRAVE +1EF3 ; PVALID # LATIN SMALL LETTER Y WITH GRAVE +1EF4 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH DOT BELOW +1EF5 ; PVALID # LATIN SMALL LETTER Y WITH DOT BELOW +1EF6 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH HOOK ABOVE +1EF7 ; PVALID # LATIN SMALL LETTER Y WITH HOOK ABOVE +1EF8 ; DISALLOWED # LATIN CAPITAL LETTER Y WITH TILDE +1EF9 ; PVALID # LATIN SMALL LETTER Y WITH TILDE +1EFA ; DISALLOWED # LATIN CAPITAL LETTER MIDDLE-WELSH LL +1EFB ; PVALID # LATIN SMALL LETTER MIDDLE-WELSH LL +1EFC ; DISALLOWED # LATIN CAPITAL LETTER MIDDLE-WELSH V +1EFD ; PVALID # LATIN SMALL LETTER MIDDLE-WELSH V +1EFE ; DISALLOWED # LATIN CAPITAL LETTER Y WITH LOOP +1EFF..1F07 ; PVALID # LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL +1F000..1F02B; DISALLOWED # MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F02C..1F02F; UNASSIGNED # .. +1F030..1F093; DISALLOWED # DOMINO TILE HORIZONTAL BACK..DOMINO TILE VER +1F08..1F0F ; DISALLOWED # GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK +1F094..1F0FF; UNASSIGNED # .. +1F10..1F15 ; PVALID # GREEK SMALL LETTER EPSILON WITH PSILI..GREEK +1F100..1F10A; DISALLOWED # DIGIT ZERO FULL STOP..DIGIT NINE COMMA +1F10B..1F10F; UNASSIGNED # .. +1F110..1F12E; DISALLOWED # PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLE +1F12F..1F130; UNASSIGNED # .. +1F131 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER B +1F132..1F13C; UNASSIGNED # .. +1F13D ; DISALLOWED # SQUARED LATIN CAPITAL LETTER N +1F13E ; UNASSIGNED # +1F13F ; DISALLOWED # SQUARED LATIN CAPITAL LETTER P +1F140..1F141; UNASSIGNED # .. +1F142 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER S +1F143..1F145; UNASSIGNED # .. +1F146 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER W +1F147..1F149; UNASSIGNED # .. +1F14A..1F14E; DISALLOWED # SQUARED HV..SQUARED PPV +1F14F..1F156; UNASSIGNED # .. +1F157 ; DISALLOWED # NEGATIVE CIRCLED LATIN CAPITAL LETTER H +1F158..1F15E; UNASSIGNED # .. +1F15F ; DISALLOWED # NEGATIVE CIRCLED LATIN CAPITAL LETTER P +1F16..1F17 ; UNASSIGNED # .. +1F160..1F178; UNASSIGNED # .. +1F179 ; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER J +1F17A ; UNASSIGNED # +1F17B..1F17C; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER L..NEG +1F17D..1F17E; UNASSIGNED # .. +1F17F ; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER P +1F18..1F1D ; DISALLOWED # GREEK CAPITAL LETTER EPSILON WITH PSILI..GRE +1F180..1F189; UNASSIGNED # .. +1F18A..1F18D; DISALLOWED # CROSSED NEGATIVE SQUARED LATIN CAPITAL LETTE +1F18E..1F18F; UNASSIGNED # .. +1F190 ; DISALLOWED # SQUARE DJ +1F191..1F1FF; UNASSIGNED # .. +1F1E..1F1F ; UNASSIGNED # .. +1F20..1F27 ; PVALID # GREEK SMALL LETTER ETA WITH PSILI..GREEK SMA +1F200 ; DISALLOWED # SQUARE HIRAGANA HOKA +1F201..1F20F; UNASSIGNED # .. +1F210..1F231; DISALLOWED # SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED +1F232..1F23F; UNASSIGNED # .. +1F240..1F248; DISALLOWED # TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRA +1F249..1FFFD; UNASSIGNED # .. +1F28..1F2F ; DISALLOWED # GREEK CAPITAL LETTER ETA WITH PSILI..GREEK C +1F30..1F37 ; PVALID # GREEK SMALL LETTER IOTA WITH PSILI..GREEK SM +1F38..1F3F ; DISALLOWED # GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK +1F40..1F45 ; PVALID # GREEK SMALL LETTER OMICRON WITH PSILI..GREEK +1F46..1F47 ; UNASSIGNED # .. +1F48..1F4D ; DISALLOWED # GREEK CAPITAL LETTER OMICRON WITH PSILI..GRE +1F4E..1F4F ; UNASSIGNED # .. +1F50..1F57 ; PVALID # GREEK SMALL LETTER UPSILON WITH PSILI..GREEK +1F58 ; UNASSIGNED # +1F59 ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5A ; UNASSIGNED # +1F5B ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA AND +1F5C ; UNASSIGNED # +1F5D ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA AND +1F5E ; UNASSIGNED # +1F5F ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA AND +1F60..1F67 ; PVALID # GREEK SMALL LETTER OMEGA WITH PSILI..GREEK S +1F68..1F6F ; DISALLOWED # GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK +1F70 ; PVALID # GREEK SMALL LETTER ALPHA WITH VARIA +1F71 ; DISALLOWED # GREEK SMALL LETTER ALPHA WITH OXIA +1F72 ; PVALID # GREEK SMALL LETTER EPSILON WITH VARIA +1F73 ; DISALLOWED # GREEK SMALL LETTER EPSILON WITH OXIA +1F74 ; PVALID # GREEK SMALL LETTER ETA WITH VARIA +1F75 ; DISALLOWED # GREEK SMALL LETTER ETA WITH OXIA +1F76 ; PVALID # GREEK SMALL LETTER IOTA WITH VARIA +1F77 ; DISALLOWED # GREEK SMALL LETTER IOTA WITH OXIA +1F78 ; PVALID # GREEK SMALL LETTER OMICRON WITH VARIA +1F79 ; DISALLOWED # GREEK SMALL LETTER OMICRON WITH OXIA +1F7A ; PVALID # GREEK SMALL LETTER UPSILON WITH VARIA +1F7B ; DISALLOWED # GREEK SMALL LETTER UPSILON WITH OXIA +1F7C ; PVALID # GREEK SMALL LETTER OMEGA WITH VARIA +1F7D ; DISALLOWED # GREEK SMALL LETTER OMEGA WITH OXIA +1F7E..1F7F ; UNASSIGNED # .. +1F80..1FAF ; DISALLOWED # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOG +1FB0..1FB1 ; PVALID # GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK +1FB2..1FB4 ; DISALLOWED # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOG +1FB5 ; UNASSIGNED # +1FB6 ; PVALID # GREEK SMALL LETTER ALPHA WITH PERISPOMENI +1FB7..1FC4 ; DISALLOWED # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AN +1FC5 ; UNASSIGNED # +1FC6 ; PVALID # GREEK SMALL LETTER ETA WITH PERISPOMENI +1FC7..1FCF ; DISALLOWED # GREEK SMALL LETTER ETA WITH PERISPOMENI AND +1FD0..1FD2 ; PVALID # GREEK SMALL LETTER IOTA WITH VRACHY..GREEK S +1FD3 ; DISALLOWED # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND O +1FD4..1FD5 ; UNASSIGNED # .. +1FD6..1FD7 ; PVALID # GREEK SMALL LETTER IOTA WITH PERISPOMENI..GR +1FD8..1FDB ; DISALLOWED # GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK +1FDC ; UNASSIGNED # +1FDD..1FDF ; DISALLOWED # GREEK DASIA AND VARIA..GREEK DASIA AND PERIS +1FE0..1FE2 ; PVALID # GREEK SMALL LETTER UPSILON WITH VRACHY..GREE +1FE3 ; DISALLOWED # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AN +1FE4..1FE7 ; PVALID # GREEK SMALL LETTER RHO WITH PSILI..GREEK SMA +1FE8..1FEF ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH VRACHY..GR +1FF0..1FF1 ; UNASSIGNED # .. +1FF2..1FF4 ; DISALLOWED # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOG +1FF5 ; UNASSIGNED # +1FF6 ; PVALID # GREEK SMALL LETTER OMEGA WITH PERISPOMENI +1FF7..1FFE ; DISALLOWED # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AN +1FFF ; UNASSIGNED # +1FFFE..1FFFF; DISALLOWED # .. +2000..200B ; DISALLOWED # EN QUAD..ZERO WIDTH SPACE +20000..2A6D6; PVALID # .... +206A..2071 ; DISALLOWED # INHIBIT SYMMETRIC SWAPPING..SUPERSCRIPT LATI +2072..2073 ; UNASSIGNED # .. +2074..208E ; DISALLOWED # SUPERSCRIPT FOUR..SUBSCRIPT RIGHT PARENTHESI +208F ; UNASSIGNED # +2090..2094 ; DISALLOWED # LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCR +2095..209F ; UNASSIGNED # .. +20A0..20B8 ; DISALLOWED # EURO-CURRENCY SIGN..TENGE SIGN +20B9..20CF ; UNASSIGNED # .. +20D0..20F0 ; DISALLOWED # COMBINING LEFT HARPOON ABOVE..COMBINING ASTE +20F1..20FF ; UNASSIGNED # .. +2100..214D ; DISALLOWED # ACCOUNT OF..AKTIESELSKAB +214E ; PVALID # TURNED SMALL F +214F..2183 ; DISALLOWED # SYMBOL FOR SAMARITAN SOURCE..ROMAN NUMERAL R +2184 ; PVALID # LATIN SMALL LETTER REVERSED C +2185..2189 ; DISALLOWED # ROMAN NUMERAL SIX LATE FORM..VULGAR FRACTION +218A..218F ; UNASSIGNED # .. +2190..23E8 ; DISALLOWED # LEFTWARDS ARROW..DECIMAL EXPONENT SYMBOL +23E9..23FF ; UNASSIGNED # .. +2400..2426 ; DISALLOWED # SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM +2427..243F ; UNASSIGNED # .. +2440..244A ; DISALLOWED # OCR HOOK..OCR DOUBLE BACKSLASH +244B..245F ; UNASSIGNED # .. +2460..26CD ; DISALLOWED # CIRCLED DIGIT ONE..DISABLED CAR +26CE ; UNASSIGNED # +26CF..26E1 ; DISALLOWED # PICK..RESTRICTED LEFT ENTRY-2 +26E2 ; UNASSIGNED # +26E3 ; DISALLOWED # HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; UNASSIGNED # .. +26E8..26FF ; DISALLOWED # BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZ +2700 ; UNASSIGNED # +2701..2704 ; DISALLOWED # UPPER BLADE SCISSORS..WHITE SCISSORS +2705 ; UNASSIGNED # +2706..2709 ; DISALLOWED # TELEPHONE LOCATION SIGN..ENVELOPE +270A..270B ; UNASSIGNED # .. +270C..2727 ; DISALLOWED # VICTORY HAND..WHITE FOUR POINTED STAR +2728 ; UNASSIGNED # +2729..274B ; DISALLOWED # STRESS OUTLINED WHITE STAR..HEAVY EIGHT TEAR +274C ; UNASSIGNED # +274D ; DISALLOWED # SHADOWED WHITE CIRCLE +274E ; UNASSIGNED # +274F..2752 ; DISALLOWED # LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPE +2753..2755 ; UNASSIGNED # .. +2756..275E ; DISALLOWED # BLACK DIAMOND MINUS WHITE X..HEAVY DOUBLE CO +275F..2760 ; UNASSIGNED # .. +2761..2794 ; DISALLOWED # CURVED STEM PARAGRAPH SIGN ORNAMENT..HEAVY W +2795..2797 ; UNASSIGNED # .. +2798..27AF ; DISALLOWED # HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT- +27B0 ; UNASSIGNED # +27B1..27BE ; DISALLOWED # NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARD +27BF ; UNASSIGNED # +27C0..27CA ; DISALLOWED # THREE DIMENSIONAL ANGLE..VERTICAL BAR WITH H +27CB ; UNASSIGNED # +27CC ; DISALLOWED # LONG DIVISION +27CD..27CF ; UNASSIGNED # .. +27D0..2B4C ; DISALLOWED # WHITE DIAMOND WITH CENTRED DOT..RIGHTWARDS A +2A6D7..2A6FF; UNASSIGNED # .. +2A700..2B734; PVALID # .... +2B50..2B59 ; DISALLOWED # WHITE MEDIUM STAR..HEAVY CIRCLED SALTIRE +2B5A..2BFF ; UNASSIGNED # .. +2B735..2F7FF; UNASSIGNED # .. +2C00..2C2E ; DISALLOWED # GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CA +2C2F ; UNASSIGNED # +2C30..2C5E ; PVALID # GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMAL +2C5F ; UNASSIGNED # +2C60 ; DISALLOWED # LATIN CAPITAL LETTER L WITH DOUBLE BAR +2C61 ; PVALID # LATIN SMALL LETTER L WITH DOUBLE BAR +2C62..2C64 ; DISALLOWED # LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LA +2C65..2C66 ; PVALID # LATIN SMALL LETTER A WITH STROKE..LATIN SMAL +2C67 ; DISALLOWED # LATIN CAPITAL LETTER H WITH DESCENDER +2C68 ; PVALID # LATIN SMALL LETTER H WITH DESCENDER +2C69 ; DISALLOWED # LATIN CAPITAL LETTER K WITH DESCENDER +2C6A ; PVALID # LATIN SMALL LETTER K WITH DESCENDER +2C6B ; DISALLOWED # LATIN CAPITAL LETTER Z WITH DESCENDER +2C6C ; PVALID # LATIN SMALL LETTER Z WITH DESCENDER +2C6D..2C70 ; DISALLOWED # LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LE +2C71 ; PVALID # LATIN SMALL LETTER V WITH RIGHT HOOK +2C72 ; DISALLOWED # LATIN CAPITAL LETTER W WITH HOOK +2C73..2C74 ; PVALID # LATIN SMALL LETTER W WITH HOOK..LATIN SMALL +2C75 ; DISALLOWED # LATIN CAPITAL LETTER HALF H +2C76..2C7B ; PVALID # LATIN SMALL LETTER HALF H..LATIN LETTER SMAL +2C7C..2C80 ; DISALLOWED # LATIN SUBSCRIPT SMALL LETTER J..COPTIC CAPIT +2C81 ; PVALID # COPTIC SMALL LETTER ALFA +2C82 ; DISALLOWED # COPTIC CAPITAL LETTER VIDA +2C83 ; PVALID # COPTIC SMALL LETTER VIDA +2C84 ; DISALLOWED # COPTIC CAPITAL LETTER GAMMA +2C85 ; PVALID # COPTIC SMALL LETTER GAMMA +2C86 ; DISALLOWED # COPTIC CAPITAL LETTER DALDA +2C87 ; PVALID # COPTIC SMALL LETTER DALDA +2C88 ; DISALLOWED # COPTIC CAPITAL LETTER EIE +2C89 ; PVALID # COPTIC SMALL LETTER EIE +2C8A ; DISALLOWED # COPTIC CAPITAL LETTER SOU +2C8B ; PVALID # COPTIC SMALL LETTER SOU +2C8C ; DISALLOWED # COPTIC CAPITAL LETTER ZATA +2C8D ; PVALID # COPTIC SMALL LETTER ZATA +2C8E ; DISALLOWED # COPTIC CAPITAL LETTER HATE +2C8F ; PVALID # COPTIC SMALL LETTER HATE +2C90 ; DISALLOWED # COPTIC CAPITAL LETTER THETHE +2C91 ; PVALID # COPTIC SMALL LETTER THETHE +2C92 ; DISALLOWED # COPTIC CAPITAL LETTER IAUDA +2C93 ; PVALID # COPTIC SMALL LETTER IAUDA +2C94 ; DISALLOWED # COPTIC CAPITAL LETTER KAPA +2C95 ; PVALID # COPTIC SMALL LETTER KAPA +2C96 ; DISALLOWED # COPTIC CAPITAL LETTER LAULA +2C97 ; PVALID # COPTIC SMALL LETTER LAULA +2C98 ; DISALLOWED # COPTIC CAPITAL LETTER MI +2C99 ; PVALID # COPTIC SMALL LETTER MI +2C9A ; DISALLOWED # COPTIC CAPITAL LETTER NI +2C9B ; PVALID # COPTIC SMALL LETTER NI +2C9C ; DISALLOWED # COPTIC CAPITAL LETTER KSI +2C9D ; PVALID # COPTIC SMALL LETTER KSI +2C9E ; DISALLOWED # COPTIC CAPITAL LETTER O +2C9F ; PVALID # COPTIC SMALL LETTER O +2CA0 ; DISALLOWED # COPTIC CAPITAL LETTER PI +2CA1 ; PVALID # COPTIC SMALL LETTER PI +2CA2 ; DISALLOWED # COPTIC CAPITAL LETTER RO +2CA3 ; PVALID # COPTIC SMALL LETTER RO +2CA4 ; DISALLOWED # COPTIC CAPITAL LETTER SIMA +2CA5 ; PVALID # COPTIC SMALL LETTER SIMA +2CA6 ; DISALLOWED # COPTIC CAPITAL LETTER TAU +2CA7 ; PVALID # COPTIC SMALL LETTER TAU +2CA8 ; DISALLOWED # COPTIC CAPITAL LETTER UA +2CA9 ; PVALID # COPTIC SMALL LETTER UA +2CAA ; DISALLOWED # COPTIC CAPITAL LETTER FI +2CAB ; PVALID # COPTIC SMALL LETTER FI +2CAC ; DISALLOWED # COPTIC CAPITAL LETTER KHI +2CAD ; PVALID # COPTIC SMALL LETTER KHI +2CAE ; DISALLOWED # COPTIC CAPITAL LETTER PSI +2CAF ; PVALID # COPTIC SMALL LETTER PSI +2CB0 ; DISALLOWED # COPTIC CAPITAL LETTER OOU +2CB1 ; PVALID # COPTIC SMALL LETTER OOU +2CB2 ; DISALLOWED # COPTIC CAPITAL LETTER DIALECT-P ALEF +2CB3 ; PVALID # COPTIC SMALL LETTER DIALECT-P ALEF +2CB4 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC AIN +2CB5 ; PVALID # COPTIC SMALL LETTER OLD COPTIC AIN +2CB6 ; DISALLOWED # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE +2CB7 ; PVALID # COPTIC SMALL LETTER CRYPTOGRAMMIC EIE +2CB8 ; DISALLOWED # COPTIC CAPITAL LETTER DIALECT-P KAPA +2CB9 ; PVALID # COPTIC SMALL LETTER DIALECT-P KAPA +2CBA ; DISALLOWED # COPTIC CAPITAL LETTER DIALECT-P NI +2CBB ; PVALID # COPTIC SMALL LETTER DIALECT-P NI +2CBC ; DISALLOWED # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI +2CBD ; PVALID # COPTIC SMALL LETTER CRYPTOGRAMMIC NI +2CBE ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC OOU +2CBF ; PVALID # COPTIC SMALL LETTER OLD COPTIC OOU +2CC0 ; DISALLOWED # COPTIC CAPITAL LETTER SAMPI +2CC1 ; PVALID # COPTIC SMALL LETTER SAMPI +2CC2 ; DISALLOWED # COPTIC CAPITAL LETTER CROSSED SHEI +2CC3 ; PVALID # COPTIC SMALL LETTER CROSSED SHEI +2CC4 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC SHEI +2CC5 ; PVALID # COPTIC SMALL LETTER OLD COPTIC SHEI +2CC6 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC ESH +2CC7 ; PVALID # COPTIC SMALL LETTER OLD COPTIC ESH +2CC8 ; DISALLOWED # COPTIC CAPITAL LETTER AKHMIMIC KHEI +2CC9 ; PVALID # COPTIC SMALL LETTER AKHMIMIC KHEI +2CCA ; DISALLOWED # COPTIC CAPITAL LETTER DIALECT-P HORI +2CCB ; PVALID # COPTIC SMALL LETTER DIALECT-P HORI +2CCC ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC HORI +2CCD ; PVALID # COPTIC SMALL LETTER OLD COPTIC HORI +2CCE ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC HA +2CCF ; PVALID # COPTIC SMALL LETTER OLD COPTIC HA +2CD0 ; DISALLOWED # COPTIC CAPITAL LETTER L-SHAPED HA +2CD1 ; PVALID # COPTIC SMALL LETTER L-SHAPED HA +2CD2 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC HEI +2CD3 ; PVALID # COPTIC SMALL LETTER OLD COPTIC HEI +2CD4 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC HAT +2CD5 ; PVALID # COPTIC SMALL LETTER OLD COPTIC HAT +2CD6 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC GANGIA +2CD7 ; PVALID # COPTIC SMALL LETTER OLD COPTIC GANGIA +2CD8 ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC DJA +2CD9 ; PVALID # COPTIC SMALL LETTER OLD COPTIC DJA +2CDA ; DISALLOWED # COPTIC CAPITAL LETTER OLD COPTIC SHIMA +2CDB ; PVALID # COPTIC SMALL LETTER OLD COPTIC SHIMA +2CDC ; DISALLOWED # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA +2CDD ; PVALID # COPTIC SMALL LETTER OLD NUBIAN SHIMA +2CDE ; DISALLOWED # COPTIC CAPITAL LETTER OLD NUBIAN NGI +2CDF ; PVALID # COPTIC SMALL LETTER OLD NUBIAN NGI +2CE0 ; DISALLOWED # COPTIC CAPITAL LETTER OLD NUBIAN NYI +2CE1 ; PVALID # COPTIC SMALL LETTER OLD NUBIAN NYI +2CE2 ; DISALLOWED # COPTIC CAPITAL LETTER OLD NUBIAN WAU +2CE3..2CE4 ; PVALID # COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC S +2CE5..2CEB ; DISALLOWED # COPTIC SYMBOL MI RO..COPTIC CAPITAL LETTER C +2CEC ; PVALID # COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI +2CED ; DISALLOWED # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA +2CEE..2CF1 ; PVALID # COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA..CO +2CF2..2CF8 ; UNASSIGNED # .. +2CF9..2CFF ; DISALLOWED # COPTIC OLD NUBIAN FULL STOP..COPTIC MORPHOLO +2D00..2D25 ; PVALID # GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LET +2D26..2D2F ; UNASSIGNED # .. +2D30..2D65 ; PVALID # TIFINAGH LETTER YA..TIFINAGH LETTER YAZZ +2D66..2D6E ; UNASSIGNED # .. +2D6F ; DISALLOWED # TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D70..2D7F ; UNASSIGNED # .. +2D80..2D96 ; PVALID # ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGW +2D97..2D9F ; UNASSIGNED # .. +2DA0..2DA6 ; PVALID # ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA7 ; UNASSIGNED # +2DA8..2DAE ; PVALID # ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DAF ; UNASSIGNED # +2DB0..2DB6 ; PVALID # ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB7 ; UNASSIGNED # +2DB8..2DBE ; PVALID # ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CC +2DBF ; UNASSIGNED # +2DC0..2DC6 ; PVALID # ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC7 ; UNASSIGNED # +2DC8..2DCE ; PVALID # ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DCF ; UNASSIGNED # +2DD0..2DD6 ; PVALID # ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD7 ; UNASSIGNED # +2DD8..2DDE ; PVALID # ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DDF ; UNASSIGNED # +2DE0..2DFF ; PVALID # COMBINING CYRILLIC LETTER BE..COMBINING CYRI +2E00..2E2E ; DISALLOWED # RIGHT ANGLE SUBSTITUTION MARKER..REVERSED QU +2E2F ; PVALID # VERTICAL TILDE +2E30..2E31 ; DISALLOWED # RING POINT..WORD SEPARATOR MIDDLE DOT +2E32..2E7F ; UNASSIGNED # .. +2E80..2E99 ; DISALLOWED # CJK RADICAL REPEAT..CJK RADICAL RAP +2E9A ; UNASSIGNED # +2E9B..2EF3 ; DISALLOWED # CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED +2EF4..2EFF ; UNASSIGNED # .. +2F00..2FD5 ; DISALLOWED # KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2F800..2FA1D; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPA +2FA1E..2FFFD; UNASSIGNED # .. +2FD6..2FEF ; UNASSIGNED # .. +2FF0..2FFB ; DISALLOWED # IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RI +2FFC..2FFF ; UNASSIGNED # .. +2FFFE..2FFFF; DISALLOWED # .. +3000..3004 ; DISALLOWED # IDEOGRAPHIC SPACE..JAPANESE INDUSTRIAL STAND +30000..3FFFD; UNASSIGNED # .. +3005..3007 ; PVALID # IDEOGRAPHIC ITERATION MARK..IDEOGRAPHIC NUMB +3008..3029 ; DISALLOWED # LEFT ANGLE BRACKET..HANGZHOU NUMERAL NINE +302A..302D ; PVALID # IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENT +302E..303B ; DISALLOWED # HANGUL SINGLE DOT TONE MARK..VERTICAL IDEOGR +303C ; PVALID # MASU MARK +303D..303F ; DISALLOWED # PART ALTERNATION MARK..IDEOGRAPHIC HALF FILL +3040 ; UNASSIGNED # +3041..3096 ; PVALID # HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMA +3097..3098 ; UNASSIGNED # .. +3099..309A ; PVALID # COMBINING KATAKANA-HIRAGANA VOICED SOUND MAR +309B..309C ; DISALLOWED # KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKAN +309D..309E ; PVALID # HIRAGANA ITERATION MARK..HIRAGANA VOICED ITE +309F..30A0 ; DISALLOWED # HIRAGANA DIGRAPH YORI..KATAKANA-HIRAGANA DOU +30A1..30FA ; PVALID # KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FB ; CONTEXTO # KATAKANA MIDDLE DOT +30FC..30FE ; PVALID # KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATA +30FF ; DISALLOWED # KATAKANA DIGRAPH KOTO +3100..3104 ; UNASSIGNED # .. +3105..312D ; PVALID # BOPOMOFO LETTER B..BOPOMOFO LETTER IH +312E..3130 ; UNASSIGNED # .. +3131..318E ; DISALLOWED # HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +318F ; UNASSIGNED # +3190..319F ; DISALLOWED # IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRA +31A0..31B7 ; PVALID # BOPOMOFO LETTER BU..BOPOMOFO FINAL LETTER H +31B8..31BF ; UNASSIGNED # .. +31C0..31E3 ; DISALLOWED # CJK STROKE T..CJK STROKE Q +31E4..31EF ; UNASSIGNED # .. +31F0..31FF ; PVALID # KATAKANA LETTER SMALL KU..KATAKANA LETTER SM +3200..321E ; DISALLOWED # PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED K +321F ; UNASSIGNED # +3220..32FE ; DISALLOWED # PARENTHESIZED IDEOGRAPH ONE..CIRCLED KATAKAN +32FF ; UNASSIGNED # +3300..33FF ; DISALLOWED # SQUARE APAATO..SQUARE GAL +3400..4DB5 ; PVALID # .... +40000..4FFFD; UNASSIGNED # .. +4DB6..4DBF ; UNASSIGNED # .. +4DC0..4DFF ; DISALLOWED # HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM F +4E00..9FCB ; PVALID # .. +4FFFE..4FFFF; DISALLOWED # .. +50000..5FFFD; UNASSIGNED # .. +5FFFE..5FFFF; DISALLOWED # .. +60000..6FFFD; UNASSIGNED # .. +6FFFE..6FFFF; DISALLOWED # .. +70000..7FFFD; UNASSIGNED # .. +7FFFE..7FFFF; DISALLOWED # .. +80000..8FFFD; UNASSIGNED # .. +8FFFE..8FFFF; DISALLOWED # .. +90000..9FFFD; UNASSIGNED # .. +9FCC..9FFF ; UNASSIGNED # .. +9FFFE..9FFFF; DISALLOWED # .. +A000..A48C ; PVALID # YI SYLLABLE IT..YI SYLLABLE YYR +A0000..AFFFD; UNASSIGNED # .. +A48D..A48F ; UNASSIGNED # .. +A490..A4C6 ; DISALLOWED # YI RADICAL QOT..YI RADICAL KE +A4C7..A4CF ; UNASSIGNED # .. +A4D0..A4FD ; PVALID # LISU LETTER BA..LISU LETTER TONE MYA JEU +A4FE..A4FF ; DISALLOWED # LISU PUNCTUATION COMMA..LISU PUNCTUATION FUL +A500..A60C ; PVALID # VAI SYLLABLE EE..VAI SYLLABLE LENGTHENER +A60D..A60F ; DISALLOWED # VAI COMMA..VAI QUESTION MARK +A610..A62B ; PVALID # VAI SYLLABLE NDOLE FA..VAI SYLLABLE NDOLE DO +A62C..A63F ; UNASSIGNED # .. +A640 ; DISALLOWED # CYRILLIC CAPITAL LETTER ZEMLYA +A641 ; PVALID # CYRILLIC SMALL LETTER ZEMLYA +A642 ; DISALLOWED # CYRILLIC CAPITAL LETTER DZELO +A643 ; PVALID # CYRILLIC SMALL LETTER DZELO +A644 ; DISALLOWED # CYRILLIC CAPITAL LETTER REVERSED DZE +A645 ; PVALID # CYRILLIC SMALL LETTER REVERSED DZE +A646 ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTA +A647 ; PVALID # CYRILLIC SMALL LETTER IOTA +A648 ; DISALLOWED # CYRILLIC CAPITAL LETTER DJERV +A649 ; PVALID # CYRILLIC SMALL LETTER DJERV +A64A ; DISALLOWED # CYRILLIC CAPITAL LETTER MONOGRAPH UK +A64B ; PVALID # CYRILLIC SMALL LETTER MONOGRAPH UK +A64C ; DISALLOWED # CYRILLIC CAPITAL LETTER BROAD OMEGA +A64D ; PVALID # CYRILLIC SMALL LETTER BROAD OMEGA +A64E ; DISALLOWED # CYRILLIC CAPITAL LETTER NEUTRAL YER +A64F ; PVALID # CYRILLIC SMALL LETTER NEUTRAL YER +A650 ; DISALLOWED # CYRILLIC CAPITAL LETTER YERU WITH BACK YER +A651 ; PVALID # CYRILLIC SMALL LETTER YERU WITH BACK YER +A652 ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED YAT +A653 ; PVALID # CYRILLIC SMALL LETTER IOTIFIED YAT +A654 ; DISALLOWED # CYRILLIC CAPITAL LETTER REVERSED YU +A655 ; PVALID # CYRILLIC SMALL LETTER REVERSED YU +A656 ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED A +A657 ; PVALID # CYRILLIC SMALL LETTER IOTIFIED A +A658 ; DISALLOWED # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS +A659 ; PVALID # CYRILLIC SMALL LETTER CLOSED LITTLE YUS +A65A ; DISALLOWED # CYRILLIC CAPITAL LETTER BLENDED YUS +A65B ; PVALID # CYRILLIC SMALL LETTER BLENDED YUS +A65C ; DISALLOWED # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITT +A65D ; PVALID # CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE +A65E ; DISALLOWED # CYRILLIC CAPITAL LETTER YN +A65F ; PVALID # CYRILLIC SMALL LETTER YN +A660..A661 ; UNASSIGNED # .. +A662 ; DISALLOWED # CYRILLIC CAPITAL LETTER SOFT DE +A663 ; PVALID # CYRILLIC SMALL LETTER SOFT DE +A664 ; DISALLOWED # CYRILLIC CAPITAL LETTER SOFT EL +A665 ; PVALID # CYRILLIC SMALL LETTER SOFT EL +A666 ; DISALLOWED # CYRILLIC CAPITAL LETTER SOFT EM +A667 ; PVALID # CYRILLIC SMALL LETTER SOFT EM +A668 ; DISALLOWED # CYRILLIC CAPITAL LETTER MONOCULAR O +A669 ; PVALID # CYRILLIC SMALL LETTER MONOCULAR O +A66A ; DISALLOWED # CYRILLIC CAPITAL LETTER BINOCULAR O +A66B ; PVALID # CYRILLIC SMALL LETTER BINOCULAR O +A66C ; DISALLOWED # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O +A66D..A66F ; PVALID # CYRILLIC SMALL LETTER DOUBLE MONOCULAR O..CO +A670..A673 ; DISALLOWED # COMBINING CYRILLIC TEN MILLIONS SIGN..SLAVON +A674..A67B ; UNASSIGNED # .. +A67C..A67D ; PVALID # COMBINING CYRILLIC KAVYKA..COMBINING CYRILLI +A67E ; DISALLOWED # CYRILLIC KAVYKA +A67F ; PVALID # CYRILLIC PAYEROK +A680 ; DISALLOWED # CYRILLIC CAPITAL LETTER DWE +A681 ; PVALID # CYRILLIC SMALL LETTER DWE +A682 ; DISALLOWED # CYRILLIC CAPITAL LETTER DZWE +A683 ; PVALID # CYRILLIC SMALL LETTER DZWE +A684 ; DISALLOWED # CYRILLIC CAPITAL LETTER ZHWE +A685 ; PVALID # CYRILLIC SMALL LETTER ZHWE +A686 ; DISALLOWED # CYRILLIC CAPITAL LETTER CCHE +A687 ; PVALID # CYRILLIC SMALL LETTER CCHE +A688 ; DISALLOWED # CYRILLIC CAPITAL LETTER DZZE +A689 ; PVALID # CYRILLIC SMALL LETTER DZZE +A68A ; DISALLOWED # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK +A68B ; PVALID # CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK +A68C ; DISALLOWED # CYRILLIC CAPITAL LETTER TWE +A68D ; PVALID # CYRILLIC SMALL LETTER TWE +A68E ; DISALLOWED # CYRILLIC CAPITAL LETTER TSWE +A68F ; PVALID # CYRILLIC SMALL LETTER TSWE +A690 ; DISALLOWED # CYRILLIC CAPITAL LETTER TSSE +A691 ; PVALID # CYRILLIC SMALL LETTER TSSE +A692 ; DISALLOWED # CYRILLIC CAPITAL LETTER TCHE +A693 ; PVALID # CYRILLIC SMALL LETTER TCHE +A694 ; DISALLOWED # CYRILLIC CAPITAL LETTER HWE +A695 ; PVALID # CYRILLIC SMALL LETTER HWE +A696 ; DISALLOWED # CYRILLIC CAPITAL LETTER SHWE +A697 ; PVALID # CYRILLIC SMALL LETTER SHWE +A698..A69F ; UNASSIGNED # .. +A6A0..A6E5 ; PVALID # BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; DISALLOWED # BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0..A6F1 ; PVALID # BAMUM COMBINING MARK KOQNDON..BAMUM COMBININ +A6F2..A6F7 ; DISALLOWED # BAMUM NJAEMLI..BAMUM QUESTION MARK +A6F8..A6FF ; UNASSIGNED # .. +A700..A716 ; DISALLOWED # MODIFIER LETTER CHINESE TONE YIN PING..MODIF +A717..A71F ; PVALID # MODIFIER LETTER DOT VERTICAL BAR..MODIFIER L +A720..A722 ; DISALLOWED # MODIFIER LETTER STRESS AND HIGH TONE..LATIN +A723 ; PVALID # LATIN SMALL LETTER EGYPTOLOGICAL ALEF +A724 ; DISALLOWED # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN +A725 ; PVALID # LATIN SMALL LETTER EGYPTOLOGICAL AIN +A726 ; DISALLOWED # LATIN CAPITAL LETTER HENG +A727 ; PVALID # LATIN SMALL LETTER HENG +A728 ; DISALLOWED # LATIN CAPITAL LETTER TZ +A729 ; PVALID # LATIN SMALL LETTER TZ +A72A ; DISALLOWED # LATIN CAPITAL LETTER TRESILLO +A72B ; PVALID # LATIN SMALL LETTER TRESILLO +A72C ; DISALLOWED # LATIN CAPITAL LETTER CUATRILLO +A72D ; PVALID # LATIN SMALL LETTER CUATRILLO +A72E ; DISALLOWED # LATIN CAPITAL LETTER CUATRILLO WITH COMMA +A72F..A731 ; PVALID # LATIN SMALL LETTER CUATRILLO WITH COMMA..LAT +A732 ; DISALLOWED # LATIN CAPITAL LETTER AA +A733 ; PVALID # LATIN SMALL LETTER AA +A734 ; DISALLOWED # LATIN CAPITAL LETTER AO +A735 ; PVALID # LATIN SMALL LETTER AO +A736 ; DISALLOWED # LATIN CAPITAL LETTER AU +A737 ; PVALID # LATIN SMALL LETTER AU +A738 ; DISALLOWED # LATIN CAPITAL LETTER AV +A739 ; PVALID # LATIN SMALL LETTER AV +A73A ; DISALLOWED # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR +A73B ; PVALID # LATIN SMALL LETTER AV WITH HORIZONTAL BAR +A73C ; DISALLOWED # LATIN CAPITAL LETTER AY +A73D ; PVALID # LATIN SMALL LETTER AY +A73E ; DISALLOWED # LATIN CAPITAL LETTER REVERSED C WITH DOT +A73F ; PVALID # LATIN SMALL LETTER REVERSED C WITH DOT +A740 ; DISALLOWED # LATIN CAPITAL LETTER K WITH STROKE +A741 ; PVALID # LATIN SMALL LETTER K WITH STROKE +A742 ; DISALLOWED # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE +A743 ; PVALID # LATIN SMALL LETTER K WITH DIAGONAL STROKE +A744 ; DISALLOWED # LATIN CAPITAL LETTER K WITH STROKE AND DIAGO +A745 ; PVALID # LATIN SMALL LETTER K WITH STROKE AND DIAGONA +A746 ; DISALLOWED # LATIN CAPITAL LETTER BROKEN L +A747 ; PVALID # LATIN SMALL LETTER BROKEN L +A748 ; DISALLOWED # LATIN CAPITAL LETTER L WITH HIGH STROKE +A749 ; PVALID # LATIN SMALL LETTER L WITH HIGH STROKE +A74A ; DISALLOWED # LATIN CAPITAL LETTER O WITH LONG STROKE OVER +A74B ; PVALID # LATIN SMALL LETTER O WITH LONG STROKE OVERLA +A74C ; DISALLOWED # LATIN CAPITAL LETTER O WITH LOOP +A74D ; PVALID # LATIN SMALL LETTER O WITH LOOP +A74E ; DISALLOWED # LATIN CAPITAL LETTER OO +A74F ; PVALID # LATIN SMALL LETTER OO +A750 ; DISALLOWED # LATIN CAPITAL LETTER P WITH STROKE THROUGH D +A751 ; PVALID # LATIN SMALL LETTER P WITH STROKE THROUGH DES +A752 ; DISALLOWED # LATIN CAPITAL LETTER P WITH FLOURISH +A753 ; PVALID # LATIN SMALL LETTER P WITH FLOURISH +A754 ; DISALLOWED # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL +A755 ; PVALID # LATIN SMALL LETTER P WITH SQUIRREL TAIL +A756 ; DISALLOWED # LATIN CAPITAL LETTER Q WITH STROKE THROUGH D +A757 ; PVALID # LATIN SMALL LETTER Q WITH STROKE THROUGH DES +A758 ; DISALLOWED # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE +A759 ; PVALID # LATIN SMALL LETTER Q WITH DIAGONAL STROKE +A75A ; DISALLOWED # LATIN CAPITAL LETTER R ROTUNDA +A75B ; PVALID # LATIN SMALL LETTER R ROTUNDA +A75C ; DISALLOWED # LATIN CAPITAL LETTER RUM ROTUNDA +A75D ; PVALID # LATIN SMALL LETTER RUM ROTUNDA +A75E ; DISALLOWED # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE +A75F ; PVALID # LATIN SMALL LETTER V WITH DIAGONAL STROKE +A760 ; DISALLOWED # LATIN CAPITAL LETTER VY +A761 ; PVALID # LATIN SMALL LETTER VY +A762 ; DISALLOWED # LATIN CAPITAL LETTER VISIGOTHIC Z +A763 ; PVALID # LATIN SMALL LETTER VISIGOTHIC Z +A764 ; DISALLOWED # LATIN CAPITAL LETTER THORN WITH STROKE +A765 ; PVALID # LATIN SMALL LETTER THORN WITH STROKE +A766 ; DISALLOWED # LATIN CAPITAL LETTER THORN WITH STROKE THROU +A767 ; PVALID # LATIN SMALL LETTER THORN WITH STROKE THROUGH +A768 ; DISALLOWED # LATIN CAPITAL LETTER VEND +A769 ; PVALID # LATIN SMALL LETTER VEND +A76A ; DISALLOWED # LATIN CAPITAL LETTER ET +A76B ; PVALID # LATIN SMALL LETTER ET +A76C ; DISALLOWED # LATIN CAPITAL LETTER IS +A76D ; PVALID # LATIN SMALL LETTER IS +A76E ; DISALLOWED # LATIN CAPITAL LETTER CON +A76F ; PVALID # LATIN SMALL LETTER CON +A770 ; DISALLOWED # MODIFIER LETTER US +A771..A778 ; PVALID # LATIN SMALL LETTER DUM..LATIN SMALL LETTER U +A779 ; DISALLOWED # LATIN CAPITAL LETTER INSULAR D +A77A ; PVALID # LATIN SMALL LETTER INSULAR D +A77B ; DISALLOWED # LATIN CAPITAL LETTER INSULAR F +A77C ; PVALID # LATIN SMALL LETTER INSULAR F +A77D..A77E ; DISALLOWED # LATIN CAPITAL LETTER INSULAR G..LATIN CAPITA +A77F ; PVALID # LATIN SMALL LETTER TURNED INSULAR G +A780 ; DISALLOWED # LATIN CAPITAL LETTER TURNED L +A781 ; PVALID # LATIN SMALL LETTER TURNED L +A782 ; DISALLOWED # LATIN CAPITAL LETTER INSULAR R +A783 ; PVALID # LATIN SMALL LETTER INSULAR R +A784 ; DISALLOWED # LATIN CAPITAL LETTER INSULAR S +A785 ; PVALID # LATIN SMALL LETTER INSULAR S +A786 ; DISALLOWED # LATIN CAPITAL LETTER INSULAR T +A787..A788 ; PVALID # LATIN SMALL LETTER INSULAR T..MODIFIER LETTE +A789..A78B ; DISALLOWED # MODIFIER LETTER COLON..LATIN CAPITAL LETTER +A78C ; PVALID # LATIN SMALL LETTER SALTILLO +A78D..A7FA ; UNASSIGNED # .. +A7FB..A827 ; PVALID # LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI N +A828..A82B ; DISALLOWED # SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POE +A82C..A82F ; UNASSIGNED # .. +A830..A839 ; DISALLOWED # NORTH INDIC FRACTION ONE QUARTER..NORTH INDI +A83A..A83F ; UNASSIGNED # .. +A840..A873 ; PVALID # PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABI +A874..A877 ; DISALLOWED # PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOU +A878..A87F ; UNASSIGNED # .. +A880..A8C4 ; PVALID # SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VI +A8C5..A8CD ; UNASSIGNED # .. +A8CE..A8CF ; DISALLOWED # SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0..A8D9 ; PVALID # SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8DA..A8DF ; UNASSIGNED # .. +A8E0..A8F7 ; PVALID # COMBINING DEVANAGARI DIGIT ZERO..DEVANAGARI +A8F8..A8FA ; DISALLOWED # DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB ; PVALID # DEVANAGARI HEADSTROKE +A8FC..A8FF ; UNASSIGNED # .. +A900..A92D ; PVALID # KAYAH LI DIGIT ZERO..KAYAH LI TONE CALYA PLO +A92E..A92F ; DISALLOWED # KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930..A953 ; PVALID # REJANG LETTER KA..REJANG VIRAMA +A954..A95E ; UNASSIGNED # .. +A95F..A97C ; DISALLOWED # REJANG SECTION MARK..HANGUL CHOSEONG SSANGYE +A97D..A97F ; UNASSIGNED # .. +A980..A9C0 ; PVALID # JAVANESE SIGN PANYANGGA..JAVANESE PANGKON +A9C1..A9CD ; DISALLOWED # JAVANESE LEFT RERENGGAN..JAVANESE TURNED PAD +A9CE ; UNASSIGNED # +A9CF..A9D9 ; PVALID # JAVANESE PANGRANGKEP..JAVANESE DIGIT NINE +A9DA..A9DD ; UNASSIGNED # .. +A9DE..A9DF ; DISALLOWED # JAVANESE PADA TIRTA TUMETES..JAVANESE PADA I +A9E0..A9FF ; UNASSIGNED # .. +AA00..AA36 ; PVALID # CHAM LETTER A..CHAM CONSONANT SIGN WA +AA37..AA3F ; UNASSIGNED # .. +AA40..AA4D ; PVALID # CHAM LETTER FINAL K..CHAM CONSONANT SIGN FIN +AA4E..AA4F ; UNASSIGNED # .. +AA50..AA59 ; PVALID # CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5A..AA5B ; UNASSIGNED # .. +AA5C..AA5F ; DISALLOWED # CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TR +AA60..AA76 ; PVALID # MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM K +AA77..AA79 ; DISALLOWED # MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SY +AA7A..AA7B ; PVALID # MYANMAR LETTER AITON RA..MYANMAR SIGN PAO KA +AA7C..AA7F ; UNASSIGNED # .. +AA80..AAC2 ; PVALID # TAI VIET LETTER LOW KO..TAI VIET TONE MAI SO +AAC3..AADA ; UNASSIGNED # .. +AADB..AADD ; PVALID # TAI VIET SYMBOL KON..TAI VIET SYMBOL SAM +AADE..AADF ; DISALLOWED # TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI +AAE0..ABBF ; UNASSIGNED # .. +ABC0..ABEA ; PVALID # MEETEI MAYEK LETTER KOK..MEETEI MAYEK VOWEL +ABEB ; DISALLOWED # MEETEI MAYEK CHEIKHEI +ABEC..ABED ; PVALID # MEETEI MAYEK LUM IYEK..MEETEI MAYEK APUN IYE +ABEE..ABEF ; UNASSIGNED # .. +ABF0..ABF9 ; PVALID # MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT +ABFA..ABFF ; UNASSIGNED # .. +AC00..D7A3 ; PVALID # .. +AFFFE..AFFFF; DISALLOWED # .. +B0000..BFFFD; UNASSIGNED # .. +BFFFE..BFFFF; DISALLOWED # .. +C0000..CFFFD; UNASSIGNED # .. +CFFFE..CFFFF; DISALLOWED # .. +D0000..DFFFD; UNASSIGNED # .. +D7A4..D7AF ; UNASSIGNED # .. +D7B0..D7C6 ; DISALLOWED # HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARA +D7C7..D7CA ; UNASSIGNED # .. +D7CB..D7FB ; DISALLOWED # HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEO +D7FC..D7FF ; UNASSIGNED # .. +D800..FA0D ; DISALLOWED # ..CJK COMPAT +DFFFE..DFFFF; DISALLOWED # .. +E0000 ; UNASSIGNED # +E0001 ; DISALLOWED # LANGUAGE TAG +E0002..E001F; UNASSIGNED # .. +E0020..E007F; DISALLOWED # TAG SPACE..CANCEL TAG +E0080..E00FF; UNASSIGNED # .. +E0100..E01EF; DISALLOWED # VARIATION SELECTOR-17..VARIATION SELECTOR-25 +E01F0..EFFFD; UNASSIGNED # .. +EFFFE..10FFFF; DISALLOWED # .. +FA0E..FA0F ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPAT +FA10 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA10 +FA11 ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA11 +FA12 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA12 +FA13..FA14 ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPAT +FA15..FA1E ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA15..CJK COMPAT +FA1F ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA1F +FA20 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA20 +FA21 ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA21 +FA22 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA22 +FA23..FA24 ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPAT +FA25..FA26 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA25..CJK COMPAT +FA27..FA29 ; PVALID # CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPAT +FA2A..FA2D ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA2A..CJK COMPAT +FA2E..FA2F ; UNASSIGNED # .. +FA30..FA6D ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA30..CJK COMPAT +FA6E..FA6F ; UNASSIGNED # .. +FA70..FAD9 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPAT +FADA..FAFF ; UNASSIGNED # .. +FB00..FB06 ; DISALLOWED # LATIN SMALL LIGATURE FF..LATIN SMALL LIGATUR +FB07..FB12 ; UNASSIGNED # .. +FB13..FB17 ; DISALLOWED # ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SM +FB18..FB1C ; UNASSIGNED # .. +FB1D ; DISALLOWED # HEBREW LETTER YOD WITH HIRIQ +FB1E ; PVALID # HEBREW POINT JUDEO-SPANISH VARIKA +FB1F..FB36 ; DISALLOWED # HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBRE +FB37 ; UNASSIGNED # +FB38..FB3C ; DISALLOWED # HEBREW LETTER TET WITH DAGESH..HEBREW LETTER +FB3D ; UNASSIGNED # +FB3E ; DISALLOWED # HEBREW LETTER MEM WITH DAGESH +FB3F ; UNASSIGNED # +FB40..FB41 ; DISALLOWED # HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER +FB42 ; UNASSIGNED # +FB43..FB44 ; DISALLOWED # HEBREW LETTER FINAL PE WITH DAGESH..HEBREW L +FB45 ; UNASSIGNED # +FB46..FBB1 ; DISALLOWED # HEBREW LETTER TSADI WITH DAGESH..ARABIC LETT +FBB2..FBD2 ; UNASSIGNED # .. +FBD3..FD3F ; DISALLOWED # ARABIC LETTER NG ISOLATED FORM..ORNATE RIGHT +FD40..FD4F ; UNASSIGNED # .. +FD50..FD8F ; DISALLOWED # ARABIC LIGATURE TEH WITH JEEM WITH MEEM INIT +FD90..FD91 ; UNASSIGNED # .. +FD92..FDC7 ; DISALLOWED # ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INI +FDC8..FDCF ; UNASSIGNED # .. +FDD0..FDFD ; DISALLOWED # ..ARABIC LIGATURE BISMILLAH AR +FDFE..FDFF ; UNASSIGNED # .. +FE00..FE19 ; DISALLOWED # VARIATION SELECTOR-1..PRESENTATION FORM FOR +FE1A..FE1F ; UNASSIGNED # .. +FE20..FE26 ; PVALID # COMBINING LIGATURE LEFT HALF..COMBINING CONJ +FE27..FE2F ; UNASSIGNED # .. +FE30..FE52 ; DISALLOWED # PRESENTATION FORM FOR VERTICAL TWO DOT LEADE +FE53 ; UNASSIGNED # +FE54..FE66 ; DISALLOWED # SMALL SEMICOLON..SMALL EQUALS SIGN +FE67 ; UNASSIGNED # +FE68..FE6B ; DISALLOWED # SMALL REVERSE SOLIDUS..SMALL COMMERCIAL AT +FE6C..FE6F ; UNASSIGNED # .. +FE70..FE72 ; DISALLOWED # ARABIC FATHATAN ISOLATED FORM..ARABIC DAMMAT +FE73 ; PVALID # ARABIC TAIL FRAGMENT +FE74 ; DISALLOWED # ARABIC KASRATAN ISOLATED FORM +FE75 ; UNASSIGNED # +FE76..FEFC ; DISALLOWED # ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE +FEFD..FEFE ; UNASSIGNED # .. +FEFF ; DISALLOWED # ZERO WIDTH NO-BREAK SPACE +FF00 ; UNASSIGNED # +FF01..FFBE ; DISALLOWED # FULLWIDTH EXCLAMATION MARK..HALFWIDTH HANGUL +FFBF..FFC1 ; UNASSIGNED # .. +FFC2..FFC7 ; DISALLOWED # HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL +FFC8..FFC9 ; UNASSIGNED # .. +FFCA..FFCF ; DISALLOWED # HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGU +FFD0..FFD1 ; UNASSIGNED # .. +FFD2..FFD7 ; DISALLOWED # HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL +FFD8..FFD9 ; UNASSIGNED # .. +FFDA..FFDC ; DISALLOWED # HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL +FFDD..FFDF ; UNASSIGNED # .. +FFE0..FFE6 ; DISALLOWED # FULLWIDTH CENT SIGN..FULLWIDTH WON SIGN +FFE7 ; UNASSIGNED # +FFE8..FFEE ; DISALLOWED # HALFWIDTH FORMS LIGHT VERTICAL..HALFWIDTH WH +FFEF..FFF8 ; UNASSIGNED # .. +FFF9..FFFF ; DISALLOWED # INTERLINEAR ANNOTATION ANCHOR.. + benchmark + + jar + + false + + + / + true + true + test + + + + + ${project.build.directory}/test-classes + / + + **/*.class + **/*.json + **/*.properties + **/*.xml + **/META-INF/** + + true + + + \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/AbsoluteIriTest.java b/src/test/java/com/networknt/schema/AbsoluteIriTest.java new file mode 100644 index 000000000..c07860d82 --- /dev/null +++ b/src/test/java/com/networknt/schema/AbsoluteIriTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AbsoluteIriTest { + + @Test + void absolute() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("classpath:resource", iri.resolve("classpath:resource").toString()); + } + + @Test + void resolveNull() { + AbsoluteIri iri = new AbsoluteIri(null); + assertEquals("test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeColonDotPathSegment() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo/foo:bar", iri.resolve("./foo:bar").toString()); + } + + @Test + void relativeColonSecondSegment() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo/bar/foo:bar", iri.resolve("bar/foo:bar").toString()); + } + + @Test + void relativeColonQueryString() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo/test.json?queryParam=foo:bar", iri.resolve("test.json?queryParam=foo:bar").toString()); + } + + @Test + void relativeColonAnchor() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo/test.json#foo:bar", iri.resolve("test.json#foo:bar").toString()); + } + + @Test + void relativeAtDocument() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtDirectory() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/"); + assertEquals("http://www.example.org/foo/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRoot() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org"); + assertEquals("http://www.example.org/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithTrailingSlash() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/"); + assertEquals("http://www.example.org/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithSchemeSpecificPart() { + AbsoluteIri iri = new AbsoluteIri("classpath:resource"); + assertEquals("classpath:test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithSchemeSpecificPartNoPath() { + AbsoluteIri iri = new AbsoluteIri("classpath:"); + assertEquals("classpath:test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithSchemeSpecificPartSlash() { + AbsoluteIri iri = new AbsoluteIri("classpath:/resource"); + assertEquals("classpath:/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithSchemeSpecificPartNoPathTrailingSlash() { + AbsoluteIri iri = new AbsoluteIri("classpath:/"); + assertEquals("classpath:/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeAtRootWithSchemeSpecificPartTrailingSlash() { + AbsoluteIri iri = new AbsoluteIri("classpath:resource/"); + assertEquals("classpath:resource/test.json", iri.resolve("test.json").toString()); + } + + @Test + void relativeParentWithSchemeSpecificPart() { + AbsoluteIri iri = new AbsoluteIri("classpath:resource/hello/world/testing.json"); + assertEquals("classpath:resource/test.json", iri.resolve("../../test.json").toString()); + } + + @Test + void rootColonDotPathSegment() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/foo:bar", iri.resolve("/foo:bar").toString()); + } + + @Test + void rootColonSecondSegment() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/bar/foo:bar", iri.resolve("/bar/foo:bar").toString()); + } + + @Test + void rootAbsoluteAtDocument() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json"); + assertEquals("http://www.example.org/test.json", iri.resolve("/test.json").toString()); + } + + @Test + void rootAbsoluteAtDirectory() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/"); + assertEquals("http://www.example.org/test.json", iri.resolve("/test.json").toString()); + } + + @Test + void rootAbsoluteAtRoot() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org"); + assertEquals("http://www.example.org/test.json", iri.resolve("/test.json").toString()); + } + + @Test + void rootAbsoluteAtRootWithTrailingSlash() { + AbsoluteIri iri = new AbsoluteIri("http://www.example.org/"); + assertEquals("http://www.example.org/test.json", iri.resolve("/test.json").toString()); + } + + @Test + void rootAbsoluteAtRootSchemeSpecificPart() { + AbsoluteIri iri = new AbsoluteIri("classpath:resource"); + assertEquals("classpath:resource/test.json", iri.resolve("/test.json").toString()); + } + + @Test + void schemeClasspath() { + assertEquals("classpath", AbsoluteIri.of("classpath:resource/test.json").getScheme()); + } + + @Test + void schemeHttps() { + assertEquals("https", AbsoluteIri.of("https://www.example.org").getScheme()); + } + + @Test + void schemeNone() { + assertEquals("", AbsoluteIri.of("relative").getScheme()); + } + + @Test + void schemeUrn() { + assertEquals("urn", AbsoluteIri.of("urn:isbn:1234567890").getScheme()); + } + +} diff --git a/src/test/java/com/networknt/schema/AbstractJsonSchemaTest.java b/src/test/java/com/networknt/schema/AbstractJsonSchemaTest.java new file mode 100644 index 000000000..2c1a92e96 --- /dev/null +++ b/src/test/java/com/networknt/schema/AbstractJsonSchemaTest.java @@ -0,0 +1,67 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.serialization.JsonMapperFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Abstract class to use if the data JSON has a declared schema node at root level + * + * @see Issue769ContainsTest + * @author vwuilbea + */ +abstract class AbstractJsonSchemaTest { + + private static final String SCHEMA = "$schema"; + private static final SpecificationVersion DEFAULT_VERSION_FLAG = SpecificationVersion.DRAFT_2020_12; + private static final String ASSERT_MSG_KEYWORD = "Validation result should contain {0} keyword"; + + protected List validate(String dataPath) { + JsonNode dataNode = getJsonNodeFromPath(dataPath); + return getJsonSchemaFromDataNode(dataNode).validate(dataNode); + } + + protected void assertValidatorType(String filename, KeywordType validatorTypeCode) { + List errors = validate(getDataTestFolder() + filename); + + assertTrue( + errors.stream().anyMatch(vm -> validatorTypeCode.getValue().equals(vm.getKeyword())), + () -> MessageFormat.format(ASSERT_MSG_KEYWORD, validatorTypeCode.getValue())); + } + + protected abstract String getDataTestFolder(); + + private Schema getJsonSchemaFromDataNode(JsonNode dataNode) { + return Optional.ofNullable(dataNode.get(SCHEMA)) + .map(JsonNode::textValue) + .map(this::getJsonNodeFromPath) + .map(this::getJsonSchema) + .orElseThrow(() -> new IllegalArgumentException("No schema found on document to test")); + } + + private JsonNode getJsonNodeFromPath(String dataPath) { + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + ObjectMapper mapper = JsonMapperFactory.getInstance(); + try { + return mapper.readTree(dataInputStream); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + private Schema getJsonSchema(JsonNode schemaNode) { + return SchemaRegistry + .withDefaultDialect(SpecificationVersionDetector.detectOptionalVersion(schemaNode, false).orElse(DEFAULT_VERSION_FLAG)) + .getSchema(schemaNode); + } + +} diff --git a/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java b/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java new file mode 100644 index 000000000..9bb76ed27 --- /dev/null +++ b/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.networknt.schema.regex.JDKRegularExpressionFactory; +import com.networknt.schema.regex.JoniRegularExpressionFactory; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.ResourceLoader; +import com.networknt.schema.suite.TestCase; +import com.networknt.schema.suite.TestSource; +import com.networknt.schema.suite.TestSpec; + +import org.junit.jupiter.api.AssertionFailureBuilder; +import org.junit.jupiter.api.DynamicNode; +import org.opentest4j.AssertionFailedError; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.networknt.schema.SpecificationVersionDetector.detectVersion; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +abstract class AbstractJsonSchemaTestSuite { + + private static String toForwardSlashPath(Path file) { + return file.toString().replace('\\', '/'); + } + + private static void executeTest(Schema schema, TestSpec testSpec) { + List errors = schema.validate(testSpec.getData(), OutputFormat.DEFAULT, (executionContext, schemaContext) -> { + executionContext.executionConfig(executionConfig -> { + if (testSpec.getConfig() != null) { + if (testSpec.getConfig().containsKey("readOnly")) { + executionConfig.readOnly((Boolean) testSpec.getConfig().get("readOnly")); + } + if (testSpec.getConfig().containsKey("writeOnly")) { + executionConfig.writeOnly((Boolean) testSpec.getConfig().get("writeOnly")); + } + } + if (testSpec.getTestCase().getSource().getPath().getParent().toString().endsWith("format")) { + executionConfig.formatAssertionsEnabled(true); + } + }); + }); + + if (testSpec.isValid()) { + if (!errors.isEmpty()) { + String msg = new StringBuilder("Expected success") + .append("\n description: ") + .append(testSpec.getDescription()) + .append("\n schema: ") + .append(schema) + .append("\n data: ") + .append(testSpec.getData()) + .toString(); + + AssertionFailedError t = AssertionFailureBuilder.assertionFailure() + .message(msg) + .reason(errors.stream().map(Error::getMessage).collect(Collectors.joining("\n ", "\n errors:\n ", ""))) + .build(); + t.setStackTrace(new StackTraceElement[0]); + throw t; + } + } else { + if (errors.isEmpty()) { + String msg = new StringBuilder("Expected failure") + .append("\n description: ") + .append(testSpec.getDescription()) + .append("\n schema: ") + .append(schema) + .append("\n data: ") + .append(testSpec.getData()) + .toString(); + + AssertionFailedError t = AssertionFailureBuilder.assertionFailure() + .message(msg) + .build(); + t.setStackTrace(new StackTraceElement[0]); + throw t; + } + } + + // Expected Validation Messages need not be exactly same as actual errors. + // This code checks if expected validation message is subset of actual errors + Set actual = errors.stream() + .map(error -> error.getInstanceLocation().toString() + ": " + error.getMessage()) + .collect(Collectors.toSet()); + Set expected = testSpec.getErrors(); + expected.removeAll(actual); + if (!expected.isEmpty()) { + String msg = new StringBuilder("Expected Validation Messages") + .append("\n description: ") + .append(testSpec.getDescription()) + .append("\n schema: ") + .append(schema) + .append("\n data: ") + .append(testSpec.getData()) + .append(actual.stream().collect(Collectors.joining("\n ", "\n errors:\n ", ""))) + .toString(); + + AssertionFailedError t = AssertionFailureBuilder.assertionFailure() + .message(msg) + .reason(expected.stream().collect(Collectors.joining("\n ", "\n expected:\n ", ""))) + .build(); + t.setStackTrace(new StackTraceElement[0]); + throw t; + } + } + + private static Iterable unsupportedMetaSchema(TestCase testCase) { + return Collections.singleton( + dynamicTest("Detected an unsupported schema", () -> { + String schema = testCase.getSchema().asText(); + AssertionFailedError t = AssertionFailureBuilder.assertionFailure() + .message("Detected an unsupported schema: " + schema) + .reason("Future and custom meta-schemas are not supported") + .build(); + t.setStackTrace(new StackTraceElement[0]); + throw t; + }) + ); + } + + protected Stream createTests(SpecificationVersion defaultVersion, String basePath) { + return findTestCases(basePath) + .stream() + .peek(System.out::println) + .flatMap(path -> buildContainers(defaultVersion, path)); + } + + protected boolean enabled(@SuppressWarnings("unused") Path path) { + return true; + } + + protected Optional reason(@SuppressWarnings("unused") Path path) { + return Optional.empty(); + } + + private Stream buildContainers(SpecificationVersion defaultVersion, Path path) { + boolean disabled = !enabled(path); + String reason = reason(path).orElse("Unknown"); + return TestSource.loadFrom(path, disabled, reason) + .map(testSource -> buildContainer(defaultVersion, testSource)) + .orElse(Stream.empty()); + } + + private Stream buildContainer(SpecificationVersion defaultVersion, TestSource testSource) { + return testSource.getTestCases().stream().map(testCase -> buildContainer(defaultVersion, testCase)); + } + + private DynamicNode buildContainer(SpecificationVersion defaultVersion, TestCase testCase) { + try { + return dynamicContainer(testCase.getDisplayName(), testCase.getTests().stream().map(testSpec -> { + // Configure the schemaValidator to set typeLoose's value based on the test file, + // if test file do not contains typeLoose flag, use default value: false. + @SuppressWarnings("deprecation") boolean typeLoose = testSpec.isTypeLoose(); + + SchemaRegistryConfig.Builder configBuilder = SchemaRegistryConfig.builder(); + configBuilder.strict("type", false); + configBuilder.typeLoose(typeLoose); + configBuilder.regularExpressionFactory( + TestSpec.RegexKind.JDK == testSpec.getRegex() ? JDKRegularExpressionFactory.getInstance() + : JoniRegularExpressionFactory.getInstance()); + testSpec.getStrictness().forEach(configBuilder::strict); + + if (testSpec.getConfig() != null) { + if (testSpec.getConfig().containsKey("isCustomMessageSupported")) { + configBuilder.errorMessageKeyword( + (Boolean) testSpec.getConfig().get("isCustomMessageSupported") ? "message" : null); + } + } + SchemaRegistry schemaRegistry = buildSchemaRegistry(defaultVersion, testCase, configBuilder.build()); + return buildTest(schemaRegistry, testSpec); + })); + } catch (SchemaException e) { + String msg = e.getMessage(); + if (msg.endsWith("' is unrecognizable schema")) { + return dynamicContainer(testCase.getDisplayName(), unsupportedMetaSchema(testCase)); + } + throw e; + } + } + + private SchemaRegistry buildSchemaRegistry(SpecificationVersion defaultVersion, TestCase testCase, SchemaRegistryConfig schemaRegistryConfig) { + if (testCase.isDisabled()) return null; + ResourceLoader schemaLoader = new ResourceLoader() { + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + String iri = absoluteIri.toString(); + if (iri.startsWith("http://localhost:1234")) { + return () -> { + String path = iri.substring("http://localhost:1234".length()); + File file = new File("src/test/resources/remotes" + path); + if (file.exists()) { + return new FileInputStream("src/test/resources/remotes" + path); + } + return new FileInputStream("src/test/suite/remotes" + path); + }; + } + return null; + } + }; + SpecificationVersion specVersion = detectVersion(testCase.getSchema(), testCase.getSpecification(), defaultVersion, false); + SchemaRegistry base = SchemaRegistry.withDefaultDialect(specVersion); + return SchemaRegistry + .builder(base) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers + .mapPrefix("https://", "http://") + .mapPrefix("http://json-schema.org", "resource:")) + .resourceLoaders(resourceLoaders -> resourceLoaders.add(schemaLoader)) + .schemaRegistryConfig(schemaRegistryConfig) + .build(); + } + + private DynamicNode buildTest(SchemaRegistry validatorFactory, TestSpec testSpec) { + if (testSpec.isDisabled()) { + return dynamicTest(testSpec.getDescription(), () -> abortAndReset(testSpec.getReason())); + } + + SchemaLocation testCaseFileUri = SchemaLocation.of("classpath:" + toForwardSlashPath(testSpec.getTestCase().getSpecification())); + Schema schema = validatorFactory.getSchema(testCaseFileUri, testSpec.getTestCase().getSchema()); + + return dynamicTest(testSpec.getDescription(), () -> executeAndReset(schema, testSpec)); + } + + private void abortAndReset(String reason) { + try { + abort(reason); + } finally { + cleanup(); + } + } + + private void executeAndReset(Schema schema, TestSpec testSpec) { + try { + executeTest(schema, testSpec); + } finally { + cleanup(); + } + } + + protected void cleanup() { + } + + private List findTestCases(String basePath) { + try (Stream paths = Files.walk(Paths.get(basePath))) { + return paths + .filter(path -> path.toString().endsWith(".json")) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/test/java/com/networknt/schema/AdditionalPropertiesOneOfFailsTest.java b/src/test/java/com/networknt/schema/AdditionalPropertiesOneOfFailsTest.java deleted file mode 100644 index 472aa28f1..000000000 --- a/src/test/java/com/networknt/schema/AdditionalPropertiesOneOfFailsTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.util.Set; - -public class AdditionalPropertiesOneOfFailsTest { - - private static Set errors = null; - - protected JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - return factory.getSchema(schemaContent); - } - - protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(content); - return node; - } - - @BeforeEach - public void withJsonSchema() { - // correct processing would include the assertions in the tests - if (errors == null) { - String schemaPathJson = "/openapi3/AdditionalPropertiesOneOfFailsTest.json"; - String dataPath = "/data/AdditionalPropertiesOneOfFailsTest.json"; - InputStream schemaInputStream = getClass().getResourceAsStream(schemaPathJson); - - JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream); - schema.getValidationContext().getConfig().setFailFast(false); - - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - try { - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - - errors = schema.validate(node); - - System.out.println("nr. of reported errors: " + errors.size()); - errors.stream().forEach(er -> System.out.println(er.toString())); - } catch (Exception e) { - System.out.println("Fail!"); - } - - } - } - - @Test - @Disabled - public void toxicIsAdditional() { - Assertions.assertTrue(errors.stream().filter(er -> er.toString().contains("toxic: is not defined in the schema")).count() == 2, - "property toxic is not defined on activity chemical"); - } - - @Test - @Disabled - public void chemicalCharacteristicNameIsAdditional() { - - - Assertions.assertTrue(errors.stream().filter(er -> er.toString().contains("$.activities[2].chemicalCharacteristic.name: is not defined in the schema")).count() == 1, - "property name is not defined in 'oneOf' the ChemicalCharacteristic component schemas"); - } - - - @Test - @Disabled - public void depthIsAdditional() { - - Assertions.assertTrue(errors.stream().filter(er -> er.toString().contains("depth: is not defined in the schema")).count() == 1, - "property depth is not defined on activity machine"); - } - - @Test - @Disabled - public void chemicalCharacteristicCategoryNameIsDefined() { - - Assertions.assertFalse(errors.stream().filter(er -> er.toString().contains("$.activities[0].chemicalCharacteristic.categoryName: is not defined in the schema")).count() == 1, - "property categoryName is defined in 'oneOf' the ChemicalCharacteristic component schemas "); - } - - @Test - @Disabled - public void weightIsMissingOnlyOnce() { - - Assertions.assertTrue(errors.stream().filter(er -> er.toString().contains("weight: is missing")).count() == 1, - "property weight is required on activity machine "); - } - - @Test - @Disabled - public void heightIsNotMissingNotOnceAndNotTwice() { - - Assertions.assertFalse(errors.stream().filter(er -> er.toString().contains("heigth: is missing")).count() == 1, - "property height is defined "); - - } - - @Test - @Disabled - public void heightWrongType() { - - Assertions.assertTrue(errors.stream().filter(er -> er.toString().contains("heigth: number found, integer expected")).count() == 1, - "property height has the wrong type"); - - } -} diff --git a/src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java new file mode 100644 index 000000000..64dc4330c --- /dev/null +++ b/src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * AdditionalPropertiesValidatorTest. + */ +class AdditionalPropertiesValidatorTest { + /** + * Tests that the message contains the correct values when additional properties + * schema is false. + */ + @Test + void messageFalse() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"foo\":\"hello\",\r\n" + + " \"bar\":\"world\"\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/additionalProperties", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/additionalProperties", message.getSchemaLocation().toString()); + assertEquals("", message.getInstanceLocation().toString()); + assertEquals("false", message.getSchemaNode().toString()); + assertEquals("{\"foo\":\"hello\",\"bar\":\"world\"}", message.getInstanceNode().toString()); + assertEquals(": property 'bar' is not defined in the schema and the schema does not allow additional properties", message.toString()); + assertEquals("bar", message.getProperty()); + } + + /** + * Tests that the message contains the correct values when additional properties + * schema has a schema with type. + */ + @Test + void messageSchema() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": { \"type\": \"number\" }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"foo\":\"hello\",\r\n" + + " \"bar\":\"world\"\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/additionalProperties/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/additionalProperties/type", message.getSchemaLocation().toString()); + assertEquals("/bar", message.getInstanceLocation().toString()); + assertEquals("\"number\"", message.getSchemaNode().toString()); + assertEquals("\"world\"", message.getInstanceNode().toString()); + assertEquals("/bar: string found, number expected", message.toString()); + assertNull(message.getProperty()); + } + +} diff --git a/src/test/java/com/networknt/schema/AllOfValidatorTest.java b/src/test/java/com/networknt/schema/AllOfValidatorTest.java new file mode 100644 index 000000000..9559e1ff7 --- /dev/null +++ b/src/test/java/com/networknt/schema/AllOfValidatorTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class AllOfValidatorTest { + @Test + void invalidTypeShouldThrowSchemaException() { + String schemaData = "{\r\n" + + " \"$defs\": {\r\n" + + " \"User\": true\r\n" + + " },\r\n" + + " \"allOf\": {\r\n" + + " \"$ref\": \"#/defs/User\"\r\n" + + " }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + SchemaException ex = assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + assertEquals("type", ex.getError().getMessageKey()); + } + + @Test + void walkValidationWithNullNodeShouldNotValidate() { + String schemaContents = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"prop1\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false\r\n" + + " }"; + + String jsonContents = "{}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaContents); + Result result = schema.walk(jsonContents, InputFormat.JSON, true); + assertEquals(true, result.getErrors().isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/AnnotationsTest.java b/src/test/java/com/networknt/schema/AnnotationsTest.java new file mode 100644 index 000000000..e147ef12a --- /dev/null +++ b/src/test/java/com/networknt/schema/AnnotationsTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.annotation.Annotation; +import com.networknt.schema.annotation.Annotations; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.path.PathType; + +/** + * AnnotationsTest. + */ +class AnnotationsTest { + @Test + void put() { + Annotations annotations = new Annotations(); + Annotation annotation = new Annotation("unevaluatedProperties", + new NodePath(PathType.JSON_POINTER), SchemaLocation.of(""), new NodePath(PathType.JSON_POINTER), + "test"); + annotations.put(annotation); + assertTrue(annotations.asMap().get(annotation.getInstanceLocation()).contains(annotation)); + } +} diff --git a/src/test/java/com/networknt/schema/AnyOfValidatorTest.java b/src/test/java/com/networknt/schema/AnyOfValidatorTest.java new file mode 100644 index 000000000..b7b89e78b --- /dev/null +++ b/src/test/java/com/networknt/schema/AnyOfValidatorTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class AnyOfValidatorTest { + @Test + void invalidTypeShouldThrowJsonSchemaException() { + String schemaData = "{\r\n" + + " \"$defs\": {\r\n" + + " \"User\": true\r\n" + + " },\r\n" + + " \"anyOf\": {\r\n" + + " \"$ref\": \"#/defs/User\"\r\n" + + " }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + SchemaException ex = assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + assertEquals("type", ex.getError().getMessageKey()); + } + + @Test + void walkValidationWithNullNodeShouldNotValidate() { + String schemaContents = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"prop1\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false\r\n" + + " }"; + + String jsonContents = "{}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaContents); + Result result = schema.walk(jsonContents, InputFormat.JSON, true); + assertEquals(true, result.getErrors().isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java index 544a71f74..d02924de5 100644 --- a/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java +++ b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java @@ -18,10 +18,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.serialization.JsonMapperFactory; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -30,47 +30,55 @@ */ public class BaseJsonSchemaValidatorTest { - private ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = JsonMapperFactory.getInstance(); - protected JsonNode getJsonNodeFromClasspath(String name) throws IOException { + public static JsonNode getJsonNodeFromClasspath(String name) throws IOException { InputStream is1 = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(name); + .getResourceAsStream(name); return mapper.readTree(is1); } - protected JsonNode getJsonNodeFromStringContent(String content) throws IOException { + public static JsonNode getJsonNodeFromStringContent(String content) throws IOException { return mapper.readTree(content); } - protected JsonNode getJsonNodeFromUrl(String url) throws IOException { + public static JsonNode getJsonNodeFromUrl(String url) throws IOException { return mapper.readTree(new URL(url)); } - protected JsonSchema getJsonSchemaFromClasspath(String name) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + public static Schema getJsonSchemaFromClasspath(String name) { + return getJsonSchemaFromClasspath(name, SpecificationVersion.DRAFT_4, null); + } + + public static Schema getJsonSchemaFromClasspath(String name, SpecificationVersion schemaVersion) { + return getJsonSchemaFromClasspath(name, schemaVersion, null); + } + + public static Schema getJsonSchemaFromClasspath(String name, SpecificationVersion schemaVersion, SchemaRegistryConfig config) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(schemaVersion, builder -> builder.schemaRegistryConfig(config)); InputStream is = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(name); + .getResourceAsStream(name); return factory.getSchema(is); } - protected JsonSchema getJsonSchemaFromStringContent(String schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + public static Schema getJsonSchemaFromStringContent(String schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); return factory.getSchema(schemaContent); } - protected JsonSchema getJsonSchemaFromUrl(String uri) throws URISyntaxException { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - return factory.getSchema(new URI(uri)); + public static Schema getJsonSchemaFromUrl(String uri) throws URISyntaxException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + return factory.getSchema(SchemaLocation.of(uri)); } - protected JsonSchema getJsonSchemaFromJsonNode(JsonNode jsonNode) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + public static Schema getJsonSchemaFromJsonNode(JsonNode jsonNode) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); return factory.getSchema(jsonNode); } // Automatically detect version for given JsonNode - protected JsonSchema getJsonSchemaFromJsonNodeAutomaticVersion(JsonNode jsonNode) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(jsonNode)); + public static Schema getJsonSchemaFromJsonNodeAutomaticVersion(JsonNode jsonNode) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersionDetector.detect(jsonNode)); return factory.getSchema(jsonNode); } diff --git a/src/test/java/com/networknt/schema/BaseSuiteJsonSchemaTest.java b/src/test/java/com/networknt/schema/BaseSuiteJsonSchemaTest.java deleted file mode 100644 index eeebfe3be..000000000 --- a/src/test/java/com/networknt/schema/BaseSuiteJsonSchemaTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import io.undertow.Undertow; -import io.undertow.server.handlers.resource.FileResourceManager; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; - -import java.io.File; -import java.io.InputStream; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -import static io.undertow.Handlers.resource; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public abstract class BaseSuiteJsonSchemaTest { - protected ObjectMapper mapper = new ObjectMapper(); - protected JsonSchemaFactory validatorFactory; - protected static Undertow server = null; - - protected BaseSuiteJsonSchemaTest(SpecVersion.VersionFlag version) { - validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(version)).objectMapper(mapper).build(); - } - - @BeforeAll - public static void setUp() { - if (server == null) { - server = Undertow.builder() - .addHttpListener(1234, "localhost") - .setHandler(resource(new FileResourceManager( - new File("./src/test/resources/remotes"), 100))) - .build(); - server.start(); - } - } - - @AfterAll - public static void tearDown() throws Exception { - if (server != null) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - - } - server.stop(); - server = null; - } - } - - protected void runTestFile(String testCaseFile) throws Exception { - final URI testCaseFileUri = URI.create("classpath:" + testCaseFile); - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(testCaseFile); - ArrayNode testCases = mapper.readValue(in, ArrayNode.class); - - for (int j = 0; j < testCases.size(); j++) { - try { - JsonNode testCase = testCases.get(j); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - - ArrayNode testNodes = (ArrayNode) testCase.get("tests"); - for (int i = 0; i < testNodes.size(); i++) { - JsonNode test = testNodes.get(i); - JsonNode node = test.get("data"); - JsonNode typeLooseNode = test.get("isTypeLoose"); - // Configure the schemaValidator to set typeLoose's value based on the test file, - // if test file do not contains typeLoose flag, use default value: true. - config.setTypeLoose((typeLooseNode == null) ? false : typeLooseNode.asBoolean()); - JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config); - List errors = new ArrayList(); - - errors.addAll(schema.validate(node)); - - if (test.get("valid").asBoolean()) { - if (!errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema.toString()); - System.out.println("data: " + test.get("data")); - } - assertEquals(0, errors.size()); - } else { - if (errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); - } else { - JsonNode errorCount = test.get("errorCount"); - if (errorCount != null && errorCount.isInt() && errors.size() != errorCount.asInt()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); - System.out.println("errors: " + errors); - assertEquals(errorCount.asInt(), errors.size(), "expected error count"); - } - } - assertEquals(false, errors.isEmpty()); - } - } - } catch (JsonSchemaException e) { - throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e); - } - } - } -} diff --git a/src/test/java/com/networknt/schema/CollectorContextTest.java b/src/test/java/com/networknt/schema/CollectorContextTest.java index e67ca8df9..0d5ada6e2 100644 --- a/src/test/java/com/networknt/schema/CollectorContextTest.java +++ b/src/test/java/com/networknt/schema/CollectorContextTest.java @@ -19,53 +19,62 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.Format; +import com.networknt.schema.keyword.AbstractKeywordValidator; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +class CollectorContextTest { + enum Data { + SAMPLE_COLLECTOR, + SAMPLE_COLLECTOR_OTHER + } -public class CollectorContextTest { - - private static final String SAMPLE_COLLECTOR = "sampleCollector"; - - private static final String SAMPLE_COLLECTOR_OTHER = "sampleCollectorOther"; - - private JsonSchema jsonSchema; - - private JsonSchema jsonSchemaForCombine; + private Schema jsonSchema; + private Schema jsonSchemaForCombine; + @BeforeEach - public void setup() throws Exception { + void setup() throws Exception { setupSchema(); } - @AfterEach - public void cleanup() { - if (CollectorContext.getInstance() != null) { - CollectorContext.getInstance().reset(); - } - } - - @SuppressWarnings("unchecked") @Test - public void testCollectorContextWithKeyword() throws Exception { - ValidationResult validationResult = validate("{\"test-property1\":\"sample1\",\"test-property2\":\"sample2\"}"); - Assertions.assertEquals(0, validationResult.getValidationMessages().size()); - List contextValues = (List) validationResult.getCollectorContext().get(SAMPLE_COLLECTOR); + void testCollectorContextWithKeyword() throws Exception { + Result validationResult = validate("{\"test-property1\":\"sample1\",\"test-property2\":\"sample2\"}"); + Assertions.assertEquals(0, validationResult.getErrors().size()); + List contextValues = validationResult.getCollectorContext().get(Data.SAMPLE_COLLECTOR); contextValues.sort(null); - Assertions.assertEquals(0, validationResult.getValidationMessages().size()); + Assertions.assertEquals(0, validationResult.getErrors().size()); Assertions.assertEquals(2, contextValues.size()); Assertions.assertEquals(contextValues.get(0), "actual_value_added_to_context1"); Assertions.assertEquals(contextValues.get(1), "actual_value_added_to_context2"); } - @SuppressWarnings("unchecked") @Test - public void testCollectorContextWithMultipleThreads() throws Exception { + void testCollectorContextWithMultipleThreads() throws Exception { ValidationThread validationRunnable1 = new ValidationThread("{\"test-property1\":\"sample1\" }", "thread1"); ValidationThread validationRunnable2 = new ValidationThread("{\"test-property1\":\"sample2\" }", "thread2"); @@ -86,70 +95,62 @@ public void testCollectorContextWithMultipleThreads() throws Exception { thread2.join(); thread3.join(); - ValidationResult validationResult1 = validationRunnable1.getValidationResult(); - ValidationResult validationResult2 = validationRunnable2.getValidationResult(); - ValidationResult validationResult3 = validationRunnable3.getValidationResult(); + Result validationResult1 = validationRunnable1.getValidationResult(); + Result validationResult2 = validationRunnable2.getValidationResult(); + Result validationResult3 = validationRunnable3.getValidationResult(); - Assertions.assertEquals(0, validationResult1.getValidationMessages().size()); - Assertions.assertEquals(0, validationResult2.getValidationMessages().size()); - Assertions.assertEquals(0, validationResult3.getValidationMessages().size()); + Assertions.assertEquals(0, validationResult1.getErrors().size()); + Assertions.assertEquals(0, validationResult2.getErrors().size()); + Assertions.assertEquals(0, validationResult3.getErrors().size()); - List contextValue1 = (List) validationResult1.getCollectorContext().get(SAMPLE_COLLECTOR); - List contextValue2 = (List) validationResult2.getCollectorContext().get(SAMPLE_COLLECTOR); - List contextValue3 = (List) validationResult3.getCollectorContext().get(SAMPLE_COLLECTOR); + List contextValue1 = validationResult1.getCollectorContext().get(Data.SAMPLE_COLLECTOR); + List contextValue2 = validationResult2.getCollectorContext().get(Data.SAMPLE_COLLECTOR); + List contextValue3 = validationResult3.getCollectorContext().get(Data.SAMPLE_COLLECTOR); Assertions.assertEquals(contextValue1.get(0), "actual_value_added_to_context1"); Assertions.assertEquals(contextValue2.get(0), "actual_value_added_to_context2"); Assertions.assertEquals(contextValue3.get(0), "actual_value_added_to_context3"); } - @SuppressWarnings("unchecked") @Test - public void testCollectorGetAll() throws JsonMappingException, JsonProcessingException, IOException { + void testCollectorGetAll() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); - ValidationResult validationResult = jsonSchemaForCombine.validateAndCollect(objectMapper + ExecutionContext executionContext = jsonSchemaForCombine.createExecutionContext(); + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + jsonSchemaForCombine.validate(executionContext, objectMapper .readTree("{\"property1\":\"sample1\",\"property2\":\"sample2\",\"property3\":\"sample3\" }")); + Result validationResult = new Result(executionContext); CollectorContext collectorContext = validationResult.getCollectorContext(); - Assertions.assertEquals(((List) collectorContext.get(SAMPLE_COLLECTOR)).size(), 1); - Assertions.assertEquals(((List) collectorContext.get(SAMPLE_COLLECTOR_OTHER)).size(), 3); + List sampleCollector = collectorContext.get(Data.SAMPLE_COLLECTOR); + List sampleCollectorOther = collectorContext.get(Data.SAMPLE_COLLECTOR_OTHER); + Assertions.assertEquals(sampleCollector.size(), 1); + Assertions.assertEquals(sampleCollectorOther.size(), 3); } - private JsonMetaSchema getJsonMetaSchema(String uri) throws Exception { - JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(uri, JsonMetaSchema.getV201909()) - .addKeyword(new CustomKeyword()).addKeyword(new CustomKeyword1()).addFormat(new Format() { - - @SuppressWarnings("unchecked") - @Override - public boolean matches(String value) { - CollectorContext collectorContext = CollectorContext.getInstance(); - if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, new ArrayList()); - } - List returnList = (List) collectorContext.get(SAMPLE_COLLECTOR); - returnList.add(value); - return true; - } + private Dialect getDialect(String uri) throws Exception { + Dialect dialect = Dialect.builder(uri, Dialects.getDraft201909()) + .keyword(new CustomKeyword()).keyword(new CustomKeyword1()).format(new Format() { + @Override + public boolean matches(ExecutionContext executionContext, String value) { + CollectorContext collectorContext = executionContext.getCollectorContext(); + List returnList = collectorContext.computeIfAbsent(Data.SAMPLE_COLLECTOR, + key -> new ArrayList()); + returnList.add(value); + return true; + } @Override public String getName() { return "sample-format"; } - - // Return null. As are just testing collection context. - @Override - public String getErrorMessageDescription() { - return null; - } }).build(); - return jsonMetaSchema; + return dialect; } private void setupSchema() throws Exception { - final JsonMetaSchema metaSchema = getJsonMetaSchema( + final Dialect dialect = getDialect( "https://github.com/networknt/json-schema-validator/tests/schemas/example01"); - final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) - .build(); + final SchemaRegistry schemaFactory = SchemaRegistry.withDialect(dialect); this.jsonSchema = schemaFactory.getSchema(getSchemaString()); this.jsonSchemaForCombine = schemaFactory.getSchema(getSchemaStringMultipleProperties()); } @@ -213,12 +214,12 @@ private String getSchemaStringMultipleProperties() { private class ValidationThread implements Runnable { - private String data; - - private String name; + private final String data; - private ValidationResult validationResult; + private final String name; + private Result validationResult; + ValidationThread(String data, String name) { this.name = name; this.data = data; @@ -237,7 +238,7 @@ public void run() { } } - ValidationResult getValidationResult() { + Result getValidationResult() { return this.validationResult; } @@ -259,10 +260,10 @@ public String getValue() { } @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator(); + return new CustomValidator(schemaLocation, schemaNode); } return null; } @@ -274,51 +275,68 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc * This will be helpful in cases where we don't want to revisit the entire JSON * document again just for gathering this kind of information. */ - private class CustomValidator implements JsonValidator { - - @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { - // Get an instance of collector context. - CollectorContext collectorContext = CollectorContext.getInstance(); - if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector()); - } - collectorContext.combineWithCollector(SAMPLE_COLLECTOR, node.textValue()); - return new TreeSet(); + private class CustomValidator extends AbstractKeywordValidator { + private final CustomCollector customCollector = new CustomCollector(); + public CustomValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode, schemaLocation); } - @Override - public Set validate(JsonNode rootNode) { - return validate(rootNode, rootNode, BaseJsonValidator.AT_ROOT); - } + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + CollectorContext collectorContext = executionContext.getCollectorContext(); + List result = collectorContext.computeIfAbsent(Data.SAMPLE_COLLECTOR, + key -> customCollector.supplier().get()); + customCollector.accumulator().accept(result, node); + } @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { // Ignore this method for testing. - return null; } } - - private class CustomCollector extends AbstractCollector> { - - List returnList = new ArrayList(); - + private class CustomCollector implements Collector, List> { private Map referenceMap = null; public CustomCollector() { - referenceMap = getDatasourceMap(); + this(getDatasourceMap()); } - - @Override - public List collect() { - return returnList; - } - - @Override - public void combine(Object object) { - returnList.add(referenceMap.get((String) object)); + + public CustomCollector(Map referenceMap) { + this.referenceMap = referenceMap; } + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, JsonNode> accumulator() { + return (returnList, instanceNode) -> { + synchronized (returnList) { + returnList.add(referenceMap.get(instanceNode.textValue())); + } + }; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> { + left.addAll(right); + return left; + }; + } + + @Override + public Function, List> finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH)); + } } /** @@ -332,10 +350,10 @@ public String getValue() { } @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator1(); + return new CustomValidator1(schemaLocation, schemaNode); } return null; } @@ -349,43 +367,197 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc * we expect this validator to be called multiple times as the associated * keyword has been used multiple times in JSON Schema. */ - private class CustomValidator1 implements JsonValidator { - @SuppressWarnings("unchecked") + private class CustomValidator1 extends AbstractKeywordValidator { + public CustomValidator1(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode,schemaLocation); + } + @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { // Get an instance of collector context. - CollectorContext collectorContext = CollectorContext.getInstance(); + CollectorContext collectorContext = executionContext.getCollectorContext(); // If collector type is not added to context add one. - if (collectorContext.get(SAMPLE_COLLECTOR_OTHER) == null) { - collectorContext.add(SAMPLE_COLLECTOR_OTHER, new ArrayList()); + List returnList = collectorContext.computeIfAbsent(Data.SAMPLE_COLLECTOR_OTHER, + key -> new ArrayList()); + synchronized (returnList) { + returnList.add(node.textValue()); } - List returnList = (List) collectorContext.get(SAMPLE_COLLECTOR_OTHER); - returnList.add(node.textValue()); - return new TreeSet(); } @Override - public Set validate(JsonNode rootNode) { - return validate(rootNode, rootNode, BaseJsonValidator.AT_ROOT); - } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { // Ignore this method for testing. - return null; } } - private ValidationResult validate(String jsonData) throws JsonMappingException, JsonProcessingException, Exception { + private Result validate(String jsonData) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - return this.jsonSchema.validateAndCollect(objectMapper.readTree(jsonData)); + ExecutionContext executionContext = this.jsonSchema.createExecutionContext(); + this.jsonSchema.validate(executionContext, objectMapper.readTree(jsonData)); + return new Result(executionContext); } - private Map getDatasourceMap() { + protected static Map getDatasourceMap() { Map map = new HashMap(); map.put("sample1", "actual_value_added_to_context1"); map.put("sample2", "actual_value_added_to_context2"); map.put("sample3", "actual_value_added_to_context3"); return map; } + + @Test + void constructor() { + CollectorContext context = new CollectorContext(); + assertTrue(context.getData().isEmpty()); + } + + @Test + void constructorWithMap() { + ConcurrentHashMap data = new ConcurrentHashMap<>(); + CollectorContext context = new CollectorContext(data); + assertSame(data, context.getData()); + } + + private class CollectKeyword implements Keyword { + @Override + public String getValue() { + return "collect"; + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { + if (schemaNode != null && schemaNode.isBoolean()) { + return new CollectValidator(schemaLocation, schemaNode); + } + return null; + } + } + + private class CollectValidator extends AbstractKeywordValidator { + CollectValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CollectKeyword(), schemaNode, schemaLocation); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + // Get an instance of collector context. + CollectorContext collectorContext = executionContext.getCollectorContext(); + AtomicInteger count = collectorContext.computeIfAbsent("collect", + (key) -> new AtomicInteger(0)); + count.incrementAndGet(); + } + + @Override + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + if (!shouldValidateSchema) { + CollectorContext collectorContext = executionContext.getCollectorContext(); + AtomicInteger count = (AtomicInteger) collectorContext.getData().computeIfAbsent("collect", + (key) -> new AtomicInteger(0)); + count.incrementAndGet(); + } + super.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + } + + @Test + void concurrency() throws Exception { + CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>()); + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(new CollectKeyword()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + Schema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); + Exception[] instance = new Exception[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + schema.validate("1", InputFormat.JSON, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); + } catch (RuntimeException e) { + instance[0] = e; + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + if (instance[0] != null) { + throw instance[0]; + } + AtomicInteger result = collectorContext.get("collect"); + assertEquals(50, result.get()); + } + + @Test + void iterate() { + CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>()); + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(new CollectKeyword()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + Schema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); + for (int i = 0; i < 50; ++i) { + schema.validate("1", InputFormat.JSON, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); + } + AtomicInteger result = collectorContext.get("collect"); + assertEquals(50, result.get()); + } + + @Test + void iterateWalk() { + CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>()); + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(new CollectKeyword()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + Schema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); + for (int i = 0; i < 50; ++i) { + schema.walk("1", InputFormat.JSON, false, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); + } + AtomicInteger result = collectorContext.get("collect"); + assertEquals(50, result.get()); + } + + @Test + void iterateWalkValidate() { + CollectorContext collectorContext = new CollectorContext(new ConcurrentHashMap<>()); + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(new CollectKeyword()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + Schema schema = factory.getSchema("{\n" + + " \"collect\": true\n" + + "}"); + for (int i = 0; i < 50; ++i) { + schema.walk("1", InputFormat.JSON, true, executionContext -> { + executionContext.setCollectorContext(collectorContext); + }); + } + AtomicInteger result = collectorContext.get("collect"); + assertEquals(50, result.get()); + } + } diff --git a/src/test/java/com/networknt/schema/ConstValidatorTest.java b/src/test/java/com/networknt/schema/ConstValidatorTest.java new file mode 100644 index 000000000..585c0552a --- /dev/null +++ b/src/test/java/com/networknt/schema/ConstValidatorTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.i18n.ResourceBundleMessageSource; + +/** + * Test for ConstValidator. + */ +class ConstValidatorTest { + + @Test + void localeMessageOthers() { + String schemaData = "{\r\n" + + " \"const\": \"aa\"\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .messageSource(new ResourceBundleMessageSource("const-messages-override", "jsv-messages")) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + String inputData = "\"bb\""; + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(": must be the constant value 'aa' but is 'bb'", messages.iterator().next().toString()); + } + + @Test + void localeMessageNumber() { + String schemaData = "{\r\n" + + " \"const\": 1\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .messageSource(new ResourceBundleMessageSource("const-messages-override", "jsv-messages")) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + String inputData = "2"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(": must be the constant value '1' but is '2'", messages.iterator().next().toString()); + } + + @Test + void validOthers() { + String schemaData = "{\r\n" + + " \"const\": \"aa\"\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + String inputData = "\"aa\""; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void validNumber() { + String schemaData = "{\r\n" + + " \"const\": 1234.56789\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + String inputData = "1234.56789"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void invalidNumber() { + String schemaData = "{\r\n" + + " \"const\": 1234.56789\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + String inputData = "\"1234.56789\""; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + } + +} diff --git a/src/test/java/com/networknt/schema/ContentSchemaValidatorTest.java b/src/test/java/com/networknt/schema/ContentSchemaValidatorTest.java new file mode 100644 index 000000000..929c0cd1a --- /dev/null +++ b/src/test/java/com/networknt/schema/ContentSchemaValidatorTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.output.OutputUnit; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * ContentSchemaValidatorTest. + */ +class ContentSchemaValidatorTest { + @Test + void annotationCollection() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"contentMediaType\": \"application/jwt\",\r\n" + + " \"contentSchema\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"minItems\": 2,\r\n" + + " \"prefixItems\": [\r\n" + + " {\r\n" + + " \"const\": {\r\n" + + " \"typ\": \"JWT\",\r\n" + + " \"alg\": \"HS256\"\r\n" + + " }\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\"iss\", \"exp\"],\r\n" + + " \"properties\": {\r\n" + + " \"iss\": {\"type\": \"string\"},\r\n" + + " \"exp\": {\"type\": \"integer\"}\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = "\"helloworld\""; + + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":true,\"details\":[{\"valid\":true,\"evaluationPath\":\"\",\"schemaLocation\":\"#\",\"instanceLocation\":\"\",\"annotations\":{\"contentMediaType\":\"application/jwt\",\"contentSchema\":{\"type\":\"array\",\"minItems\":2,\"prefixItems\":[{\"const\":{\"typ\":\"JWT\",\"alg\":\"HS256\"}},{\"type\":\"object\",\"required\":[\"iss\",\"exp\"],\"properties\":{\"iss\":{\"type\":\"string\"},\"exp\":{\"type\":\"integer\"}}}]}}}]}"; + assertEquals(expected, output); + } +} diff --git a/src/test/java/com/networknt/schema/CustomMessageTest.java b/src/test/java/com/networknt/schema/CustomMessageTest.java new file mode 100644 index 000000000..cf0064bd0 --- /dev/null +++ b/src/test/java/com/networknt/schema/CustomMessageTest.java @@ -0,0 +1,24 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.util.stream.Stream; + +@DisplayName("Custom Messages") +class CustomMessageTest extends AbstractJsonSchemaTestSuite { + + @TestFactory + @DisplayName("Draft 2019-09 - Custom Messages Enabled") + Stream draft201909__customMessagesEnabled() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/schema/customMessageTests/custom-message-tests.json"); + } + + @TestFactory + @DisplayName("Draft 2019-09 - Custom Messages Disabled") + Stream draft201909__customMessagesDisabled() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/schema/customMessageTests/custom-message-disabled-tests.json"); + } + +} diff --git a/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java b/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java index d1e0ac808..50fde8bac 100644 --- a/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java +++ b/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java @@ -16,20 +16,24 @@ package com.networknt.schema; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.AbstractKeyword; +import com.networknt.schema.keyword.AbstractKeywordValidator; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; + import org.junit.jupiter.api.Test; import java.io.IOException; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; -public class CustomMetaSchemaTest { +class CustomMetaSchemaTest { /** * Introduces the keyword "enumNames". @@ -41,59 +45,68 @@ public class CustomMetaSchemaTest { * * @author klaskalass */ - public static class EnumNamesKeyword extends AbstractKeyword { + static class EnumNamesKeyword extends AbstractKeyword { - private static final class Validator extends AbstractJsonValidator { + private static final class Validator extends AbstractKeywordValidator { private final List enumValues; private final List enumNames; + private final String keyword; - private Validator(String keyword, List enumValues, List enumNames) { - super(keyword); + private Validator(SchemaLocation schemaLocation, String keyword, + List enumValues, List enumNames, JsonNode schemaNode) { + super(new EnumNamesKeyword(), schemaNode, schemaLocation); if (enumNames.size() != enumValues.size()) { throw new IllegalArgumentException("enum and enumNames need to be of same length"); } this.enumNames = enumNames; this.enumValues = enumValues; + this.keyword = keyword; } @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { String value = node.asText(); int idx = enumValues.indexOf(value); if (idx < 0) { throw new IllegalArgumentException("value not found in enum. value: " + value + " enum: " + enumValues); } String valueName = enumNames.get(idx); - return fail(CustomErrorMessageType.of("tests.example.enumNames", new MessageFormat("{0}: enumName is {1}")), at, valueName); + Error error = Error.builder().keyword(keyword) + .schemaNode(node) + .instanceNode(node) + .messageKey("tests.example.enumNames").message("enumName is {0}").instanceLocation(instanceLocation) + .arguments(valueName).build(); + executionContext.addError(error); } } - public EnumNamesKeyword() { + EnumNamesKeyword() { super("enumNames"); } @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { /* * You can access the schema node here to read data from your keyword */ if (!schemaNode.isArray()) { - throw new JsonSchemaException("Keyword enumNames needs to receive an array"); + throw new SchemaException("Keyword enumNames needs to receive an array"); } JsonNode parentSchemaNode = parentSchema.getSchemaNode(); if (!parentSchemaNode.has("enum")) { - throw new JsonSchemaException("Keyword enumNames needs to have a sibling enum keyword"); + throw new SchemaException("Keyword enumNames needs to have a sibling enum keyword"); } JsonNode enumSchemaNode = parentSchemaNode.get("enum"); - return new Validator(getValue(), readStringList(enumSchemaNode), readStringList(schemaNode)); + return new Validator(schemaLocation, getValue(), readStringList(enumSchemaNode), + readStringList(schemaNode), schemaNode); } private List readStringList(JsonNode node) { if (!node.isArray()) { - throw new JsonSchemaException("Keyword enum needs to receive an array"); + throw new SchemaException("Keyword enum needs to receive an array"); } ArrayList result = new ArrayList(node.size()); for (JsonNode child : node) { @@ -104,26 +117,26 @@ private List readStringList(JsonNode node) { } @Test - public void customMetaSchemaWithIgnoredKeyword() throws JsonProcessingException, IOException { + void customMetaSchemaWithIgnoredKeyword() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); - final JsonMetaSchema metaSchema = JsonMetaSchema - .builder("https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV4()) + final Dialect dialect = Dialect + .builder("https://github.com/networknt/json-schema-validator/tests/schemas/example01", Dialects.getDraft4()) // Generated UI uses enumNames to render Labels for enum values - .addKeyword(new EnumNamesKeyword()) + .keyword(new EnumNamesKeyword()) .build(); - final JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).addMetaSchema(metaSchema).build(); - final JsonSchema schema = validatorFactory.getSchema("{\n" + + final SchemaRegistry validatorFactory = SchemaRegistry.withDialect(dialect); + final Schema schema = validatorFactory.getSchema("{\n" + " \"$schema\":\n" + " \"https://github.com/networknt/json-schema-validator/tests/schemas/example01\",\n" + " \"enum\": [\"foo\", \"bar\"],\n" + " \"enumNames\": [\"Foo !\", \"Bar !\"]\n" + "}"); - Set messages = schema.validate(objectMapper.readTree("\"foo\"")); + List messages = schema.validate(objectMapper.readTree("\"foo\"")); assertEquals(1, messages.size()); - ValidationMessage message = messages.iterator().next(); - assertEquals("$: enumName is Foo !", message.getMessage()); + Error message = messages.iterator().next(); + assertEquals(": enumName is Foo !", message.toString()); } } diff --git a/src/test/java/com/networknt/schema/CustomUriTest.java b/src/test/java/com/networknt/schema/CustomUriTest.java index ded82508b..c5790c077 100644 --- a/src/test/java/com/networknt/schema/CustomUriTest.java +++ b/src/test/java/com/networknt/schema/CustomUriTest.java @@ -1,64 +1,46 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; -import com.networknt.schema.uri.URIFactory; -import com.networknt.schema.uri.URIFetcher; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.ResourceLoader; + import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.util.Set; +import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -public class CustomUriTest { +class CustomUriTest { @Test - public void customUri() throws Exception { + void customUri() throws Exception { /* Given */ - final JsonSchemaFactory factory = buildJsonSchemaFactory(); - final JsonSchema schema = factory.getSchema( + final SchemaRegistry factory = buildJsonSchemaFactory(); + final Schema schema = factory.getSchema( "{\"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\"type\": \"object\",\"additionalProperties\": false,\"properties\": {\"customAnyOf\": {\"anyOf\": [{\"type\": \"null\"},{\"$ref\": \"custom:date\"}]},\"customOneOf\": {\"oneOf\": [{\"type\": \"null\"},{\"$ref\": \"custom:date\"}]}}}"); final ObjectMapper mapper = new ObjectMapper(); final JsonNode value = mapper.readTree("{\"customAnyOf\": null,\"customOneOf\": null}"); /* When */ - final Set errors = schema.validate(value); + final List errors = schema.validate(value); /* Then */ assertThat(errors.isEmpty(), is(true)); } - private JsonSchemaFactory buildJsonSchemaFactory() { - return JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)) - .uriFetcher(new CustomUriFetcher(), "custom").uriFactory(new CustomUriFactory(), "custom").build(); + private SchemaRegistry buildJsonSchemaFactory() { + return SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09)) + .resourceLoaders(resourceLoaders -> resourceLoaders.add(new CustomUriFetcher())).build(); } - private static class CustomUriFetcher implements URIFetcher { + private static class CustomUriFetcher implements ResourceLoader { private static final String SCHEMA = "{\"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\"$id\":\"custom:date\",\"type\":\"string\",\"format\":\"date\"}"; @Override - public InputStream fetch(final URI uri) throws IOException { - return new ByteArrayInputStream(SCHEMA.getBytes(StandardCharsets.UTF_8)); - } - } - - private static class CustomUriFactory implements URIFactory { - @Override - public URI create(final String uri) { - return URI.create(uri); - } - - @Override - public URI create(final URI baseURI, final String segment) { - return baseURI.resolve(segment); + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + return () -> new ByteArrayInputStream(SCHEMA.getBytes(StandardCharsets.UTF_8)); } } } diff --git a/src/test/java/com/networknt/schema/CyclicDependencyTest.java b/src/test/java/com/networknt/schema/CyclicDependencyTest.java index 585217bd0..3ac6a8910 100644 --- a/src/test/java/com/networknt/schema/CyclicDependencyTest.java +++ b/src/test/java/com/networknt/schema/CyclicDependencyTest.java @@ -3,18 +3,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import java.net.URI; -import java.net.URL; - import static org.junit.jupiter.api.Assertions.assertEquals; -public class CyclicDependencyTest { +class CyclicDependencyTest { @Test - public void whenDependencyBetweenSchemaThenValidationSuccessful() throws Exception { + void whenDependencyBetweenSchemaThenValidationSuccessful() throws Exception { - JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)) + SchemaRegistry schemaFactory = SchemaRegistry + .builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4)) .build(); String jsonObject = "{\n" + " \"element\": {\n" + @@ -35,10 +32,7 @@ public void whenDependencyBetweenSchemaThenValidationSuccessful() throws Excepti " ]\n" + "}"; - URI jsonSchemaLocation = new URL("https://raw.githubusercontent.com/francesc79/json-schema-validator/bug/cyclic-dep/src/test/resources/draft4/cyclic/Master.json").toURI(); - - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - JsonSchema schema = schemaFactory.getSchema(jsonSchemaLocation, config); + Schema schema = schemaFactory.getSchema(SchemaLocation.of("resource:/draft4/issue258/Master.json")); assertEquals(0, schema.validate(new ObjectMapper().readTree(jsonObject)).size()); } diff --git a/src/test/java/com/networknt/schema/DateTimeDSTTest.java b/src/test/java/com/networknt/schema/DateTimeDSTTest.java index 035d1a3a7..f98ed0ffb 100644 --- a/src/test/java/com/networknt/schema/DateTimeDSTTest.java +++ b/src/test/java/com/networknt/schema/DateTimeDSTTest.java @@ -6,11 +6,11 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class DateTimeDSTTest { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class DateTimeDSTTest { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -21,14 +21,14 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldWorkV7() throws Exception { + void shouldWorkV7() throws Exception { String schemaPath = "/schema/dateTimeArray.json"; String dataPath = "/data/dstTimes.json"; // Contains 2020 DST changes for various countries InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/DefaultJsonSchemaIdValidatorTest.java b/src/test/java/com/networknt/schema/DefaultJsonSchemaIdValidatorTest.java new file mode 100644 index 000000000..4c52bd6e8 --- /dev/null +++ b/src/test/java/com/networknt/schema/DefaultJsonSchemaIdValidatorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import org.junit.jupiter.api.Test; + +/** + * Tests for the non-standard DefaultJsonSchemaIdValidator. + */ +class DefaultJsonSchemaIdValidatorTest { + @Test + void givenRelativeIdShouldThrowInvalidSchemaException() { + String schema = "{\r\n" + " \"$id\": \"0\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .schemaIdValidator(SchemaIdValidator.DEFAULT) + .build(); + assertThrowsExactly(InvalidSchemaException.class, + () -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema)); + try { + SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema); + } catch (InvalidSchemaException e) { + assertEquals("/$id: '0' is not a valid $id", e.getError().toString()); + } + } + + @Test + void givenFragmentWithNoContextShouldNotThrowInvalidSchemaException() { + String schema = "{\r\n" + " \"$id\": \"#0\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .schemaIdValidator(SchemaIdValidator.DEFAULT) + .build(); + assertDoesNotThrow(() -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema)); + } + + @Test + void givenSlashWithNoContextShouldNotThrowInvalidSchemaException() { + String schema = "{\r\n" + " \"$id\": \"/base\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .schemaIdValidator(SchemaIdValidator.DEFAULT) + .build(); + assertDoesNotThrow(() -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema)); + } + + @Test + void givenRelativeIdWithClasspathBaseShouldNotThrowInvalidSchemaException() { + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .schemaIdValidator(SchemaIdValidator.DEFAULT) + .build(); + assertDoesNotThrow(() -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(SchemaLocation.of("classpath:schema/id-relative.json"))); + } +} diff --git a/src/test/java/com/networknt/schema/DependentRequiredTest.java b/src/test/java/com/networknt/schema/DependentRequiredTest.java new file mode 100644 index 000000000..4a5e327a6 --- /dev/null +++ b/src/test/java/com/networknt/schema/DependentRequiredTest.java @@ -0,0 +1,66 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; + +class DependentRequiredTest { + + static final String SCHEMA = + "{ " + + " \"$schema\":\"https://json-schema.org/draft/2019-09/schema\"," + + " \"type\": \"object\"," + + " \"properties\": {" + + " \"optional\": \"string\"," + + " \"requiredWhenOptionalPresent\": \"string\"" + + " }," + + " \"dependentRequired\": {" + + " \"optional\": [ \"requiredWhenOptionalPresent\" ]," + + " \"otherOptional\": [ \"otherDependentRequired\" ]" + + " }" + + "}"; + + private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + private static final Schema schema = factory.getSchema(SCHEMA); + private static final ObjectMapper mapper = new ObjectMapper(); + + @Test + void shouldReturnNoErrorMessagesForObjectWithoutOptionalField() throws IOException { + + List messages = whenValidate("{}"); + + assertThat(messages, empty()); + } + + @Test + void shouldReturnErrorMessageForObjectWithoutDependentRequiredField() throws IOException { + + List messages = whenValidate("{ \"optional\": \"present\" }"); + + assertThat( + messages.stream().map(Error::toString).collect(Collectors.toList()), + contains(": has a missing property 'requiredWhenOptionalPresent' which is dependent required because 'optional' is present")); + } + + @Test + void shouldReturnNoErrorMessagesForObjectWithOptionalAndDependentRequiredFieldSet() throws JsonProcessingException { + + List messages = + whenValidate("{ \"optional\": \"present\", \"requiredWhenOptionalPresent\": \"present\" }"); + + assertThat(messages, empty()); + } + + private static List whenValidate(String content) throws JsonProcessingException { + return schema.validate(mapper.readTree(content)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/DisallowUnknownKeywordFactoryTest.java b/src/test/java/com/networknt/schema/DisallowUnknownKeywordFactoryTest.java new file mode 100644 index 000000000..7169a9586 --- /dev/null +++ b/src/test/java/com/networknt/schema/DisallowUnknownKeywordFactoryTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.DisallowUnknownKeywordFactory; + +class DisallowUnknownKeywordFactoryTest { + @Test + void shouldThrowForUnknownKeywords() { + DisallowUnknownKeywordFactory factory = DisallowUnknownKeywordFactory.getInstance(); + assertThrows(InvalidSchemaException.class, () -> factory.getKeyword("helloworld", null)); + } + + @Test + void getSchemaShouldThrowForUnknownKeywords() { + Dialect dialect = Dialect.builder(Dialects.getDraft202012()) + .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + String schemaData = "{\r\n" + + " \"equals\": \"world\"\r\n" + + "}"; + assertThrows(InvalidSchemaException.class, () -> factory.getSchema(schemaData)); + } +} diff --git a/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java new file mode 100644 index 000000000..1c4857c08 --- /dev/null +++ b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java @@ -0,0 +1,984 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.Dialects; + +/** + * Test for discriminator. + */ +class DiscriminatorValidatorTest { + /** + * Issue 609. + */ + @Test + void discriminatorInArray() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"Kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRoom\",\r\n" + + " \"numberOfBeds\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + /** + * Issue 588. + */ + @Test + void anyOfWithConfigEnabledButNoDiscriminator() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"intOrStringType\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }"; + + String inputData = "{\r\n" + + " \"intOrStringType\": 4\r\n" + + " }"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + /** + * Issue 609. + */ + @Test + void discriminatorInArrayInvalidDiscriminatorPropertyAnyOf() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"Kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRooooom\",\r\n" + + " \"numberOfBeds\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(1, messages.size()); // RECHECK THIS + } + + /** + * Issue 609. + */ + @Test + void discriminatorInArrayInvalidDiscriminatorPropertyOneOf() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"Kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRooooom\",\r\n" + + " \"numberOfBeds\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void discriminatorInArrayOneOfShouldOnlyReportErrorsInMatchingDiscriminator() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"Kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRoom\",\r\n" + + " \"incorrectProperty\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + // Only the oneOf and the error in the BedRoom discriminator is reported + // the mismatch in Kitchen is not reported + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("numberOfBeds", list.get(1).getProperty()); + } + + @Test + void discriminatorInOneOfShouldOnlyReportErrorsInMatchingDiscriminator() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ]\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"Kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRoom\",\r\n" + + " \"incorrectProperty\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + // Only the oneOf and the error in the BedRoom discriminator is reported + // the mismatch in Kitchen is not reported + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("numberOfBeds", list.get(1).getProperty()); + } + + @Test + void discriminatorMappingInOneOfShouldOnlyReportErrorsInMatchingDiscriminator() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\",\r\n" + + " \"mapping\": {\r\n" + + " \"kitchen\": \"#/components/schemas/Kitchen\",\r\n" + + " \"bedroom\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ]\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"@type\": \"kitchen\",\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"bedroom\",\r\n" + + " \"incorrectProperty\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + // Only the oneOf and the error in the BedRoom discriminator is reported + // the mismatch in Kitchen is not reported + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("numberOfBeds", list.get(1).getProperty()); + } + + /** + * See issue 436 and 985. + */ + @Test + void oneOfMissingDiscriminatorValue() { + String schemaData = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"discriminator\": { \"propertyName\": \"name\" },\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/defs/Foo\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/defs/Bar\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"defs\": {\r\n" + + " \"Foo\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"const\": \"Foo\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [ \"name\" ],\r\n" + + " \"additionalProperties\": false\r\n" + + " },\r\n" + + " \"Bar\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"const\": \"Bar\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [ \"name\" ],\r\n" + + " \"additionalProperties\": false\r\n" + + " }\r\n" + + " }\r\n" + + " }"; + + String inputData = "{}"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("required", list.get(2).getKeyword()); + } + + /** + * See issue 436. + */ + @Test + void anyOfMissingDiscriminatorValue() { + String schemaData = "{\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "[\r\n" + + " {\r\n" + + " \"hasMicrowaveOven\": true\r\n" + + " },\r\n" + + " {\r\n" + + " \"@type\": \"BedRoom\",\r\n" + + " \"numberOfBeds\": 4\r\n" + + " }\r\n" + + "]"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("required", list.get(0).getKeyword()); + } + + /** + * Mapped to Bedroom with missing number of beds, however the discriminator is + * not supposed to change the result of anyOf and the data passes for "/anyOf/0" + * : {"$ref":"#/components/schemas/Room"}. + * + * This case is an example of the actual implementation for undefined behavior + * eg. multiple discriminators for an instance location. + */ + @Test + void anyOfRedefinedDiscriminatorAndDiscriminatorWithMissingPropertyName() { + String schemaData = "{\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/KidsBedRoom\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/GuestRoom\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"components\": {\r\n" + + " \"schemas\": {\r\n" + + " \"Room\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"floor\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\",\r\n" + + " \"mapping\": {\r\n" + + " \"bed\": \"#/components/schemas/BedRoom\",\r\n" + + " \"guest\": \"#/components/schemas/GuestRoom\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"BedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"numberOfBeds\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"numberOfBeds\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"mapping\": {\r\n" + + " \"guest\": \"#/components/schemas/GuestRoom\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"KidsBedRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"isTidy\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"isTidy\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"GuestRoom\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"guest\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"guest\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Kitchen\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Room\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"hasMicrowaveOven\": {\r\n" + + " \"type\": \"boolean\"\r\n" + + " },\r\n" + + " \"equipment\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Pot\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/Blender\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"hasMicrowaveOven\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"KitchenEquipment\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"@type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"@type\"\r\n" + + " ],\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"@type\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"Pot\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/KitchenEquipment\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"capacity\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"capacity\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"Blender\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/components/schemas/KitchenEquipment\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"maxSpeed\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"maxSpeed\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData = "{\"@type\":\"bed\"}"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + List list = messages.stream().collect(Collectors.toList()); + // There should be no errors as discriminator should not affect the validation result of anyOf + // Although the matched schema has the following error + // : required property 'numberOfBeds' not found + // There is still a schema in the anyOf that matches + assertTrue(list.isEmpty()); + } + +} diff --git a/src/test/java/com/networknt/schema/DurationFormatValidatorTest.java b/src/test/java/com/networknt/schema/DurationFormatValidatorTest.java new file mode 100644 index 000000000..e71e00539 --- /dev/null +++ b/src/test/java/com/networknt/schema/DurationFormatValidatorTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DurationFormatValidatorTest { + + @Test + void durationFormatValidatorTest() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + final String schema = "{\"type\": \"string\", \"format\": \"duration\"}\n"; + final JsonNode validTargetNode = objectMapper.readTree("\"P1D\""); + final JsonNode invalidTargetNode = objectMapper.readTree("\"INVALID_DURATION\""); + + final SchemaRegistry validatorFactory = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09)).build(); + final Schema validatorSchema = validatorFactory.getSchema(schema); + + List messages = validatorSchema.validate(validTargetNode); + assertEquals(0, messages.size()); + + messages = validatorSchema.validate(invalidTargetNode, OutputFormat.DEFAULT, (executionContext, schemaContext) -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertEquals(1, messages.size()); + + } +} diff --git a/src/test/java/com/networknt/schema/EnumValidatorTest.java b/src/test/java/com/networknt/schema/EnumValidatorTest.java new file mode 100644 index 000000000..2e78e3b5c --- /dev/null +++ b/src/test/java/com/networknt/schema/EnumValidatorTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +/** + * EnumValidator test. + */ +class EnumValidatorTest { + + @Test + void enumWithObjectNodes() { + String schemaData = "{\r\n" + + " \"title\": \"Severity\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"title\": \"Name\",\r\n" + + " \"description\": \"The human readable name of the severity\",\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"cardinality\": {\r\n" + + " \"title\": \"Cardinality\",\r\n" + + " \"description\": \"The severities cardinality, the higher the worse it gets\",\r\n" + + " \"type\": \"integer\",\r\n" + + " \"minimum\": 0,\r\n" + + " \"maximum\": 50,\r\n" + + " \"multipleOf\": 10\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false,\r\n" + + " \"required\": [\r\n" + + " \"name\",\r\n" + + " \"cardinality\"\r\n" + + " ],\r\n" + + " \"enum\": [\r\n" + + " {\r\n" + + " \"name\": \"EMPTY\",\r\n" + + " \"cardinality\": 0\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"OK\",\r\n" + + " \"cardinality\": 20\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"UNKNOWN\",\r\n" + + " \"cardinality\": 30\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"WARNING\",\r\n" + + " \"cardinality\": 40\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"CRITICAL\",\r\n" + + " \"cardinality\": 50\r\n" + + " }\r\n" + + " ],\r\n" + + " \"default\": {\r\n" + + " \"name\": \"UNKNOWN\",\r\n" + + " \"cardinality\": 30\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"name\": \"FOO\",\r\n" + + " \"cardinality\": 50\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON).stream().collect(Collectors.toList()); + assertEquals(1, messages.size()); + Error message = messages.get(0); + assertEquals( + ": does not have a value in the enumeration [{\"name\":\"EMPTY\",\"cardinality\":0}, {\"name\":\"OK\",\"cardinality\":20}, {\"name\":\"UNKNOWN\",\"cardinality\":30}, {\"name\":\"WARNING\",\"cardinality\":40}, {\"name\":\"CRITICAL\",\"cardinality\":50}]", + message.toString()); + } + + @Test + void enumWithHeterogenousNodes() { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"enum\": [6, \"foo\", [], true, {\"foo\": 12}]\r\n" + + " }"; + String inputData = "{\r\n" + + " \"name\": \"FOO\",\r\n" + + " \"cardinality\": 50\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON).stream().collect(Collectors.toList()); + assertEquals(1, messages.size()); + Error message = messages.get(0); + assertEquals(": does not have a value in the enumeration [6, \"foo\", [], true, {\"foo\":12}]", message.toString()); + } +} diff --git a/src/test/java/com/networknt/schema/ErrorHandlerTest.java b/src/test/java/com/networknt/schema/ErrorHandlerTest.java new file mode 100644 index 000000000..7b1463d53 --- /dev/null +++ b/src/test/java/com/networknt/schema/ErrorHandlerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +/** + * ErrorHandlerTest. + */ +class ErrorHandlerTest { + @Test + void errorMessage() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\r\n" + + " \"foo\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false,\r\n" + + " \"errorMessage\": {\r\n" + + " \"type\": \"should be an object\",\r\n" + + " \"required\": \"should have property foo\",\r\n" + + " \"additionalProperties\": \"should not have properties other than foo\"\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"foo\": \"a\",\r\n" + + " \"bar\": 2\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().errorMessageKeyword("errorMessage").build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON).stream().collect(Collectors.toList()); + assertFalse(messages.isEmpty()); + assertEquals("/foo", messages.get(0).getInstanceLocation().toString()); + assertEquals("should be an object", messages.get(0).getMessage()); + assertEquals("", messages.get(1).getInstanceLocation().toString()); + assertEquals("should not have properties other than foo", messages.get(1).getMessage()); + } + + @Test + void errorMessageUnionType() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"keyword1\": {\r\n" + + " \"type\": [\r\n" + + " \"string\",\r\n" + + " \"null\"\r\n" + + " ],\r\n" + + " \"errorMessage\": {\r\n" + + " \"type\": \"关键字1必须为字符串\"\r\n" + + " },\r\n" + + " \"title\": \"关键字\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"keyword1\": 2\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().errorMessageKeyword("errorMessage").build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON).stream().collect(Collectors.toList()); + assertFalse(messages.isEmpty()); + assertEquals("/keyword1", messages.get(0).getInstanceLocation().toString()); + assertEquals("关键字1必须为字符串", messages.get(0).getMessage()); + } + +} diff --git a/src/test/java/com/networknt/schema/ErrorTest.java b/src/test/java/com/networknt/schema/ErrorTest.java new file mode 100644 index 000000000..a24746c4f --- /dev/null +++ b/src/test/java/com/networknt/schema/ErrorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * ErrorTest. + */ +class ErrorTest { + @Test + void testSerialization() throws JsonProcessingException { + String value = JsonMapperFactory.getInstance() + .writeValueAsString(Error.builder().messageSupplier(() -> "hello") + .schemaLocation(SchemaLocation.of("https://www.example.com/#defs/definition")).build()); + assertEquals("{\"message\":\"hello\",\"schemaLocation\":\"https://www.example.com/#defs/definition\"}", value); + } +} diff --git a/src/test/java/com/networknt/schema/ExampleTest.java b/src/test/java/com/networknt/schema/ExampleTest.java new file mode 100644 index 000000000..c12d7014a --- /dev/null +++ b/src/test/java/com/networknt/schema/ExampleTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.DialectId; + +class ExampleTest { + @Test + void exampleSchemaLocation() { + // This creates a schema factory that will use Draft 2012-12 as the default if $schema is not specified in the initial schema + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> + builder.schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("https://www.example.org/", "classpath:schema/")) + ); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.example.org/example-main.json")); + String input = "{\r\n" + + " \"DriverProperties\": {\r\n" + + " \"CommonProperties\": {\r\n" + + " \"field2\": \"abc-def-xyz\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + // The example-main.json schema defines $schema with Draft 07 + assertEquals(DialectId.DRAFT_7, schema.getSchemaContext().getDialect().getId()); + List assertions = schema.validate(input, InputFormat.JSON); + assertEquals(1, assertions.size()); + + // The example-ref.json schema defines $schema with Draft 2019-09 + Schema refSchema = schema.getSchemaContext().getSchemaResources().get("https://www.example.org/example-ref.json#"); + assertEquals(DialectId.DRAFT_2019_09, refSchema.getSchemaContext().getDialect().getId()); + } + + @Test + void exampleClasspath() { + // This creates a schema factory that will use Draft 2012-12 as the default if $schema is not specified in the initial schema + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of("classpath:schema/example-main.json")); + String input = "{\r\n" + + " \"DriverProperties\": {\r\n" + + " \"CommonProperties\": {\r\n" + + " \"field2\": \"abc-def-xyz\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + // The example-main.json schema defines $schema with Draft 07 + assertEquals(DialectId.DRAFT_7, schema.getSchemaContext().getDialect().getId()); + List assertions = schema.validate(input, InputFormat.JSON); + assertEquals(1, assertions.size()); + + // The example-ref.json schema defines $schema with Draft 2019-09 + Schema refSchema = schema.getSchemaContext().getSchemaResources().get("classpath:schema/example-ref.json#"); + assertEquals(DialectId.DRAFT_2019_09, refSchema.getSchemaContext().getDialect().getId()); + } +} diff --git a/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java b/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java new file mode 100644 index 000000000..48e17dfff --- /dev/null +++ b/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.DisallowUnknownKeywordFactory; + +/** + * Test ExclusiveMinimumValidator validator. + */ +class ExclusiveMinimumValidatorTest { + @Test + void draftV4ShouldHaveExclusiveMinimum() { + String schemaData = "{" + + " \"type\": \"object\"," + + " \"properties\": {" + + " \"value1\": {" + + " \"type\": \"number\"," + + " \"minimum\": 0," + + " \"exclusiveMinimum\": true" + + " }" + + " }" + + "}"; + Dialect dialect = Dialect.builder(Dialects.getDraft4()) + .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\"value1\":0}"; + String validData = "{\"value1\":0.1}"; + + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals(1, messages.stream().filter(m -> "minimum".equals(m.getKeyword())).count()); + + messages = schema.validate(validData, InputFormat.JSON); + assertEquals(0, messages.size()); + } + + @Test + void draftV6ShouldNotAllowExclusiveMinimumBoolean() { + String schemaData = "{" + + " \"type\": \"object\"," + + " \"properties\": {" + + " \"value1\": {" + + " \"type\": \"number\"," + + " \"minimum\": 0," + + " \"exclusiveMinimum\": true" + + " }" + + " }" + + "}"; + Dialect dialect = Dialect.builder(Dialects.getDraft6()) + .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + } + + @Test + void draftV7ShouldNotAllowExclusiveMinimumBoolean() { + String schemaData = "{" + + " \"type\": \"object\"," + + " \"properties\": {" + + " \"value1\": {" + + " \"type\": \"number\"," + + " \"minimum\": 0," + + " \"exclusiveMinimum\": true" + + " }" + + " }" + + "}"; + Dialect dialect = Dialect.builder(Dialects.getDraft7()) + .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + } +} diff --git a/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java b/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java new file mode 100644 index 000000000..cbdc6e260 --- /dev/null +++ b/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.Format; +import com.networknt.schema.keyword.FormatKeyword; +import com.networknt.schema.keyword.KeywordValidator; + +class FormatKeywordFactoryTest { + + public static class CustomFormatKeyword extends FormatKeyword { + public CustomFormatKeyword(Map formats) { + super(formats); + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + throw new IllegalArgumentException(); + } + } + + @Test + void shouldUseFormatKeyword() { + Dialect dialect = Dialect.builder(Dialects.getDraft202012()) + .formatKeywordFactory(CustomFormatKeyword::new).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + String schemaData = "{\r\n" + + " \"format\": \"hello\"\r\n" + + "}"; + assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + } +} diff --git a/src/test/java/com/networknt/schema/FormatValidatorTest.java b/src/test/java/com/networknt/schema/FormatValidatorTest.java new file mode 100644 index 000000000..37a80e6ca --- /dev/null +++ b/src/test/java/com/networknt/schema/FormatValidatorTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.Format; +import com.networknt.schema.output.OutputUnit; +import com.networknt.schema.utils.JsonType; +import com.networknt.schema.utils.TypeFactory; + +/** + * Test for format validator. + */ +class FormatValidatorTest { + @Test + void unknownFormatNoVocab() { + String schemaData = "{\r\n" + + " \"format\":\"unknown\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"hello\"", InputFormat.JSON, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertEquals(0, messages.size()); + } + + @Test + void unknownFormatNoVocabStrictTrue() { + String schemaData = "{\r\n" + + " \"format\":\"unknown\"\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().strict("format", true).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + List messages = schema.validate("\"hello\"", InputFormat.JSON, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertEquals(1, messages.size()); + assertEquals("format.unknown", messages.iterator().next().getMessageKey()); + } + + @Test + void unknownFormatAssertionsVocab() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/format-assertion/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/format-assertion\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + + String schemaData = "{\r\n" + + " \"$schema\": \"https://www.example.com/format-assertion/schema\",\r\n" + + " \"format\":\"unknown\"\r\n" + + "}"; + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder + .resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections.singletonMap("https://www.example.com/format-assertion/schema", metaSchemaData)))) + .getSchema(schemaData); + List messages = schema.validate("\"hello\"", InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals("format.unknown", messages.iterator().next().getMessageKey()); + } + + @Test + void unknownFormatShouldCollectAnnotations() { + String schemaData = "{\r\n" + + " \"format\":\"unknown\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + OutputUnit outputUnit = schema.validate("\"hello\"", InputFormat.JSON, OutputFormat.HIERARCHICAL, + executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + assertEquals("unknown", outputUnit.getAnnotations().get("format")); + assertTrue(outputUnit.isValid()); // as no assertion vocab and assertions not enabled + } + + enum FormatInput { + DATE_TIME("date-time"), + DATE("date"), + TIME("time"), + DURATION("duration"), + EMAIL("email"), + IDN_EMAIL("idn-email"), + HOSTNAME("hostname"), + IDN_HOSTNAME("idn-hostname"), + IPV4("ipv4"), + IPV6("ipv6"), + URI("uri"), + URI_REFERENCE("uri-reference"), + IRI("iri"), + IRI_REFERENCE("iri-reference"), + UUID("uuid"), + JSON_POINTER("json-pointer"), + RELATIVE_JSON_POINTER("relative-json-pointer"), + REGEX("regex"); + + String format; + + FormatInput(String format) { + this.format = format; + } + } + + @ParameterizedTest + @EnumSource(FormatInput.class) + void formatAssertions(FormatInput formatInput) { + String formatSchema = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"format\": \""+formatInput.format+"\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(formatSchema); + List messages = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertFalse(messages.isEmpty()); + } + + static class CustomNumberFormat implements Format { + private final BigDecimal compare; + + CustomNumberFormat(BigDecimal compare) { + this.compare = compare; + } + + @Override + public boolean matches(ExecutionContext executionContext, SchemaContext schemaContext, JsonNode value) { + JsonType nodeType = TypeFactory.getValueNodeType(value, schemaContext.getSchemaRegistryConfig()); + if (nodeType != JsonType.NUMBER && nodeType != JsonType.INTEGER) { + return true; + } + BigDecimal number = value.isBigDecimal() ? value.decimalValue() : BigDecimal.valueOf(value.doubleValue()); + number = new BigDecimal(number.toPlainString()); + return number.compareTo(compare) == 0; + } + + @Override + public String getName() { + return "custom-number"; + } + + @Override + public String getMessageKey() { + return "does not match the {0} pattern"; + } + } + + @Test + void shouldAllowNumberFormat() { + Dialect customDialect = Dialect + .builder("https://www.example.com/schema", Dialects.getDraft7()) + .formats(formats -> { + CustomNumberFormat format = new CustomNumberFormat(new BigDecimal("12345")); + formats.put(format.getName(), format); + }) + .build(); + + SchemaRegistry factory = SchemaRegistry.withDialect(customDialect); + String formatSchema = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"format\": \"custom-number\"\r\n" + + "}"; + Schema schema = factory.getSchema(formatSchema); + List messages = schema.validate("123451", InputFormat.JSON, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertFalse(messages.isEmpty()); + assertEquals(": does not match the custom-number pattern", messages.iterator().next().toString()); + messages = schema.validate("12345", InputFormat.JSON, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertTrue(messages.isEmpty()); + + } + + @Test + void draft7DisableFormat() { + String schemaData = "{\r\n" + + " \"format\":\"uri\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + List messages = schema.validate("\"hello\"", InputFormat.JSON, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(false)); + }); + assertEquals(0, messages.size()); + } +} diff --git a/src/test/java/com/networknt/schema/IfValidatorTest.java b/src/test/java/com/networknt/schema/IfValidatorTest.java new file mode 100644 index 000000000..c36b231d5 --- /dev/null +++ b/src/test/java/com/networknt/schema/IfValidatorTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +/** + * Test for IfValidator. + */ +class IfValidatorTest { + + @Test + void walkValidateThen() { + String schemaData = "{\r\n" + + " \"if\": {\r\n" + + " \"const\": \"false\"\r\n" + + " },\r\n" + + " \"then\": {\r\n" + + " \"type\": \"object\"\r\n" + + " },\r\n" + + " \"else\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + "}"; + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.TYPE.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List types = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("types", key -> new ArrayList()); + types.add(walkEvent); + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk("\"false\"", InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertFalse(result.getErrors().isEmpty()); + + List types = result.getExecutionContext().getCollectorContext().get("types"); + assertEquals(1, types.size()); + assertEquals("", types.get(0).getInstanceLocation().toString()); + assertEquals("/then", types.get(0).getEvaluationPath().toString()); + } + + @Test + void walkValidateElse() { + String schemaData = "{\r\n" + + " \"if\": {\r\n" + + " \"const\": \"false\"\r\n" + + " },\r\n" + + " \"then\": {\r\n" + + " \"type\": \"object\"\r\n" + + " },\r\n" + + " \"else\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + "}"; + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.TYPE.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List types = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("types", key -> new ArrayList()); + types.add(walkEvent); + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk("\"hello\"", InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertFalse(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List types = (List) result.getExecutionContext().getCollectorContext().get("types"); + assertEquals(1, types.size()); + assertEquals("", types.get(0).getInstanceLocation().toString()); + assertEquals("/else", types.get(0).getEvaluationPath().toString()); + } + + @Test + void walkValidateNull() { + String schemaData = "{\r\n" + + " \"if\": {\r\n" + + " \"const\": \"false\"\r\n" + + " },\r\n" + + " \"then\": {\r\n" + + " \"type\": \"object\"\r\n" + + " },\r\n" + + " \"else\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + "}"; + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.TYPE.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List types = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("types", key -> new ArrayList()); + types.add(walkEvent); + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List types = (List) result.getExecutionContext().getCollectorContext().get("types"); + assertEquals(2, types.size()); + } + + @Test + void walkNoValidate() { + String schemaData = "{\r\n" + + " \"if\": {\r\n" + + " \"const\": \"false\"\r\n" + + " },\r\n" + + " \"then\": {\r\n" + + " \"type\": \"object\"\r\n" + + " },\r\n" + + " \"else\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + "}"; + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.TYPE.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List types = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("types", key -> new ArrayList()); + types.add(walkEvent); + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk("\"false\"", InputFormat.JSON, false, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List types = (List) result.getExecutionContext().getCollectorContext().get("types"); + assertEquals(2, types.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue1091Test.java b/src/test/java/com/networknt/schema/Issue1091Test.java new file mode 100644 index 000000000..450d6e164 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue1091Test.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class Issue1091Test { + @Test + @Disabled // Disabled as this test takes quite long to run for ci + void testHasAdjacentKeywordInEvaluationPath() throws Exception { + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4) + .getSchema(SchemaLocation.of("classpath:schema/issue1091.json")); + List errors = schema.validate(AbsoluteIri.of("classpath:data/issue1091.json"), InputFormat.JSON); + assertEquals(0, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue255Test.java b/src/test/java/com/networknt/schema/Issue255Test.java index e79b4032e..dc6dbba81 100644 --- a/src/test/java/com/networknt/schema/Issue255Test.java +++ b/src/test/java/com/networknt/schema/Issue255Test.java @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue255Test { - protected JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue255Test { + protected Schema getJsonSchemaFromStreamContent(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -36,14 +36,14 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldFailWhenRequiredPropertiesDoNotExistInReferencedSubSchema() throws Exception { + void shouldFailWhenRequiredPropertiesDoNotExistInReferencedSubSchema() throws Exception { String schemaPath = "/draft2019-09/issue255.json"; String dataPath = "/data/issue255.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContent(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(2, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/Issue285Test.java b/src/test/java/com/networknt/schema/Issue285Test.java index 846126611..ca2a87ce8 100644 --- a/src/test/java/com/networknt/schema/Issue285Test.java +++ b/src/test/java/com/networknt/schema/Issue285Test.java @@ -1,20 +1,23 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; -import java.util.Set; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; -public class Issue285Test { - private ObjectMapper mapper = new ObjectMapper(); - private JsonSchemaFactory schemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).objectMapper(mapper).build(); +class Issue285Test { + private final ObjectMapper mapper = new ObjectMapper(); + private final SchemaRegistry schemaFactory = SchemaRegistry + .builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers + .mapPrefix("http://json-schema.org", "resource:") + .mapPrefix("https://json-schema.org", "resource:")) + .build(); String schemaStr = "{\n" + @@ -50,15 +53,15 @@ public class Issue285Test { // This checks the that the validation checks the type of the nested attribute. // In this case the "lastName" should be a string. - // The result is as expected and we get an validation error. + // The result is as expected, and we get a validation error. @Test - public void nestedValidation() throws IOException { - JsonSchema jsonSchema = schemaFactory.getSchema(schemaStr); - Set validationMessages = jsonSchema.validate(mapper.readTree(person)); + void nestedValidation() throws IOException { + Schema jsonSchema = schemaFactory.getSchema(schemaStr); + List errors = jsonSchema.validate(mapper.readTree(person)); - System.err.println("\n" + Arrays.toString(validationMessages.toArray())); + System.err.println("\n" + Arrays.toString(errors.toArray())); - assertFalse(validationMessages.isEmpty()); + assertFalse(errors.isEmpty()); } @@ -90,17 +93,16 @@ public void nestedValidation() throws IOException { // This checks the that the validation checks the type of the nested attribute. // Based on the meta-schema found on https://json-schema.org/draft/2019-09/schema. // In this case a nested type declaration isn't valid and should raise an error. - // The result is not as expected and we get no validation error. + // The result is not as expected, and we get no validation error. @Test - @Disabled - public void nestedTypeValidation() throws IOException, URISyntaxException { - URI uri = new URI("https://json-schema.org/draft/2019-09/schema"); - JsonSchema jsonSchema = schemaFactory.getSchema(uri); - Set validationMessages = jsonSchema.validate(mapper.readTree(invalidNestedSchema)); + void nestedTypeValidation() throws IOException { + SchemaLocation uri = SchemaLocation.of("https://json-schema.org/draft/2019-09/schema"); + Schema jsonSchema = schemaFactory.getSchema(uri); + List errors = jsonSchema.validate(mapper.readTree(invalidNestedSchema)); - System.err.println("\n" + Arrays.toString(validationMessages.toArray())); + System.err.println("\n" + Arrays.toString(errors.toArray())); - assertFalse(validationMessages.isEmpty()); + assertFalse(errors.isEmpty()); } String invalidSchema = "{\n" + @@ -114,15 +116,15 @@ public void nestedTypeValidation() throws IOException, URISyntaxException { // This checks the that the validation checks the type of the JSON. // Based on the meta-schema found on https://json-schema.org/draft/2019-09/schema. // In this case the toplevel type declaration isn't valid and should raise an error. - // The result is as expected and we get no validation error: '[$.type: does not have a value in the enumeration [array, boolean, integer, null, number, object, string], $.type: should be valid to any of the schemas array]'. + // The result is as expected, and we get no validation error: '[$.type: does not have a value in the enumeration [array, boolean, integer, null, number, object, string], $.type: should be valid to any of the schemas array]'. @Test - public void typeValidation() throws IOException, URISyntaxException { - URI uri = new URI("https://json-schema.org/draft/2019-09/schema"); - JsonSchema jsonSchema = schemaFactory.getSchema(uri); - Set validationMessages = jsonSchema.validate(mapper.readTree(invalidSchema)); + void typeValidation() throws IOException { + SchemaLocation uri = SchemaLocation.of("https://json-schema.org/draft/2019-09/schema"); + Schema jsonSchema = schemaFactory.getSchema(uri); + List errors = jsonSchema.validate(mapper.readTree(invalidSchema)); - System.err.println("\n" + Arrays.toString(validationMessages.toArray())); + System.err.println("\n" + Arrays.toString(errors.toArray())); - assertFalse(validationMessages.isEmpty()); + assertFalse(errors.isEmpty()); } } diff --git a/src/test/java/com/networknt/schema/Issue295Test.java b/src/test/java/com/networknt/schema/Issue295Test.java index f59627750..27e6d9a36 100644 --- a/src/test/java/com/networknt/schema/Issue295Test.java +++ b/src/test/java/com/networknt/schema/Issue295Test.java @@ -6,11 +6,11 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue295Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue295Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -21,14 +21,14 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldWorkV7() throws Exception { + void shouldWorkV7() throws Exception { String schemaPath = "/schema/issue295-v7.json"; String dataPath = "/data/issue295.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/Issue313Test.java b/src/test/java/com/networknt/schema/Issue313Test.java index 867e85edf..430f9d44b 100644 --- a/src/test/java/com/networknt/schema/Issue313Test.java +++ b/src/test/java/com/networknt/schema/Issue313Test.java @@ -7,16 +7,16 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue313Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue313Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } - protected JsonSchema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); + protected Schema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); return factory.getSchema(schemaContent); } @@ -28,26 +28,26 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce @Test @Disabled - public void shouldFailV201909() throws Exception { + void shouldFailV201909() throws Exception { String schemaPath = "/schema/issue313-2019-09.json"; String dataPath = "/data/issue313.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV201909(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV201909(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(2, errors.size()); } @Test - public void shouldFailV7() throws Exception { + void shouldFailV7() throws Exception { String schemaPath = "/schema/issue313-v7.json"; String dataPath = "/data/issue313.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(2, errors.size()); } diff --git a/src/test/java/com/networknt/schema/Issue314Test.java b/src/test/java/com/networknt/schema/Issue314Test.java index 5260d3b89..44bdfd1d3 100644 --- a/src/test/java/com/networknt/schema/Issue314Test.java +++ b/src/test/java/com/networknt/schema/Issue314Test.java @@ -4,22 +4,22 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class Issue314Test { - private static final JsonSchemaFactory FACTORY = - JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) - .addMetaSchema( - JsonMetaSchema.builder( - "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0", - JsonMetaSchema.getV7()) - .build()) - .forceHttps(false) - .build(); +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; + +class Issue314Test { + private static final SchemaRegistry REGISTRY = + SchemaRegistry.withDialect( + Dialect.builder( + "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", + Dialects.getDraft7()) + .build()); @Test - public void testNormalizeHttpOnly() { + void testNormalizeHttpOnly() { String schemaPath = "/schema/issue314-v7.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = FACTORY.getSchema(schemaInputStream); + Schema schema = REGISTRY.getSchema(schemaInputStream); Assertions.assertNotNull(schema); } diff --git a/src/test/java/com/networknt/schema/Issue327Test.java b/src/test/java/com/networknt/schema/Issue327Test.java index e2fe17fe0..2c80e418e 100644 --- a/src/test/java/com/networknt/schema/Issue327Test.java +++ b/src/test/java/com/networknt/schema/Issue327Test.java @@ -6,11 +6,11 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue327Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue327Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -21,14 +21,14 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldWorkV7() throws Exception { + void shouldWorkV7() throws Exception { String schemaPath = "/schema/issue327-v7.json"; String dataPath = "/data/issue327.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/Issue342Test.java b/src/test/java/com/networknt/schema/Issue342Test.java index 9a921cfb8..94ef7e0a5 100644 --- a/src/test/java/com/networknt/schema/Issue342Test.java +++ b/src/test/java/com/networknt/schema/Issue342Test.java @@ -1,7 +1,7 @@ package com.networknt.schema; import java.io.InputStream; -import java.util.Set; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -9,9 +9,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -public class Issue342Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue342Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -22,17 +22,17 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void propertyNameEnumShouldFailV7() throws Exception { + void propertyNameEnumShouldFailV7() throws Exception { String schemaPath = "/schema/issue342-v7.json"; String dataPath = "/data/issue342.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(1, errors.size()); - final ValidationMessage error = errors.iterator().next(); - Assertions.assertEquals("$.z", error.getPath()); - Assertions.assertEquals("Property name $.z is not valid for validation: does not have a value in the enumeration [a, b, c]", error.getMessage()); + final Error error = errors.iterator().next(); + Assertions.assertEquals("", error.getInstanceLocation().toString()); + Assertions.assertEquals(": property 'z' name is not valid: does not have a value in the enumeration [\"a\", \"b\", \"c\"]", error.toString()); } } diff --git a/src/test/java/com/networknt/schema/Issue347Test.java b/src/test/java/com/networknt/schema/Issue347Test.java index 52fef1ae4..649b82657 100644 --- a/src/test/java/com/networknt/schema/Issue347Test.java +++ b/src/test/java/com/networknt/schema/Issue347Test.java @@ -1,22 +1,23 @@ package com.networknt.schema; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; -public class Issue347Test { +class Issue347Test { @Test - public void failure() { - ObjectMapper mapper = new ObjectMapper(); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + void failure() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + assertThrows(SchemaException.class, () -> factory.getSchema(Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/issue347-v7.json"))); try { - JsonSchema schema = factory.getSchema(Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/issue347-v7.json")); + factory.getSchema(Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/issue347-v7.json")); } catch (Throwable e) { - assertThat(e, instanceOf(JsonSchemaException.class)); - assertEquals("test: null is an invalid segment for URI {2}", e.getMessage()); + assertThat(e, instanceOf(SchemaException.class)); + assertEquals("/$id: 'test' is not a valid $id", ((SchemaException) e).getError().toString()); } } } diff --git a/src/test/java/com/networknt/schema/Issue366FailFastTest.java b/src/test/java/com/networknt/schema/Issue366FailFastTest.java index 051159765..7c41e4693 100644 --- a/src/test/java/com/networknt/schema/Issue366FailFastTest.java +++ b/src/test/java/com/networknt/schema/Issue366FailFastTest.java @@ -1,106 +1,96 @@ package com.networknt.schema; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class Issue366FailFastTest { - - @BeforeEach - public void setup() throws IOException { - setupSchema(); - } - - JsonSchema jsonSchema; - ObjectMapper objectMapper = new ObjectMapper(); - private void setupSchema() throws IOException { - - SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - schemaValidatorsConfig.setFailFast(true); - JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) - .objectMapper(objectMapper) - .build(); - - schemaValidatorsConfig.setTypeLoose(false); - - URI uri = getSchema(); - - InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); - JsonNode testCases = objectMapper.readValue(in, JsonNode.class); - this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig); - } - - protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(content); - return node; - } - - @Test - public void firstOneValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(0); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(errors.isEmpty()); - } - - @Test - public void secondOneValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(1); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(errors.isEmpty()); - } - - @Test - public void bothValid() throws Exception { - String dataPath = "/data/issue366.json"; - - assertThrows(JsonSchemaException.class, () -> { +class Issue366FailFastTest { + + @BeforeEach + void setup() throws IOException { + setupSchema(); + } + + Schema jsonSchema; + ObjectMapper objectMapper = new ObjectMapper(); + + private void setupSchema() throws IOException { + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder() + .failFast(true) + .typeLoose(false) + .build(); + SchemaRegistry schemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(schemaValidatorsConfig)); + SchemaLocation uri = getSchema(); + + InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); + JsonNode testCases = objectMapper.readValue(in, JsonNode.class); + this.jsonSchema = schemaFactory.getSchema(uri, testCases); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + void firstOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(0); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + void secondOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(1); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + void bothValid() throws Exception { + String dataPath = "/data/issue366.json"; + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); List testNodes = node.findValues("tests"); JsonNode testNode = testNodes.get(0).get(2); JsonNode dataNode = testNode.get("data"); - jsonSchema.validate(dataNode); - }); - } + assertEquals(1, jsonSchema.validate(dataNode).size()); + } - @Test - public void neitherValid() throws Exception { - String dataPath = "/data/issue366.json"; + @Test + void neitherValid() throws Exception { + String dataPath = "/data/issue366.json"; - assertThrows(JsonSchemaException.class, () -> { InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); List testNodes = node.findValues("tests"); JsonNode testNode = testNodes.get(0).get(3); JsonNode dataNode = testNode.get("data"); - jsonSchema.validate(dataNode); - }); - } + assertEquals(1, jsonSchema.validate(dataNode).size()); + } - private URI getSchema() { - return URI.create("classpath:" + "/draft7/issue366_schema.json"); - } + private SchemaLocation getSchema() { + return SchemaLocation.of("classpath:" + "/draft7/issue366_schema.json"); + } } diff --git a/src/test/java/com/networknt/schema/Issue366FailSlowTest.java b/src/test/java/com/networknt/schema/Issue366FailSlowTest.java index 3c0d27fae..bd00d2558 100644 --- a/src/test/java/com/networknt/schema/Issue366FailSlowTest.java +++ b/src/test/java/com/networknt/schema/Issue366FailSlowTest.java @@ -1,105 +1,100 @@ package com.networknt.schema; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class Issue366FailSlowTest { - - @BeforeEach - public void setup() throws IOException { - setupSchema(); - } - - JsonSchema jsonSchema; - ObjectMapper objectMapper = new ObjectMapper(); - private void setupSchema() throws IOException { - - SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) - .objectMapper(objectMapper) - .build(); - - schemaValidatorsConfig.setTypeLoose(false); - - URI uri = getSchema(); - - InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); - JsonNode testCases = objectMapper.readValue(in, JsonNode.class); - this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig); - } - - protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(content); - return node; - } - - @Test - public void firstOneValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(0); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(errors.isEmpty()); - } - - @Test - public void secondOneValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(1); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(errors.isEmpty()); - } - - @Test - public void bothValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(2); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(!errors.isEmpty()); - assertEquals(errors.size(),1); - } - - @Test - public void neitherValid() throws Exception { - String dataPath = "/data/issue366.json"; - - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - List testNodes = node.findValues("tests"); - JsonNode testNode = testNodes.get(0).get(3); - JsonNode dataNode = testNode.get("data"); - Set errors = jsonSchema.validate(dataNode); - assertTrue(!errors.isEmpty()); - assertEquals(errors.size(),2); - } - - private URI getSchema() { - return URI.create("classpath:" + "/draft7/issue366_schema.json"); - } +class Issue366FailSlowTest { + + @BeforeEach + void setup() throws IOException { + setupSchema(); + } + + Schema jsonSchema; + ObjectMapper objectMapper = new ObjectMapper(); + + private void setupSchema() throws IOException { + + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().typeLoose(false).build(); + SchemaRegistry schemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(schemaValidatorsConfig)); + + SchemaLocation uri = getSchema(); + + InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); + JsonNode testCases = objectMapper.readValue(in, JsonNode.class); + this.jsonSchema = schemaFactory.getSchema(uri, testCases); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + void firstOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(0); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + void secondOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(1); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + void bothValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(2); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertFalse(errors.isEmpty()); + assertEquals(errors.size(), 1); + } + + @Test + void neitherValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(3); + JsonNode dataNode = testNode.get("data"); + List errors = jsonSchema.validate(dataNode); + assertFalse(errors.isEmpty()); + assertEquals(errors.size(), 3); + } + + private SchemaLocation getSchema() { + return SchemaLocation.of("classpath:" + "/draft7/issue366_schema.json"); + } } diff --git a/src/test/java/com/networknt/schema/Issue375Test.java b/src/test/java/com/networknt/schema/Issue375Test.java index 86ccd0a1c..4974513f3 100644 --- a/src/test/java/com/networknt/schema/Issue375Test.java +++ b/src/test/java/com/networknt/schema/Issue375Test.java @@ -17,19 +17,17 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.SpecVersion.VersionFlag; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Set; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -public class Issue375Test { - protected JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909); +class Issue375Test { + protected Schema getJsonSchemaFromStreamContent(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); return factory.getSchema(schemaContent); } @@ -39,23 +37,23 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldFailAndShowValidationValuesWithError() throws Exception { + void shouldFailAndShowValidationValuesWithError() throws Exception { String schemaPath = "/draft2019-09/issue375.json"; String dataPath = "/data/issue375.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContent(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); List errorMessages = new ArrayList(); - for (ValidationMessage error: errors) { - errorMessages.add(error.getMessage()); + for (Error error: errors) { + errorMessages.add(error.toString()); } List expectedMessages = Arrays.asList( - "Property name $.fields.longName123 is not valid for validation: may only be 5 characters long", - "Property name $.fields.longName123 is not valid for validation: does not match the regex pattern ^[a-zA-Z]+$", - "Property name $.fields.a is not valid for validation: must be at least 3 characters long"); + "/fields: property 'longName123' name is not valid: must be at most 5 characters long", + "/fields: property 'longName123' name is not valid: does not match the regex pattern ^[a-zA-Z]+$", + "/fields: property 'a' name is not valid: must be at least 3 characters long"); MatcherAssert.assertThat(errorMessages, Matchers.containsInAnyOrder(expectedMessages.toArray())); } } diff --git a/src/test/java/com/networknt/schema/Issue383Test.java b/src/test/java/com/networknt/schema/Issue383Test.java index fc4dd876b..3fdf85f32 100644 --- a/src/test/java/com/networknt/schema/Issue383Test.java +++ b/src/test/java/com/networknt/schema/Issue383Test.java @@ -1,7 +1,7 @@ package com.networknt.schema; import java.io.InputStream; -import java.util.Set; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -9,9 +9,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -public class Issue383Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue383Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -22,14 +22,14 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void nestedOneOfsShouldStillMatchV7() throws Exception { + void nestedOneOfsShouldStillMatchV7() throws Exception { String schemaPath = "/schema/issue383-v7.json"; String dataPath = "/data/issue383.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/Issue386Test.java b/src/test/java/com/networknt/schema/Issue386Test.java deleted file mode 100644 index 8f7bebfdf..000000000 --- a/src/test/java/com/networknt/schema/Issue386Test.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.networknt.schema; - -import java.io.InputStream; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class Issue386Test { - protected JsonSchema getJsonSchemaFromPathV7(String schemaPath, boolean failFast) { - InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setFailFast(failFast); - return factory.getSchema(schemaInputStream, config); - } - - protected JsonNode getJsonNodeFromPath(String dataPath) throws Exception { - InputStream dataInputStream = getClass().getResourceAsStream(dataPath); - ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(dataInputStream); - return node; - } - - @ParameterizedTest - @ValueSource(booleans = { true, false } ) - public void dataIsValid(boolean failFast) throws Exception { - String schemaPath = "/schema/issue386-v7.json"; - String dataPath = "/data/issue386.json"; - JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, failFast); - JsonNode node = getJsonNodeFromPath(dataPath).get("valid"); - node.forEach(testNode -> { - Set errors = schema.validate(testNode.get("data")); - Assertions.assertEquals(0, errors.size(), "Expected no errors for " + testNode.get("data")); - }); - } - - @Test - public void dataIsInvalidFailFast() throws Exception { - String schemaPath = "/schema/issue386-v7.json"; - String dataPath = "/data/issue386.json"; - JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, true); - JsonNode node = getJsonNodeFromPath(dataPath).get("invalid"); - node.forEach(testNode -> { - try { - schema.validate(testNode.get("data")); - Assertions.fail(); - } catch (JsonSchemaException e) { - Assertions.assertEquals(testNode.get("expectedErrors").get(0).asText(), e.getMessage()); - } - }); - } - - @Test - public void dataIsInvalidFailSlow() throws Exception { - String schemaPath = "/schema/issue386-v7.json"; - String dataPath = "/data/issue386.json"; - JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, false); - JsonNode node = getJsonNodeFromPath(dataPath).get("invalid"); - node.forEach(testNode -> { - Set errors = schema.validate(testNode.get("data")); - List errorMessages = errors.stream().map(x -> x.getMessage()).collect(Collectors.toList()); - testNode.get("expectedErrors").forEach(expectedError -> { - Assertions.assertTrue(errorMessages.contains(expectedError.asText())); - }); - }); - } -} diff --git a/src/test/java/com/networknt/schema/Issue396Test.java b/src/test/java/com/networknt/schema/Issue396Test.java index e9bff0bd9..e625cdde9 100644 --- a/src/test/java/com/networknt/schema/Issue396Test.java +++ b/src/test/java/com/networknt/schema/Issue396Test.java @@ -2,6 +2,7 @@ import java.io.InputStream; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -11,9 +12,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -public class Issue396Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue396Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -24,22 +25,22 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void testComplexPropertyNamesV7() throws Exception { + void testComplexPropertyNamesV7() throws Exception { String schemaPath = "/schema/issue396-v7.json"; String dataPath = "/data/issue396.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - final Set invalidPaths = new HashSet<>(); + final Set expected = new HashSet<>(); node.fields().forEachRemaining(entry -> { if (!entry.getValue().asBoolean()) - invalidPaths.add("$." + entry.getKey()); + expected.add(entry.getKey()); }); - Set errors = schema.validate(node); - final Set failedPaths = errors.stream().map(ValidationMessage::getPath).collect(Collectors.toSet()); - Assertions.assertEquals(failedPaths, invalidPaths); + List errors = schema.validate(node); + final Set actual = errors.stream().map(Error::getProperty).map(Object::toString).collect(Collectors.toSet()); + Assertions.assertEquals(expected, actual); } } diff --git a/src/test/java/com/networknt/schema/Issue404Test.java b/src/test/java/com/networknt/schema/Issue404Test.java index 07b306892..f9b560ebe 100644 --- a/src/test/java/com/networknt/schema/Issue404Test.java +++ b/src/test/java/com/networknt/schema/Issue404Test.java @@ -2,15 +2,15 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue404Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue404Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -21,15 +21,19 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void expectObjectNotIntegerV7() throws Exception { + void expectObjectNotIntegerV7() throws Exception { String schemaPath = "/schema/issue404-v7.json"; String dataPath = "/data/issue404.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); - Assertions.assertEquals(0, errors.size()); + List errors = schema.validate(node); + assertEquals(1, errors.size()); + assertEquals("type", errors.get(0).getKeyword()); + assertEquals("/bar", errors.get(0).getInstanceLocation().toString()); + assertEquals("/properties/bar/$ref/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("https://example.com/address.schema.json#/properties/foo/type", errors.get(0).getSchemaLocation().toString()); } } diff --git a/src/test/java/com/networknt/schema/Issue406Test.java b/src/test/java/com/networknt/schema/Issue406Test.java index b4f0533e0..36273254e 100644 --- a/src/test/java/com/networknt/schema/Issue406Test.java +++ b/src/test/java/com/networknt/schema/Issue406Test.java @@ -7,24 +7,24 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -public class Issue406Test { +class Issue406Test { protected static final String INVALID_$REF_SCHEMA = "{\"$ref\":\"urn:unresolved\"}"; protected static final String CIRCULAR_$REF_SCHEMA = "{\"$ref\":\"#/nestedSchema\"," + "\"nestedSchema\":{\"$ref\":\"#/nestedSchema\"}}"; @Test - public void testPreloadingNotHappening() { - final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - final JsonSchema schema = factory.getSchema(INVALID_$REF_SCHEMA); + void testPreloadingNotHappening() { + final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + final Schema schema = factory.getSchema(INVALID_$REF_SCHEMA); // not breaking - pass Assertions.assertNotNull(schema); } @Test - public void testPreloadingHappening() { - final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - final JsonSchema schema = factory.getSchema(INVALID_$REF_SCHEMA); - Assertions.assertThrows(JsonSchemaException.class, + void testPreloadingHappening() { + final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + final Schema schema = factory.getSchema(INVALID_$REF_SCHEMA); + Assertions.assertThrows(SchemaException.class, new Executable() { @Override public void execute() { @@ -35,9 +35,9 @@ public void execute() { } @Test - public void testPreloadingHappeningForCircularDependency() { - final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - final JsonSchema schema = factory.getSchema(CIRCULAR_$REF_SCHEMA); + void testPreloadingHappeningForCircularDependency() { + final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + final Schema schema = factory.getSchema(CIRCULAR_$REF_SCHEMA); schema.initializeValidators(); } } diff --git a/src/test/java/com/networknt/schema/Issue425Test.java b/src/test/java/com/networknt/schema/Issue425Test.java deleted file mode 100644 index 878f66fb0..000000000 --- a/src/test/java/com/networknt/schema/Issue425Test.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import io.undertow.Undertow; -import io.undertow.server.handlers.resource.FileResourceManager; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.InputStream; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -import static io.undertow.Handlers.resource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -public class Issue425Test { - protected ObjectMapper mapper = new ObjectMapper(); - protected JsonSchemaFactory validatorFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build(); - protected static Undertow server = null; - - public Issue425Test() { - } - - @BeforeAll - public static void setUp() { - if (server == null) { - server = Undertow.builder() - .addHttpListener(1234, "localhost") - .setHandler(resource(new FileResourceManager( - new File("./src/test/resources/remotes"), 100))) - .build(); - server.start(); - } - } - - @AfterAll - public static void tearDown() throws Exception { - if (server != null) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - - } - server.stop(); - } - } - - private void runTestFile(String testCaseFile) throws Exception { - final URI testCaseFileUri = URI.create("classpath:" + testCaseFile); - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(testCaseFile); - ArrayNode testCases = mapper.readValue(in, ArrayNode.class); - - for (int j = 0; j < testCases.size(); j++) { - try { - JsonNode testCase = testCases.get(j); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - - ArrayNode testNodes = (ArrayNode) testCase.get("tests"); - for (int i = 0; i < testNodes.size(); i++) { - JsonNode test = testNodes.get(i); - System.out.println("=== " + test.get("description")); - JsonNode node = test.get("data"); - JsonNode typeLooseNode = test.get("isTypeLoose"); - // Configure the schemaValidator to set typeLoose's value based on the test file, - // if test file do not contains typeLoose flag, use default value: true. - config.setTypeLoose(typeLooseNode != null && typeLooseNode.asBoolean()); - config.setOpenAPI3StyleDiscriminators(false); - JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config); - - List errors = new ArrayList(schema.validate(node)); - - if (test.get("valid").asBoolean()) { - if (!errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema.toString()); - System.out.println("data: " + test.get("data")); - System.out.println("errors:"); - for (ValidationMessage error : errors) { - System.out.println(error); - } - } - if(test.get("data").get("values").asText().equals("3")) - assertEquals(2, errors.size()); - } else { - if (errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); - } else { - JsonNode errorCount = test.get("errorCount"); - if (errorCount != null && errorCount.isInt() && errors.size() != errorCount.asInt()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); - System.out.println("errors: " + errors); - for (ValidationMessage error : errors) { - System.out.println(error); - } - assertEquals(errorCount.asInt(), errors.size(), "expected error count"); - } - } - assertFalse(errors.isEmpty()); - } - - } - - - } catch (JsonSchemaException e) { - throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e); - } - } - } - - @Test - public void testNullableOneOf() throws Exception { - runTestFile("data/issue425.json"); - } -} diff --git a/src/test/java/com/networknt/schema/Issue426Test.java b/src/test/java/com/networknt/schema/Issue426Test.java index 07c76c393..a3b27e0fa 100644 --- a/src/test/java/com/networknt/schema/Issue426Test.java +++ b/src/test/java/com/networknt/schema/Issue426Test.java @@ -6,14 +6,15 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; /** * Validating custom message */ -public class Issue426Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); +class Issue426Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().errorMessageKeyword("message").build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); return factory.getSchema(schemaContent); } @@ -23,19 +24,19 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldWorkV7() throws Exception { + void shouldWorkV7() throws Exception { String schemaPath = "/schema/issue426-v7.json"; String dataPath = "/data/issue426.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(2, errors.size()); final JsonNode message = schema.schemaNode.get("message"); - for(ValidationMessage error : errors) { + for(Error error : errors) { //validating custom message - Assertions.assertEquals(message.get(error.getType()).asText(), error.getMessage()); + Assertions.assertEquals(message.get(error.getKeyword()).asText(), error.getMessage()); } } } diff --git a/src/test/java/com/networknt/schema/Issue428Test.java b/src/test/java/com/networknt/schema/Issue428Test.java index 4f3ed8f59..166ddc96c 100644 --- a/src/test/java/com/networknt/schema/Issue428Test.java +++ b/src/test/java/com/networknt/schema/Issue428Test.java @@ -3,58 +3,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; -import io.undertow.Undertow; -import io.undertow.server.handlers.resource.FileResourceManager; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.io.File; import java.io.InputStream; -import java.net.URI; import java.util.ArrayList; import java.util.List; -import static io.undertow.Handlers.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -public class Issue428Test { +class Issue428Test { protected ObjectMapper mapper = new ObjectMapper(); - protected JsonSchemaFactory validatorFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build(); - protected static Undertow server = null; - - public Issue428Test() { - } - - @BeforeAll - public static void setUp() { - if (server == null) { - server = Undertow.builder() - .addHttpListener(1234, "localhost") - .setHandler(resource(new FileResourceManager( - new File("./src/test/resources/remotes"), 100))) - .build(); - server.start(); - } - } - - @AfterAll - public static void tearDown() throws Exception { - if (server != null) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - - } - server.stop(); - } - } private void runTestFile(String testCaseFile) throws Exception { - final URI testCaseFileUri = URI.create("classpath:" + testCaseFile); + final SchemaLocation testCaseFileUri = SchemaLocation.of("classpath:" + testCaseFile); InputStream in = Thread.currentThread().getContextClassLoader() .getResourceAsStream(testCaseFile); ArrayNode testCases = mapper.readValue(in, ArrayNode.class); @@ -62,7 +24,6 @@ private void runTestFile(String testCaseFile) throws Exception { for (int j = 0; j < testCases.size(); j++) { try { JsonNode testCase = testCases.get(j); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); ArrayNode testNodes = (ArrayNode) testCase.get("tests"); for (int i = 0; i < testNodes.size(); i++) { @@ -72,19 +33,22 @@ private void runTestFile(String testCaseFile) throws Exception { JsonNode typeLooseNode = test.get("isTypeLoose"); // Configure the schemaValidator to set typeLoose's value based on the test file, // if test file do not contains typeLoose flag, use default value: true. - config.setTypeLoose(typeLooseNode != null && typeLooseNode.asBoolean()); - config.setOpenAPI3StyleDiscriminators(false); - JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config); + SchemaRegistryConfig.Builder configBuilder = SchemaRegistryConfig.builder(); + configBuilder.typeLoose(typeLooseNode != null && typeLooseNode.asBoolean()); + SchemaRegistryConfig config = configBuilder.build(); + + SchemaRegistry validatorFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); + Schema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema")); - List errors = new ArrayList(schema.validate(node)); + List errors = new ArrayList(schema.validate(node)); if (test.get("valid").asBoolean()) { if (!errors.isEmpty()) { System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema.toString()); + System.out.println("schema: " + schema); System.out.println("data: " + test.get("data")); System.out.println("errors:"); - for (ValidationMessage error : errors) { + for (Error error : errors) { System.out.println(error); } } @@ -101,7 +65,7 @@ private void runTestFile(String testCaseFile) throws Exception { System.out.println("schema: " + schema); System.out.println("data: " + test.get("data")); System.out.println("errors: " + errors); - for (ValidationMessage error : errors) { + for (Error error : errors) { System.out.println(error); } assertEquals(errorCount.asInt(), errors.size(), "expected error count"); @@ -113,14 +77,14 @@ private void runTestFile(String testCaseFile) throws Exception { } - } catch (JsonSchemaException e) { + } catch (SchemaException e) { throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e); } } } @Test - public void testNullableOneOf() throws Exception { + void testNullableOneOf() throws Exception { runTestFile("data/issue428.json"); } } diff --git a/src/test/java/com/networknt/schema/Issue451Test.java b/src/test/java/com/networknt/schema/Issue451Test.java index d7489fb13..229bfc478 100644 --- a/src/test/java/com/networknt/schema/Issue451Test.java +++ b/src/test/java/com/networknt/schema/Issue451Test.java @@ -2,31 +2,30 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.walk.JsonSchemaWalkListener; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.PropertyWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; import com.networknt.schema.walk.WalkEvent; import com.networknt.schema.walk.WalkFlow; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.InputStream; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; /** * Validating anyOf walker */ -public class Issue451Test { +class Issue451Test { private static final String COLLECTOR_ID = "collector-451"; + + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - SchemaValidatorsConfig svc = new SchemaValidatorsConfig(); - svc.addPropertyWalkListener(new CountingWalker()); - return factory.getSchema(schemaContent, svc); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + return factory.getSchema(schemaContent); } protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { @@ -34,38 +33,20 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce return mapper.readTree(content); } - - @BeforeAll - public static void beforeAll() { - reset(); - } - - @AfterEach - public void cleanup() { - reset(); - } - - - private static void reset() { - if (CollectorContext.getInstance() != null) { - CollectorContext.getInstance().reset(); - } - } - @Test - public void shouldWalkAnyOfProperties() { + void shouldWalkAnyOfProperties() { walk(null, false); } @Test - public void shouldWalkAnyOfPropertiesWithWithPayloadAndValidation() throws Exception { + void shouldWalkAnyOfPropertiesWithWithPayloadAndValidation() throws Exception { JsonNode data = getJsonNodeFromStreamContent(Issue451Test.class.getResourceAsStream( "/data/issue451.json")); walk(data,true); } @Test - public void shouldWalkAnyOfPropertiesWithWithPayload() throws Exception { + void shouldWalkAnyOfPropertiesWithWithPayload() throws Exception { JsonNode data = getJsonNodeFromStreamContent(Issue451Test.class.getResourceAsStream( "/data/issue451.json")); walk(data, false); @@ -75,34 +56,39 @@ public void shouldWalkAnyOfPropertiesWithWithPayload() throws Exception { private void walk(JsonNode data, boolean shouldValidate) { String schemaPath = "/schema/issue451-v7.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); - schema.walk(data, shouldValidate); + WalkConfig walkConfig = WalkConfig.builder().propertyWalkListenerRunner( + PropertyWalkListenerRunner.builder().propertyWalkListener(new CountingWalker()).build()).build(); + CollectorContext collectorContext = schema.walk(data, shouldValidate, executionContext -> executionContext.setWalkConfig(walkConfig)).getCollectorContext(); - Map collector = (Map) CollectorContext.getInstance().get(COLLECTOR_ID); - Assertions.assertEquals(2, collector.get("#/definitions/definition1/properties/a")); - Assertions.assertEquals(2, collector.get("#/definitions/definition2/properties/x")); + Map collector = (Map) collectorContext.get(COLLECTOR_ID); + Assertions.assertEquals(2, + collector.get("https://example.com/issue-451.json#/definitions/definition1/properties/a")); + Assertions.assertEquals(2, + collector.get("https://example.com/issue-451.json#/definitions/definition2/properties/x")); } - private static class CountingWalker implements JsonSchemaWalkListener { + private static class CountingWalker implements WalkListener { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - String path = walkEvent.getSchemaPath(); - collector().compute(path, (k, v) -> v == null ? 1 : v + 1); + SchemaLocation path = walkEvent.getSchema().getSchemaLocation(); + collector(walkEvent.getExecutionContext()).compute(path.toString(), (k, v) -> v == null ? 1 : v + 1); return WalkFlow.CONTINUE; } @Override - public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + public void onWalkEnd(WalkEvent walkEvent, List errors) { } - private Map collector() { - Map collector = (Map) CollectorContext.getInstance().get(COLLECTOR_ID); + private Map collector(ExecutionContext executionContext) { + @SuppressWarnings("unchecked") + Map collector = (Map) executionContext.getCollectorContext().get(COLLECTOR_ID); if(collector == null) { collector = new HashMap<>(); - CollectorContext.getInstance().add(COLLECTOR_ID, collector); + executionContext.getCollectorContext().put(COLLECTOR_ID, collector); } return collector; diff --git a/src/test/java/com/networknt/schema/Issue456Test.java b/src/test/java/com/networknt/schema/Issue456Test.java index c96c389d0..7dc2566be 100644 --- a/src/test/java/com/networknt/schema/Issue456Test.java +++ b/src/test/java/com/networknt/schema/Issue456Test.java @@ -6,12 +6,12 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.Set; +import java.util.List; -public class Issue456Test { +class Issue456Test { - protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); return factory.getSchema(schemaContent); } @@ -21,27 +21,27 @@ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exce } @Test - public void shouldWorkT2() throws Exception { + void shouldWorkT2() throws Exception { String schemaPath = "/schema/issue456-v7.json"; String dataPath = "/data/issue456-T2.json"; - String dataT3Path = "/data/issue456-T3.json"; +// String dataT3Path = "/data/issue456-T3.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } @Test - public void shouldWorkT3() throws Exception { + void shouldWorkT3() throws Exception { String schemaPath = "/schema/issue456-v7.json"; String dataPath = "/data/issue456-T3.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(0, errors.size()); } diff --git a/src/test/java/com/networknt/schema/Issue461Test.java b/src/test/java/com/networknt/schema/Issue461Test.java index 88a55443e..72cbee461 100644 --- a/src/test/java/com/networknt/schema/Issue461Test.java +++ b/src/test/java/com/networknt/schema/Issue461Test.java @@ -2,40 +2,46 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.walk.JsonSchemaWalkListener; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; import com.networknt.schema.walk.WalkEvent; import com.networknt.schema.walk.WalkFlow; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Set; - -public class Issue461Test { - protected ObjectMapper mapper = new ObjectMapper(); - - protected JsonSchema getJsonSchemaFromStreamContentV7(URI schemaUri) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - SchemaValidatorsConfig svc = new SchemaValidatorsConfig(); - svc.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new Walker()); - return factory.getSchema(schemaUri, svc); +import java.util.List; + +class Issue461Test { + protected ObjectMapper mapper = JsonMapperFactory.getInstance(); + + protected Schema getJsonSchemaFromStreamContentV7(SchemaLocation schemaUri) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + return factory.getSchema(schemaUri); } @Test - public void shouldWalkWithValidation() throws URISyntaxException, IOException { - JsonSchema schema = getJsonSchemaFromStreamContentV7(new URI("http://json-schema" + - ".org/draft-07/schema#")); + void shouldWalkWithValidation() throws IOException { + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new Walker()) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + + Schema schema = getJsonSchemaFromStreamContentV7(SchemaLocation.of("resource:/draft-07/schema#")); JsonNode data = mapper.readTree(Issue461Test.class.getResource("/data/issue461-v7.json")); - ValidationResult result = schema.walk(data, true); - Assertions.assertTrue(result.getValidationMessages().isEmpty()); + Result result = schema.walk(data, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + Assertions.assertTrue(result.getErrors().isEmpty()); } /** * Example NOP walker */ - private static class Walker implements JsonSchemaWalkListener { + private static class Walker implements WalkListener { @Override public WalkFlow onWalkStart(final WalkEvent walkEvent) { return WalkFlow.CONTINUE; @@ -43,7 +49,7 @@ public WalkFlow onWalkStart(final WalkEvent walkEvent) { @Override public void onWalkEnd(final WalkEvent walkEvent, - final Set validationMessages) { + final List errors) { } } } diff --git a/src/test/java/com/networknt/schema/Issue467Test.java b/src/test/java/com/networknt/schema/Issue467Test.java new file mode 100644 index 000000000..59259cda0 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue467Test.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.PropertyWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +class Issue467Test { + private static final String schemaPath = "/schema/issue467.json"; + + protected ObjectMapper mapper = new ObjectMapper(); + + @Test + void shouldWalkKeywordWithValidation() throws URISyntaxException, IOException { + InputStream schemaInputStream = Issue467Test.class.getResourceAsStream(schemaPath); + final Set properties = new LinkedHashSet<>(); + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + properties.add(walkEvent.getExecutionContext().getEvaluationPath().append(walkEvent.getKeyword())); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List set) { + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaInputStream); + JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json")); + Result result = schema.walk(data, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals(new HashSet<>(Arrays.asList("/properties", "/properties/tags/items/0/properties")), + properties.stream().map(Object::toString).collect(Collectors.toSet())); + assertEquals(1, result.getErrors().size()); + } + + @Test + void shouldWalkPropertiesWithValidation() throws URISyntaxException, IOException { + InputStream schemaInputStream = Issue467Test.class.getResourceAsStream(schemaPath); + final Set properties = new LinkedHashSet<>(); + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + properties.add(walkEvent.getExecutionContext().getEvaluationPath()); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List set) { + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaInputStream); + JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json")); + Result result = schema.walk(data, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals( + new HashSet<>(Arrays.asList("/properties/tags", "/properties/tags/items/0/properties/category", "/properties/tags/items/0/properties/value")), + properties.stream().map(Object::toString).collect(Collectors.toSet())); + assertEquals(1, result.getErrors().size()); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue470Test.java b/src/test/java/com/networknt/schema/Issue470Test.java deleted file mode 100644 index a9bbdd9b4..000000000 --- a/src/test/java/com/networknt/schema/Issue470Test.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.util.Set; - -public class Issue470Test { - - private static JsonSchema schema; - - @BeforeAll - static void init() { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - String schemaPath = "/schema/issue470-v7.json"; - InputStream schemaInputStream = Issue470Test.class.getResourceAsStream(schemaPath); - schema = factory.getSchema(schemaInputStream); - } - - private JsonNode getJsonNodeFromJsonData(String jsonFilePath) throws Exception { - InputStream content = getClass().getResourceAsStream(jsonFilePath); - ObjectMapper mapper = new ObjectMapper(); - return mapper.readTree(content); - } - - @Test - @DisplayName("Test valid oneOf option 1") - public void testValidJson1() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-valid-1.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 2") - public void testValidJson2() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-valid-2.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test invalid oneOf option 1 - wrong type") - public void testInvalidJson1() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-invalid-1.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(1, errors.size()); - Assertions.assertEquals("$.search.byName.name: integer found, string expected", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 1 - invalid value") - public void testInvalidJson2() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-invalid-2.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(1, errors.size()); - Assertions.assertEquals("$.search.byName.name: may only be 20 characters long", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 2 - wrong type") - public void testInvalidJson3() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-invalid-3.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(1, errors.size()); - Assertions.assertEquals("$.search.byAge.age: string found, integer expected", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 2 - invalid value") - public void testInvalidJson4() throws Exception { - JsonNode node = getJsonNodeFromJsonData("/data/issue470-invalid-4.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(1, errors.size()); - Assertions.assertEquals("$.search.byAge.age: must have a maximum value of 150", errors.iterator().next().getMessage()); - } -} diff --git a/src/test/java/com/networknt/schema/Issue471Test.java b/src/test/java/com/networknt/schema/Issue471Test.java index 5e264e313..903a493a8 100644 --- a/src/test/java/com/networknt/schema/Issue471Test.java +++ b/src/test/java/com/networknt/schema/Issue471Test.java @@ -7,13 +7,12 @@ import org.junit.jupiter.api.Test; import java.io.InputStream; -import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.Set; +import java.util.List; import java.util.stream.Collectors; -public class Issue471Test { +class Issue471Test { private final String DATA_PATH = "/data/issue471.json"; private final String SCHEMA_PATH = "/schema/issue471-2019-09.json"; @@ -21,7 +20,7 @@ public class Issue471Test { @Test @Disabled - public void shouldFailV201909_with_enUS() throws Exception { + void shouldFailDRAFT_2019_09_with_enUS() throws Exception { Locale.setDefault(Locale.US); Map errorsMap = validate(); Assertions.assertEquals("$.title: may only be 10 characters long", errorsMap.get("$.title")); @@ -30,7 +29,7 @@ public void shouldFailV201909_with_enUS() throws Exception { @Test @Disabled - public void shouldFailV201909_with_zhCN() throws Exception { + void shouldFailDRAFT_2019_09_with_zhCN() throws Exception { Locale.setDefault(Locale.CHINA); Map errorsMap = validate(); Assertions.assertEquals("$.title:可能只有 10 个字符长", errorsMap.get("$.title")); @@ -39,29 +38,47 @@ public void shouldFailV201909_with_zhCN() throws Exception { @Test @Disabled - public void shouldFailV201909_with_deDE() throws Exception { + void shouldFailDRAFT_2019_09_with_deDE() throws Exception { Locale.setDefault(Locale.GERMANY); Map errorsMap = validate(); Assertions.assertEquals("$.title darf höchstens 10 Zeichen lang sein", errorsMap.get("$.title")); Assertions.assertEquals("$.pictures: Es dürfen höchstens 2 Elemente in diesem Array sein", errorsMap.get("$.pictures")); } + @Test + @Disabled + void shouldFailDRAFT_2019_09_with_frFR() throws Exception { + Locale.setDefault(Locale.FRANCE); + Map errorsMap = validate(); + Assertions.assertEquals("$.title: ne doit pas dépasser 10 caractères", errorsMap.get("$.title")); + Assertions.assertEquals("$.pictures: doit avoir un maximum de 2 éléments dans le tableau", errorsMap.get("$.pictures")); + } + + @Test + @Disabled + void shouldFailDRAFT_2019_09_with_frIT() throws Exception { + Locale.setDefault(Locale.ITALIAN); + Map errorsMap = validate(); + Assertions.assertEquals("$.title: può avere lunghezza massima di 10", errorsMap.get("$.title")); + Assertions.assertEquals("$.pictures: deve esserci un numero massimo di 2 elementi nell'array", errorsMap.get("$.pictures")); + } + private Map validate() throws Exception { InputStream schemaInputStream = Issue471Test.class.getResourceAsStream(SCHEMA_PATH); - JsonSchema schema = getJsonSchemaFromStreamContentV201909(schemaInputStream); + Schema schema = getJsonSchemaFromStreamContentV201909(schemaInputStream); InputStream dataInputStream = Issue471Test.class.getResourceAsStream(DATA_PATH); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); - Set validationMessages = schema.validate(node); - return convertValidationMessagesToMap(validationMessages); + List errors = schema.validate(node); + return convertErrorsToMap(errors); } - private Map convertValidationMessagesToMap(Set validationMessages) { - return validationMessages.stream().collect(Collectors.toMap(ValidationMessage::getPath, ValidationMessage::getMessage)); + private Map convertErrorsToMap(List errors) { + return errors.stream().collect(Collectors.toMap(m -> m.getInstanceLocation().toString(), Error::getMessage)); } - private JsonSchema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); + private Schema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); return factory.getSchema(schemaContent); } diff --git a/src/test/java/com/networknt/schema/Issue475Test.java b/src/test/java/com/networknt/schema/Issue475Test.java new file mode 100644 index 000000000..69aed0ffd --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue475Test.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.DialectId; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Tests for validation of schema against meta schema. + */ +class Issue475Test { + private static final String VALID_INPUT = "{ \n" + + " \"type\": \"object\", \n" + + " \"properties\": { \n" + + " \"key\": { \n" + + " \"title\" : \"My key\", \n" + + " \"type\": \"array\" \n" + + " } \n" + + " }\n" + + "}"; + + private static final String INVALID_INPUT = "{ \n" + + " \"type\": \"object\", \n" + + " \"properties\": { \n" + + " \"key\": { \n" + + " \"title\" : \"My key\", \n" + + " \"type\": \"blabla\" \n" + + " } \n" + + " }\n" + + "}"; + + @Test + void draft4() throws Exception { + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("http://json-schema.org", "classpath:"))); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(DialectId.DRAFT_4)); + + List assertions = schema.validate(JsonMapperFactory.getInstance().readTree(INVALID_INPUT)); + assertEquals(2, assertions.size()); + + assertions = schema.validate(JsonMapperFactory.getInstance().readTree(VALID_INPUT)); + assertEquals(0, assertions.size()); + } + + @Test + void draft6() throws Exception { + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_6, builder -> builder + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("http://json-schema.org", "classpath:"))); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(DialectId.DRAFT_6)); + + List assertions = schema.validate(JsonMapperFactory.getInstance().readTree(INVALID_INPUT)); + assertEquals(2, assertions.size()); + + assertions = schema.validate(JsonMapperFactory.getInstance().readTree(VALID_INPUT)); + assertEquals(0, assertions.size()); + } + + @Test + void draft7() throws Exception { + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("http://json-schema.org", "classpath:"))); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(DialectId.DRAFT_7)); + + List assertions = schema.validate(JsonMapperFactory.getInstance().readTree(INVALID_INPUT)); + assertEquals(2, assertions.size()); + + assertions = schema.validate(JsonMapperFactory.getInstance().readTree(VALID_INPUT)); + assertEquals(0, assertions.size()); + } + + @Test + void draft201909() throws Exception { + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09, builder -> builder + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("https://json-schema.org", "classpath:"))); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(DialectId.DRAFT_2019_09)); + + List assertions = schema.validate(JsonMapperFactory.getInstance().readTree(INVALID_INPUT)); + assertEquals(2, assertions.size()); + + assertions = schema.validate(JsonMapperFactory.getInstance().readTree(VALID_INPUT)); + assertEquals(0, assertions.size()); + } + + @Test + void draft202012() throws Exception { + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("https://json-schema.org", "classpath:"))); + Schema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(DialectId.DRAFT_2020_12)); + + List assertions = schema.validate(JsonMapperFactory.getInstance().readTree(INVALID_INPUT)); + assertEquals(2, assertions.size()); + + assertions = schema.validate(JsonMapperFactory.getInstance().readTree(VALID_INPUT)); + assertEquals(0, assertions.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue491Test.java b/src/test/java/com/networknt/schema/Issue491Test.java deleted file mode 100644 index 84fea6317..000000000 --- a/src/test/java/com/networknt/schema/Issue491Test.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.InputStream; -import java.util.Set; -import java.util.stream.Stream; - -class Issue491Test { - - private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - private static String schemaPath1 = "/schema/issue491-v7.json"; - private static String schemaPath2 = "/schema/issue491_2-v7.json"; - private static String schemaPath3 = "/schema/issue491_3-v7.json"; - - private JsonNode getJsonNodeFromJsonData(String jsonFilePath) throws Exception { - InputStream content = getClass().getResourceAsStream(jsonFilePath); - ObjectMapper mapper = new ObjectMapper(); - return mapper.readTree(content); - } - - @Test - @DisplayName("Test valid oneOf option 1") - void testValidJson1() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-1.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 2") - void testValidJson2() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 1") - void testValidJson3() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-3.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 2") - void testValidJson4() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 1") - void testValidJson5() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-4.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test valid oneOf option 2") - void testValidJson6() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json"); - Set errors = schema.validate(node); - Assertions.assertTrue(errors.isEmpty()); - } - - @Test - @DisplayName("Test invalid oneOf option 1 - wrong type") - void testInvalidJson1() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-1.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); - Assertions.assertEquals("$.search.searchAge.age: string found, integer expected", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 2 - wrong type") - void testInvalidJson2() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-2.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); - Assertions.assertEquals("$.search.name: integer found, string expected", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 1 - wrong type") - void testInvalidJson3() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-3.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); - Assertions.assertEquals("$.search.byAge.age: string found, integer expected", errors.iterator().next().getMessage()); - } - - @Test - @DisplayName("Test invalid oneOf option 2 - wrong type") - void testInvalidJson4() throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-2.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); - Assertions.assertEquals("$.search.name: integer found, string expected", errors.iterator().next().getMessage()); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - @DisplayName("Test invalid oneOf option - wrong types or values") - void testInvalidJson5(String jsonPath, String expectedError) throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3); - JsonSchema schema = factory.getSchema(schemaInputStream); - JsonNode node = getJsonNodeFromJsonData(jsonPath); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); - Assertions.assertEquals(expectedError, errors.iterator().next().getMessage()); - } - - private static Stream parametersProvider() { - return Stream.of( - Arguments.of("/data/issue491-invalid-4.json", "$.search.age: string found, integer expected"), - Arguments.of("/data/issue491-invalid-2.json", "$.search.name: integer found, string expected"), - Arguments.of("/data/issue491-invalid-5.json", "$.search.age: must have a maximum value of 150"), - Arguments.of("/data/issue491-invalid-6.json", "$.search.name: may only be 20 characters long") - ); - } -} diff --git a/src/test/java/com/networknt/schema/Issue493Test.java b/src/test/java/com/networknt/schema/Issue493Test.java index 6bc65d6cf..d0926d9bc 100644 --- a/src/test/java/com/networknt/schema/Issue493Test.java +++ b/src/test/java/com/networknt/schema/Issue493Test.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.hamcrest.Matchers; @@ -17,8 +18,8 @@ class Issue493Test { - private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909); - private static String schemaPath1 = "/schema/issue493.json"; + private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + private static final String schemaPath1 = "/schema/issue493.json"; private JsonNode getJsonNodeFromJsonData (String jsonFilePath) throws Exception @@ -34,9 +35,9 @@ void testValidJson1 () throws Exception { InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); + Schema schema = factory.getSchema(schemaInputStream); JsonNode node = getJsonNodeFromJsonData("/data/issue493-valid-1.json"); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertTrue(errors.isEmpty()); } @@ -46,9 +47,9 @@ void testValidJson2 () throws Exception { InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); + Schema schema = factory.getSchema(schemaInputStream); JsonNode node = getJsonNodeFromJsonData("/data/issue493-valid-2.json"); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertTrue(errors.isEmpty()); } @@ -57,19 +58,19 @@ void testValidJson2 () void testInvalidJson1 () throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); + InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1); + Schema schema = factory.getSchema(schemaInputStream); JsonNode node = getJsonNodeFromJsonData("/data/issue493-invalid-1.json"); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(2, errors.size()); Set allErrorMessages = new HashSet<>(); errors.forEach(vm -> { - allErrorMessages.add(vm.getMessage()); + allErrorMessages.add(vm.toString()); }); assertThat(allErrorMessages, - Matchers.containsInAnyOrder("$.parameters[0].value: string found, integer expected", - "$.parameters[0].value: does not match the regex pattern ^\\{\\{.+\\}\\}$")); + Matchers.containsInAnyOrder("/parameters/0/value: string found, integer expected", + "/parameters/0/value: does not match the regex pattern ^\\{\\{.+\\}\\}$")); } @Test @@ -77,18 +78,20 @@ void testInvalidJson1 () void testInvalidJson2 () throws Exception { - InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1); - JsonSchema schema = factory.getSchema(schemaInputStream); + InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1); + Schema schema = factory.getSchema(schemaInputStream); JsonNode node = getJsonNodeFromJsonData("/data/issue493-invalid-2.json"); - Set errors = schema.validate(node); - Assertions.assertEquals(2, errors.size()); + List errors = schema.validate(node); + Assertions.assertEquals(3, errors.size()); Set allErrorMessages = new HashSet<>(); errors.forEach(vm -> { - allErrorMessages.add(vm.getMessage()); + allErrorMessages.add(vm.toString()); }); - assertThat(allErrorMessages, - Matchers.containsInAnyOrder("$.parameters[1].value: string found, integer expected", - "$.parameters[1].value: does not match the regex pattern ^\\{\\{.+\\}\\}$")); + assertThat(allErrorMessages, Matchers.containsInAnyOrder( + "/parameters/1/value: string found, integer expected", + "/parameters/1/value: does not match the regex pattern ^\\{\\{.+\\}\\}$", + "/parameters/1: must be valid to one and only one schema, but 0 are valid" + )); } } diff --git a/src/test/java/com/networknt/schema/Issue510Test.java b/src/test/java/com/networknt/schema/Issue510Test.java deleted file mode 100644 index 6b251e34d..000000000 --- a/src/test/java/com/networknt/schema/Issue510Test.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; - -public class Issue510Test { - @Test - public void testIssue510() { - ObjectMapper objectMapper = new ObjectMapper(); - JsonSchemaFactory schemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).objectMapper(objectMapper).build(); - System.out.println("schemaFactory = " + schemaFactory); - } -} diff --git a/src/test/java/com/networknt/schema/Issue518Test.java b/src/test/java/com/networknt/schema/Issue518Test.java index 722e95d3b..f96aeed1c 100644 --- a/src/test/java/com/networknt/schema/Issue518Test.java +++ b/src/test/java/com/networknt/schema/Issue518Test.java @@ -3,27 +3,25 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; + import java.io.InputStream; -public class Issue518Test { - private static final JsonMetaSchema igluMetaSchema = - JsonMetaSchema - .builder("http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", JsonMetaSchema.getV7()) +class Issue518Test { + private static final Dialect igluMetaSchema = + Dialect + .builder("http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", Dialects.getDraft7()) .build(); - private static final JsonSchemaFactory FACTORY = - JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) - .addMetaSchema(igluMetaSchema) - .forceHttps(false) - .removeEmptyFragmentSuffix(false) - .build(); + private static final SchemaRegistry REGISTRY = + SchemaRegistry.withDialect(igluMetaSchema); @Test - public void testPreservingEmptyFragmentSuffix() { + void testPreservingEmptyFragmentSuffix() { String schemaPath = "/schema/issue518-v7.json"; InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); - JsonSchema schema = FACTORY.getSchema(schemaInputStream); + Schema schema = REGISTRY.getSchema(schemaInputStream); Assertions.assertNotNull(schema); } diff --git a/src/test/java/com/networknt/schema/Issue532Test.java b/src/test/java/com/networknt/schema/Issue532Test.java index bb380cd5c..fa537fdd8 100644 --- a/src/test/java/com/networknt/schema/Issue532Test.java +++ b/src/test/java/com/networknt/schema/Issue532Test.java @@ -4,13 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -public class Issue532Test { +class Issue532Test { @Test - public void failure() { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - JsonSchemaException ex = assertThrows(JsonSchemaException.class, () -> { + void failure() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + SchemaException ex = assertThrows(SchemaException.class, () -> { factory.getSchema("{ \"$schema\": true }"); }); - assertEquals("Unknown MetaSchema: true", ex.getMessage()); + assertEquals("Unknown dialect: true", ex.getMessage()); } } diff --git a/src/test/java/com/networknt/schema/Issue550Test.java b/src/test/java/com/networknt/schema/Issue550Test.java new file mode 100644 index 000000000..4c65f08b8 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue550Test.java @@ -0,0 +1,55 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; + + +class Issue550Test { + protected Schema getJsonSchemaFromStreamContentV7(String schemaPath) { + InputStream schemaContent = getClass().getResourceAsStream(schemaPath); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + return factory.getSchema(schemaContent); + } + + protected JsonNode getJsonNodeFromStreamContent(String dataPath) throws Exception { + InputStream content = getClass().getResourceAsStream(dataPath); + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + void testValidationMessageDoContainSchemaPath() throws Exception { + String schemaPath = "/schema/issue500_1-v7.json"; + String dataPath = "/data/issue500_1.json"; + Schema schema = getJsonSchemaFromStreamContentV7(schemaPath); + JsonNode node = getJsonNodeFromStreamContent(dataPath); + + List errors = schema.validate(node); + Error error = errors.stream().findFirst().get(); + + Assertions.assertEquals("https://example.com/person.schema.json#/properties/age/minimum", error.getSchemaLocation().toString()); + Assertions.assertEquals(1, errors.size()); + } + + @Test + void testValidationMessageDoContainSchemaPathForOneOf() throws Exception { + String schemaPath = "/schema/issue500_2-v7.json"; + String dataPath = "/data/issue500_2.json"; + Schema schema = getJsonSchemaFromStreamContentV7(schemaPath); + JsonNode node = getJsonNodeFromStreamContent(dataPath); + + List errors = schema.validate(node); + Error error = errors.stream().findFirst().get(); + + // Instead of capturing all subSchema within oneOf, a pointer to oneOf should be provided. + Assertions.assertEquals("https://example.com/person.schema.json#/oneOf", error.getSchemaLocation().toString()); + Assertions.assertEquals(1, errors.size()); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue575Test.java b/src/test/java/com/networknt/schema/Issue575Test.java new file mode 100644 index 000000000..e54e05ae7 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue575Test.java @@ -0,0 +1,129 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.InputStream; +import java.util.List; +import java.util.stream.Stream; + +/** + * This project uses a dependency (com.ethlo.time:itu) to validate time representations. Version 1.51 of this library + * has a problem dealing with certain time zones having a negative offset; for example "-2:30" (Newfoundland time, NDT). + * Moving to version 1.7.0 of this library resolves the issue. + *

+ * This test class confirms that valid negative offsets do not result in a JSON validation error if the ITU library is + * updated to version 1.7.0 or later. + */ +class Issue575Test { + private static Schema schema; + + @BeforeAll + static void init() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + String schemaPath = "/schema/issue575-2019-09.json"; + InputStream schemaInputStream = Issue575Test.class.getResourceAsStream(schemaPath); + schema = factory.getSchema(schemaInputStream); + } + + static Stream validTimeZoneOffsets() { + String json1 = "{\"testDateTime\":\"2022-05-18T08:27:53-05:00\"}"; // America/New_York + String json2 = "{\"testDateTime\":\"2022-05-18T08:27:53-04:00\"}"; // America/New_York (DST) + String json3 = "{\"testDateTime\":\"2022-05-18T08:27:53-03:30\"}"; // America/St_Johns + String json4 = "{\"testDateTime\":\"2022-05-18T08:27:53-02:30\"}"; // America/St_Johns (DST) + String json5 = "{\"testDateTime\":\"2022-05-18T08:27:53+02:00\"}"; // Africa/Cairo + String json6 = "{\"testDateTime\":\"2022-05-18T08:27:53+03:30\"}"; // Asia/Tehran + String json7 = "{\"testDateTime\":\"2022-05-18T08:27:53+04:30\"}"; // Asia/Tehran (DST) + String json8 = "{\"testDateTime\":\"2022-05-18T08:27:53+04:00\"}"; // Asia/Dubai + String json9 = "{\"testDateTime\":\"2022-05-18T08:27:53+05:00\"}"; // Indian/Maldives + String json10 = "{\"testDateTime\":\"2022-05-18T08:27:53+05:30\"}"; // Asia/Kolkata + String json11 = "{\"testDateTime\":\"2022-05-18T08:27:53+10:00\"}"; // Australia/Sydney + String json12 = "{\"testDateTime\":\"2022-05-18T08:27:53+11:00\"}"; // Australia/Sydney (DST) + String json13 = "{\"testDateTime\":\"2022-05-18T08:27:53+14:00\"}"; // Pacific/Kiritimati + String json14 = "{\"testDateTime\":\"2022-05-18T18:45:32.123-05:00\"}"; // America/New_York + String json15 = "{\"testDateTime\":\"2022-05-18T18:45:32.123456-05:00\"}"; // America/New_York + String json16 = "{\"testDateTime\":\"2022-05-18T08:27:53Z\"}"; // UTC + String json17 = "{\"testDateTime\":\"2022-05-18T08:27:53+00:00\"}"; // UTC + + return Stream.of( + Arguments.of(json1), + Arguments.of(json2), + Arguments.of(json3), + Arguments.of(json4), + Arguments.of(json5), + Arguments.of(json6), + Arguments.of(json7), + Arguments.of(json8), + Arguments.of(json9), + Arguments.of(json10), + Arguments.of(json11), + Arguments.of(json12), + Arguments.of(json13), + Arguments.of(json14), + Arguments.of(json15), + Arguments.of(json16), + Arguments.of(json17) + ); + } + + /** + * Confirms that valid time zone offsets do not result in a JSON validation error. + * + * @param jsonObject a sample JSON payload to test + */ + @ParameterizedTest + @MethodSource("validTimeZoneOffsets") + void testValidTimeZoneOffsets(String jsonObject) throws JsonProcessingException { + List errors = schema.validate(new ObjectMapper().readTree(jsonObject)); + Assertions.assertTrue(errors.isEmpty()); + } + + static Stream invalidTimeRepresentations() { + // Invalid JSON payload: 30 days in April + String json1 = "{\"testDateTime\":\"2022-04-31T08:27:53+05:00\"}"; + // Invalid JSON payload: Invalid date/time separator + String json2 = "{\"testDateTime\":\"2022-05-18X08:27:53+05:00\"}"; + // Invalid JSON payload: Time zone details are missing + String json3 = "{\"testDateTime\":\"2022-05-18T08:27:53\"}"; + // Invalid JSON payload: seconds missing from time + String json4 = "{\"testDateTime\":\"2022-05-18T11:23Z\"}"; + // Invalid JSON payload: Text instead of date-time value + String json5 = "{\"testDateTime\":\"Orlando\"}"; + // Invalid JSON payload: A time zone offset of +23:00 is not valid + String json6 = "{\"testDateTime\":\"2022-05-18T08:27:53+23:00\"}"; + // Invalid JSON payload: A time zone offset of -23:00 is not valid + String json7 = "{\"testDateTime\":\"2022-05-18T08:27:53-23:00\"}"; + // Invalid JSON payload: com.ethlo.time:itu does not allow offset -00:00 (Valid per RFC3339 section 4.3. but prohibited in ISO-8601) + String json8 = "{\"testDateTime\":\"2022-05-18T08:27:53-00:00\"}"; + + return Stream.of( + Arguments.of(json1), + Arguments.of(json2), + Arguments.of(json3), + Arguments.of(json4), + Arguments.of(json5), + Arguments.of(json6), + Arguments.of(json7), + Arguments.of(json8) + ); + } + + /** + * Confirms that invalid time representations result in one or more a JSON validation errors. + * + * @param jsonObject a sample JSON payload to test + */ + @ParameterizedTest + @MethodSource("invalidTimeRepresentations") + void testInvalidTimeRepresentations(String jsonObject) throws JsonProcessingException { + List errors = schema.validate(new ObjectMapper().readTree(jsonObject), OutputFormat.DEFAULT, (executionContext, schemaContext) -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + Assertions.assertFalse(errors.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue604Test.java b/src/test/java/com/networknt/schema/Issue604Test.java new file mode 100644 index 000000000..60872c733 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue604Test.java @@ -0,0 +1,24 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.WalkConfig; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class Issue604Test { + @Test + void failure() { + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, false, false)).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema("{ \"type\": \"object\", \"properties\": { \"foo\": { \"type\": \"object\", \"properties\": { \"bar\": { \"type\": \"boolean\", \"default\": false } } } } }"); + ObjectMapper objectMapper = new ObjectMapper(); + assertDoesNotThrow(() -> { + schema.walk(objectMapper.readTree("{}"), false, executionContext -> executionContext.setWalkConfig(walkConfig)); + }); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue606Test.java b/src/test/java/com/networknt/schema/Issue606Test.java new file mode 100644 index 000000000..14ef4eee3 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue606Test.java @@ -0,0 +1,34 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; + +class Issue606Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + return factory.getSchema(schemaContent); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + void shouldWorkV7() throws Exception { + String schemaPath = "/schema/issue606-v7.json"; + String dataPath = "/data/issue606.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List errors = schema.validate(node); + Assertions.assertEquals(0, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue619Test.java b/src/test/java/com/networknt/schema/Issue619Test.java new file mode 100644 index 000000000..97f569974 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue619Test.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.resource.ResourceLoader; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.networknt.schema.BaseJsonSchemaValidatorTest.getJsonNodeFromStringContent; +import com.networknt.schema.resource.InputStreamSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.FileInputStream; + +class Issue619Test { + + private SchemaRegistry factory; + private JsonNode one; + private JsonNode two; + private JsonNode three; + + @BeforeEach + void setup() throws Exception { + ResourceLoader schemaLoader = new ResourceLoader() { + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + String iri = absoluteIri.toString(); + if (iri.startsWith("http://localhost:1234")) { + return () -> { + String path = iri.substring("http://localhost:1234".length()); + return new FileInputStream("src/test/suite/remotes" + path); + }; + } + return null; + } + }; + + factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.add(schemaLoader))); + one = getJsonNodeFromStringContent("1"); + two = getJsonNodeFromStringContent("2"); + three = getJsonNodeFromStringContent("3"); + } + + @Test + void bundledSchemaLoadsAndValidatesCorrectly_Ref() { + Schema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json\" }"); + + assertTrue(referencingRootSchema.validate(one).isEmpty()); + assertTrue(referencingRootSchema.validate(two).isEmpty()); + assertFalse(referencingRootSchema.validate(three).isEmpty()); + } + + @Test + void bundledSchemaLoadsAndValidatesCorrectly_Uri() throws Exception { + Schema rootSchema = factory.getSchema(SchemaLocation.of("resource:schema/issue619.json")); + + assertTrue(rootSchema.validate(one).isEmpty()); + assertTrue(rootSchema.validate(two).isEmpty()); + assertFalse(rootSchema.validate(three).isEmpty()); + } + + @Test + void uriWithEmptyFragment_Ref() { + Schema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#\" }"); + + assertTrue(referencingRootSchema.validate(one).isEmpty()); + assertTrue(referencingRootSchema.validate(two).isEmpty()); + assertFalse(referencingRootSchema.validate(three).isEmpty()); + } + + @Test + void uriWithEmptyFragment_Uri() throws Exception { + Schema rootSchema = factory.getSchema(SchemaLocation.of("resource:schema/issue619.json#")); + + assertTrue(rootSchema.validate(one).isEmpty()); + assertTrue(rootSchema.validate(two).isEmpty()); + assertFalse(rootSchema.validate(three).isEmpty()); + } + + @Test + void uriThatPointsToTwoShouldOnlyValidateTwo_Ref() { + Schema referencingTwoSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/two\" }"); + + assertFalse(referencingTwoSchema.validate(one).isEmpty()); + assertTrue(referencingTwoSchema.validate(two).isEmpty()); + assertFalse(referencingTwoSchema.validate(three).isEmpty()); + } + + @Test + void uriThatPointsToOneShouldOnlyValidateOne_Uri() throws Exception { + Schema oneSchema = factory.getSchema(SchemaLocation.of("resource:schema/issue619.json#/definitions/one")); + + assertTrue(oneSchema.validate(one).isEmpty()); + assertFalse(oneSchema.validate(two).isEmpty()); + assertFalse(oneSchema.validate(three).isEmpty()); + } + + @Test + void uriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne_Ref() { + Schema referencingTwoSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/refToOne\" }"); + + assertTrue(referencingTwoSchema.validate(one).isEmpty()); + assertFalse(referencingTwoSchema.validate(two).isEmpty()); + assertFalse(referencingTwoSchema.validate(three).isEmpty()); + } + + @Test + void uriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne_Uri() throws Exception { + Schema oneSchema = factory.getSchema(SchemaLocation.of("resource:schema/issue619.json#/definitions/refToOne")); + + assertTrue(oneSchema.validate(one).isEmpty()); + assertFalse(oneSchema.validate(two).isEmpty()); + assertFalse(oneSchema.validate(three).isEmpty()); + } + + @Test + void uriThatPointsToSchemaWithIdThatHasDifferentUri_Ref() throws Exception { + JsonNode oneArray = getJsonNodeFromStringContent("[[1]]"); + JsonNode textArray = getJsonNodeFromStringContent("[[\"a\"]]"); + + Schema schemaWithIdFromRef = factory.getSchema("{ \"$ref\": \"resource:tests/draft4/refRemote.json#/3/schema\" }"); + assertTrue(schemaWithIdFromRef.validate(oneArray).isEmpty()); + assertFalse(schemaWithIdFromRef.validate(textArray).isEmpty()); + } + + @Test + void uriThatPointsToSchemaWithIdThatHasDifferentUri_Uri() throws Exception { + JsonNode oneArray = getJsonNodeFromStringContent("[[1]]"); + JsonNode textArray = getJsonNodeFromStringContent("[[\"a\"]]"); + + Schema schemaWithIdFromUri = factory.getSchema(SchemaLocation.of("resource:tests/draft4/refRemote.json#/3/schema")); + assertTrue(schemaWithIdFromUri.validate(oneArray).isEmpty()); + assertFalse(schemaWithIdFromUri.validate(textArray).isEmpty()); + } + + @Test + void uriThatPointsToSchemaThatDoesNotExistShouldFail_Ref() { + Schema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:data/schema-that-does-not-exist.json#/definitions/something\" }"); + + assertThrows(SchemaException.class, () -> referencingNonexistentSchema.validate(one)); + } + + @Test + void uriThatPointsToSchemaThatDoesNotExistShouldFail_Uri() { + assertThrows(SchemaException.class, () -> factory.getSchema(SchemaLocation.of("resource:data/schema-that-does-not-exist.json#/definitions/something"))); + } + + @Test + void uriThatPointsToNodeThatDoesNotExistShouldFail_Ref() { + Schema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/node-that-does-not-exist\" }"); + + assertThrows(SchemaException.class, () -> referencingNonexistentSchema.validate(one)); + } + + @Test + void uriThatPointsToNodeThatDoesNotExistShouldFail_Uri() { + // This test failed before adding the 10-millisecond sleep. IllegalStateException is returned with recursive update error. This only happens on my faster desktop + // computer during 'maven clean install'. It passes within the IDE on the same computer. It passes on my slower laptop. It passes on Travis CI. + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + assertThrows(SchemaException.class, () -> factory.getSchema(SchemaLocation.of("resource:schema/issue619.json#/definitions/node-that-does-not-exist"))); + } +} diff --git a/src/test/java/com/networknt/schema/Issue650Test.java b/src/test/java/com/networknt/schema/Issue650Test.java new file mode 100644 index 000000000..fdd15a33d --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue650Test.java @@ -0,0 +1,84 @@ +package com.networknt.schema; + +import java.io.InputStream; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.BinaryNode; + +/** + * + * created at 07.02.2023 + * + * @author k-oliver + * @since 1.0.77 + */ +class Issue650Test { + + /** + * Test using a Java model with a byte[] property which jackson converts to a BASE64 encoded string automatically. Then convert into + * a jackson tree. The resulting node is of type {@link BinaryNode}. This test checks if validation handles the {@link BinaryNode} as string + * when validating. + * + * @throws Exception + * @since 1.0.77 + */ + @Test + void testBinaryNode() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + // schema with data property of type string: + InputStream schemaInputStream = getClass().getResourceAsStream("/draft7/issue650.json"); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaInputStream); + + // create model first: + Issue650Test.Model model = new Issue650Test.Model(); + model.setData("content".getBytes("UTF-8")); + // now convert to tree. The resulting type of the data property is BinaryNode now: + JsonNode node = mapper.valueToTree(model); + + // validate: + List errors = schema.validate(node); + + // check result: + Assertions.assertTrue(errors.isEmpty()); + } + + /** + * created at 07.02.2023 + * + * @author Oliver Kelling + * @since 1.0.77 + */ + private static class Model { + private byte[] data; + + + /** + * @return the data + * @since 1.0.77 + */ + @SuppressWarnings("unused") // called by jackson + public byte[] getData() { + return this.data; + } + + + /** + * @param data the data to set + * @since 1.0.77 + */ + public void setData(byte[] data) { + this.data = data; + } + + } +} diff --git a/src/test/java/com/networknt/schema/Issue662Test.java b/src/test/java/com/networknt/schema/Issue662Test.java new file mode 100644 index 000000000..5241d4378 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue662Test.java @@ -0,0 +1,59 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class Issue662Test extends BaseJsonSchemaValidatorTest { + + private static final String RESOURCE_PREFIX = "issues/662/"; + private static Schema schema; + + @BeforeAll + static void setup() { + schema = getJsonSchemaFromClasspath(resource("schema.json"), SpecificationVersion.DRAFT_7); + } + + @Test + void testNoErrorsForEmptyObject() throws IOException { + JsonNode node = getJsonNodeFromClasspath(resource("emptyObject.json")); + List errors = schema.validate(node); + assertTrue(errors.isEmpty(), "No validation errors for empty optional object"); + } + + @Test + void testNoErrorsForValidObject() throws IOException { + JsonNode node = getJsonNodeFromClasspath(resource("validObject.json")); + List errors = schema.validate(node); + assertTrue(errors.isEmpty(), "No validation errors for a valid optional object"); + } + + @Test + void testCorrectErrorForInvalidValue() throws IOException { + JsonNode node = getJsonNodeFromClasspath(resource("objectInvalidValue.json")); + List errors = schema.validate(node); + List errorMessages = errors.stream() + .map(v -> v.getEvaluationPath() + " = " + v.toString()) + .collect(toList()); + + // As this is from an anyOf evaluation both error messages should be present as they didn't match any + // The evaluation cannot be expected to know the semantic meaning that this is an optional object + // The evaluation path can be used to provide clarity on the reason + // Omitting the 'object found, null expected' message also provides the misleading impression that the + // object is required when leaving it empty is a possible option + assertTrue(errorMessages + .contains("/properties/optionalObject/anyOf/0/type = /optionalObject: object found, null expected")); + assertTrue(errorMessages.contains( + "/properties/optionalObject/anyOf/1/properties/value/enum = /optionalObject/value: does not have a value in the enumeration [\"one\", \"two\"]")); + } + + private static String resource(String name) { + return RESOURCE_PREFIX + name; + } +} diff --git a/src/test/java/com/networknt/schema/Issue664Test.java b/src/test/java/com/networknt/schema/Issue664Test.java new file mode 100644 index 000000000..738e08665 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue664Test.java @@ -0,0 +1,45 @@ +package com.networknt.schema; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +class Issue664Test { + protected Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + return factory.getSchema(schemaContent); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + void shouldHaveFullSchemaPaths() throws Exception { + String schemaPath = "/schema/issue664-v7.json"; + String dataPath = "/data/issue664.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List errorSchemaPaths = schema.validate(node).stream().map(Error::getSchemaLocation) + .map(Object::toString).collect(Collectors.toList()); + + List expectedSchemaPaths = Arrays.asList( + "#/items/allOf/0/anyOf/0/oneOf", + "#/items/allOf/0/anyOf/0/oneOf/0/not", + "#/items/allOf/1/else/properties/postal_code/pattern", + "#/items/allOf/1/then/properties/postal_code/pattern" + ); + MatcherAssert.assertThat(errorSchemaPaths, Matchers.containsInAnyOrder(expectedSchemaPaths.toArray())); + } +} diff --git a/src/test/java/com/networknt/schema/Issue665Test.java b/src/test/java/com/networknt/schema/Issue665Test.java new file mode 100644 index 000000000..3baad8214 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue665Test.java @@ -0,0 +1,47 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.List; + +class Issue665Test extends BaseJsonSchemaValidatorTest { + + @Test + void testUrnUriAsLocalRef() throws IOException { + Schema schema = getJsonSchemaFromClasspath("draft7/urn/issue665.json", SpecificationVersion.DRAFT_7); + Assertions.assertNotNull(schema); + Assertions.assertDoesNotThrow(schema::initializeValidators); + List messages = schema.validate(getJsonNodeFromStringContent( + "{\"myData\": {\"value\": \"hello\"}}")); + Assertions.assertTrue(messages.isEmpty()); + } + + @Test + void testUrnUriAsLocalRef_ExternalURN() { + SchemaRegistry factory = SchemaRegistry + .builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7)) + .schemaIdResolvers(schemaIdResolvers -> { + schemaIdResolvers.mappings(Collections.singletonMap("urn:data", + "classpath:draft7/urn/issue665_external_urn_subschema.json")); + }) + .build(); + + try (InputStream is = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("draft7/urn/issue665_external_urn_ref.json")) { + Schema schema = factory.getSchema(is); + Assertions.assertNotNull(schema); + Assertions.assertDoesNotThrow(schema::initializeValidators); + List messages = schema.validate(getJsonNodeFromStringContent( + "{\"myData\": {\"value\": \"hello\"}}")); + Assertions.assertTrue(messages.isEmpty()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/test/java/com/networknt/schema/Issue668Test.java b/src/test/java/com/networknt/schema/Issue668Test.java new file mode 100644 index 000000000..040b65286 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue668Test.java @@ -0,0 +1,35 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; + +class Issue668Test { + protected Schema getJsonSchemaFromStreamContent(InputStream schemaContent) throws Exception { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + YAMLMapper mapper = new YAMLMapper(); + JsonNode node = mapper.readTree(schemaContent); + return factory.getSchema(node); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + void shouldHandleReferencesToYaml() throws Exception { + String schemaPath = "/schema/issue668.yml"; + String dataPath = "/data/issue668.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + Schema schema = getJsonSchemaFromStreamContent(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + MatcherAssert.assertThat(schema.validate(node), Matchers.empty()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue686Test.java b/src/test/java/com/networknt/schema/Issue686Test.java new file mode 100644 index 000000000..c0f49d187 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue686Test.java @@ -0,0 +1,67 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.i18n.DefaultMessageSource; +import com.networknt.schema.i18n.ResourceBundleMessageSource; + +import org.junit.jupiter.api.Test; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class Issue686Test { + + @Test + void testDefaults() { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + assertEquals(DefaultMessageSource.getInstance(), config.getMessageSource()); + } + + @Test + void testValidationWithDefaultBundleAndLocale() throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + ResourceBundle resourceBundle = ResourceBundle.getBundle(DefaultMessageSource.BUNDLE_BASE_NAME, Locale.getDefault()); + String expectedMessage = new MessageFormat(resourceBundle.getString("type")).format(new String[] {"integer", "string"}); + verify(config, "/foo: " + expectedMessage); + } + + @Test + void testValidationWithDefaultBundleAndCustomLocale() throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().locale(Locale.ITALIAN).build(); + verify(config, "/foo: integer trovato, string previsto"); + } + + @Test + void testValidationWithCustomBundle() throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .messageSource(new ResourceBundleMessageSource("issue686/translations")) + .locale(Locale.FRENCH) + .build(); + verify(config, "/foo: integer found, string expected (TEST) (FR)"); + } + + @Test + void testLocaleSwitch() throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().locale(Locale.ITALIAN).build(); + verify(config, "/foo: integer trovato, string previsto"); + SchemaRegistryConfig config2 = SchemaRegistryConfig.builder().locale(Locale.FRENCH).build(); + verify(config2, "/foo: integer trouvé, string attendu"); + } + + private Schema getSchema(SchemaRegistryConfig config) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09, builder -> builder.schemaRegistryConfig(config)); + return factory.getSchema("{ \"$schema\": \"https://json-schema.org/draft/2019-09/schema\", \"$id\": \"https://json-schema.org/draft/2019-09/schema\", \"type\": \"object\", \"properties\": { \"foo\": { \"type\": \"string\" } } } }"); + } + + private void verify(SchemaRegistryConfig config, String expectedMessage) throws JsonProcessingException { + List messages = getSchema(config).validate(new ObjectMapper().readTree(" { \"foo\": 123 } ")); + assertEquals(1, messages.size()); + assertEquals(expectedMessage, messages.iterator().next().toString()); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue687Test.java b/src/test/java/com/networknt/schema/Issue687Test.java new file mode 100644 index 000000000..8995df5bd --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue687Test.java @@ -0,0 +1,140 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.path.PathType; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class Issue687Test { + + @Test + void testRoot() { + assertEquals("$", PathType.LEGACY.getRoot()); + assertEquals("$", PathType.JSON_PATH.getRoot()); + assertEquals("", PathType.JSON_POINTER.getRoot()); + } + + @Test + void testDefault() { + assertEquals(PathType.LEGACY, PathType.DEFAULT); + } + + static Stream appendTokens() { + return Stream.of( + Arguments.of(PathType.LEGACY, "$.foo", "bar", "$.foo.bar"), + Arguments.of(PathType.LEGACY, "$.foo", "b.ar", "$.foo.b.ar"), + Arguments.of(PathType.LEGACY, "$.foo", "b~ar", "$.foo.b~ar"), + Arguments.of(PathType.LEGACY, "$.foo", "b/ar", "$.foo.b/ar"), + Arguments.of(PathType.JSON_PATH, "$.foo", "bar", "$.foo.bar"), + Arguments.of(PathType.JSON_PATH, "$.foo", "b.ar", "$.foo['b.ar']"), + Arguments.of(PathType.JSON_PATH, "$.foo", "b~ar", "$.foo['b~ar']"), + Arguments.of(PathType.JSON_PATH, "$.foo", "b/ar", "$.foo['b/ar']"), + Arguments.of(PathType.JSON_PATH, "$", "'", "$['\\'']"), + Arguments.of(PathType.JSON_PATH, "$", "b'ar", "$['b\\'ar']"), + Arguments.of(PathType.JSON_POINTER, "/foo", "bar", "/foo/bar"), + Arguments.of(PathType.JSON_POINTER, "/foo", "b.ar", "/foo/b.ar"), + Arguments.of(PathType.JSON_POINTER, "/foo", "b~ar", "/foo/b~0ar"), + Arguments.of(PathType.JSON_POINTER, "/foo", "b/ar", "/foo/b~1ar") + ); + } + + static Stream appendIndexes() { + return Stream.of( + Arguments.of(PathType.LEGACY, "$.foo", 0, "$.foo[0]"), + Arguments.of(PathType.JSON_PATH, "$.foo", 0, "$.foo[0]"), + Arguments.of(PathType.JSON_POINTER, "/foo", 0, "/foo/0") + ); + } + + static Stream errors() { + String schemaPath = "/schema/issue687.json"; + String content = "{ \"foo\": \"a\", \"b.ar\": 1, \"children\": [ { \"childFoo\": \"a\", \"c/hildBar\": 1 } ] }"; + return Stream.of( + Arguments.of(PathType.LEGACY, schemaPath, content, new String[] { "$.b.ar", "$.children[0].c/hildBar" }), + Arguments.of(PathType.JSON_PATH, schemaPath, content, new String[] { "$['b.ar']", "$.children[0]['c/hildBar']" }), + Arguments.of(PathType.JSON_PATH, schemaPath, content, new String[] { "$['b.ar']", "$.children[0]['c/hildBar']" }), + Arguments.of(PathType.JSON_POINTER, schemaPath, content, new String[] { "/b.ar", "/children/0/c~1hildBar" }) + ); + } + + @ParameterizedTest + @MethodSource("appendTokens") + void testAppendToken(PathType pathType, String currentPath, String token, String expected) { + StringBuilder builder = new StringBuilder(); + builder.append(currentPath); + pathType.append(builder, token); + assertEquals(expected, builder.toString()); + } + + @ParameterizedTest + @MethodSource("appendIndexes") + void testAppendIndex(PathType pathType, String currentPath, Integer index, String expected) { + StringBuilder builder = new StringBuilder(); + builder.append(currentPath); + pathType.append(builder, index); + assertEquals(expected, builder.toString()); + } + + @ParameterizedTest + @MethodSource("errors") + void testError(PathType pathType, String schemaPath, String content, String[] expectedMessagePaths) throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().pathType(pathType).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09, builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema(Issue687Test.class.getResourceAsStream(schemaPath)); + List messages = schema.validate(new ObjectMapper().readTree(content)); + assertEquals(expectedMessagePaths.length, messages.size()); + for (String expectedPath: expectedMessagePaths) { + assertTrue(messages.stream().anyMatch(msg -> expectedPath.equals(msg.getInstanceLocation().toString()))); + } + } + + static Stream specialCharacterTests() { + return Stream.of( + Arguments.of(PathType.JSON_PATH, "'", "$['\\'']"), + Arguments.of(PathType.JSON_PATH, "\\\"", "$['\"']"), + Arguments.of(PathType.JSON_PATH, "\\n", "$['\\n']"), + Arguments.of(PathType.JSON_PATH, "\\r", "$['\\r']"), + Arguments.of(PathType.JSON_PATH, "\\t", "$['\\t']"), + Arguments.of(PathType.JSON_PATH, "\\f", "$['\\f']"), + Arguments.of(PathType.JSON_PATH, "\\b", "$['\\b']"), + Arguments.of(PathType.JSON_POINTER, "~", "/~0"), + Arguments.of(PathType.JSON_POINTER, "/", "/~1"), + Arguments.of(PathType.JSON_POINTER, "\\n", "/\\n"), + Arguments.of(PathType.JSON_POINTER, "\\r", "/\\r"), + Arguments.of(PathType.JSON_POINTER, "\\t", "/\\t"), + Arguments.of(PathType.JSON_POINTER, "\\f", "/\\f"), + Arguments.of(PathType.JSON_POINTER, "\\b", "/\\b") + ); + } + + @ParameterizedTest + @MethodSource("specialCharacterTests") + void testSpecialCharacters(PathType pathType, String propertyName, String expectedPath) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().pathType(pathType).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09, builder -> builder.schemaRegistryConfig(schemaValidatorsConfig)) + .getSchema(mapper.readTree("{\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \""+propertyName+"\": {\n" + + " \"type\": \"boolean\"\n" + + " }\n" + + " }\n" + + "}")); + List errors = schema.validate(mapper.readTree("{\""+propertyName+"\": 1}")); + assertEquals(1, errors.size()); + assertEquals(expectedPath, errors.iterator().next().getInstanceLocation().toString()); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue724Test.java b/src/test/java/com/networknt/schema/Issue724Test.java new file mode 100644 index 000000000..13486210a --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue724Test.java @@ -0,0 +1,88 @@ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertLinesMatch; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +class Issue724Test { + + @Test + void test() throws JsonProcessingException { + StringCollector stringCollector = new StringCollector(); + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder().keywordWalkListener(stringCollector).build(); + + String schema = + "{\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n" + + " \"type\" : \"object\",\n" + + " \"properties\" : {\n" + + " \"credit_card\": {\n" + + " \"type\" : \"string\"\n" + + " }\n" + + " },\n" + + " \"dependentSchemas\": {\n" + + " \"credit_card\": {\n" + + " \"properties\": {\n" + + " \"billing_address\": {\n" + + " \"type\" : \"string\"\n" + + " }\n" + + " },\n" + + " \"required\": [\"billing_address\"]\n" + + " }\n" + + " }\n" + + "}\n"; + String data = + "{\n" + + " \"credit_card\" : \"my_credit_card\",\n" + + " \"billing_address\" : \"my_billing_address\"\n" + + "}\n"; + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + Schema jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schema); + jsonSchema.walk(new ObjectMapper().readTree(data), /* shouldValidateSchema= */ false, executionContext -> executionContext.setWalkConfig(walkConfig)); + + System.out.println(stringCollector.strings); + assertLinesMatch(Arrays.asList("my_credit_card", "my_billing_address"), stringCollector.strings); + } + + static class StringCollector implements WalkListener { + final List strings = new ArrayList<>(); + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + boolean isString = + Optional.of(walkEvent.getSchema().getSchemaNode()) + .map(jsonNode -> jsonNode.get("type")) + .map(JsonNode::asText) + .map(type -> type.equals("string")) + .orElse(false); + + if (isString) { + this.strings.add(walkEvent.getInstanceNode().asText()); + } + + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + // nothing to do here + } + } + +} diff --git a/src/test/java/com/networknt/schema/Issue769ContainsTest.java b/src/test/java/com/networknt/schema/Issue769ContainsTest.java new file mode 100644 index 000000000..19a551a2f --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue769ContainsTest.java @@ -0,0 +1,42 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.keyword.ContainsValidator; +import com.networknt.schema.keyword.KeywordType; + +/** + *

Test class for issue #769

+ *

This test class asserts that correct messages are returned for contains, minContains et maxContains keywords

+ *

Tested class: {@link ContainsValidator}

+ * + * @author vwuilbea + */ +class Issue769ContainsTest extends AbstractJsonSchemaTest { + + @Override + protected String getDataTestFolder() { + return "/data/contains/issue769/"; + } + + @Test + void shouldReturnMinContainsKeyword() { + assertValidatorType("min-contains.json", KeywordType.MIN_CONTAINS); + } + + @Test + void shouldReturnContainsKeywordForMinContainsV7() { + assertValidatorType("min-contains-v7.json", KeywordType.CONTAINS); + } + + @Test + void shouldReturnMaxContainsKeyword() { + assertValidatorType("max-contains.json", KeywordType.MAX_CONTAINS); + } + + @Test + void shouldReturnContainsKeywordForMaxContainsV7() { + assertValidatorType("max-contains-v7.json", KeywordType.CONTAINS); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue784Test.java b/src/test/java/com/networknt/schema/Issue784Test.java new file mode 100644 index 000000000..da483abe1 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue784Test.java @@ -0,0 +1,75 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.Format; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class Issue784Test { + + private static final String FOO_BAR = "foo-bar"; + private static final String SOMETHING_ELSE = "something-else"; + + static class CustomDateTimeFormat implements Format { + + @Override + public String getName() { + return "date-time"; + } + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return value.equals(FOO_BAR); + } + } + + @Test + void allowToOverrideDataTime() throws IOException { + Schema jsonSchema = createSchema(true); + + // Custom validator checks for FOO_BAR + assertEquals(0, validate(jsonSchema, FOO_BAR).size()); + + // Fails with SOMETHING_ELSE + assertEquals(1, validate(jsonSchema, SOMETHING_ELSE).size()); + } + + @Test + void useDefaultValidatorIfNotOverriden() throws IOException { + Schema jsonSchema = createSchema(false); + + // Default validator fails with FOO_BAR + assertEquals(1, validate(jsonSchema, FOO_BAR).size()); + + // Default validator fails with SOMETHING_ELSE + assertEquals(1, validate(jsonSchema, SOMETHING_ELSE).size()); + } + + + private List validate(Schema jsonSchema, String myDateTimeContent) throws JsonProcessingException { + return jsonSchema.validate(new ObjectMapper().readTree(" { \"my-date-time\": \"" + myDateTimeContent + "\" } ")); + } + + private Schema createSchema(boolean useCustomDateFormat) { + Dialect overrideDateTimeValidator = Dialect + .builder(Dialects.getDraft7().getId(), Dialects.getDraft7()) + .formats(formats -> { + if (useCustomDateFormat) { + CustomDateTimeFormat format = new CustomDateTimeFormat(); + formats.put(format.getName(), format); + } + }) + .build(); + + return SchemaRegistry.withDialect(overrideDateTimeValidator) + .getSchema(Issue784Test.class.getResourceAsStream("/issue784/schema.json")); + } +} diff --git a/src/test/java/com/networknt/schema/Issue792.java b/src/test/java/com/networknt/schema/Issue792.java new file mode 100644 index 000000000..b2302017c --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue792.java @@ -0,0 +1,38 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class Issue792 { + + @Test + void test() throws JsonProcessingException { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(false).failFast(true).build(); + + SchemaRegistry schemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); + + String schemaDef = + "{\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" + + " \"$id\": \"http://some-id/\",\n" + + " \"title\": \"title\",\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"field\": {\n" + + " \"type\": \"string\",\n" + + " \"pattern\": \"^[A-Z]{2}$\"\n" + + " }\n" + + " }\n" + + "}"; + + Schema jsonSchema = schemaFactory.getSchema(schemaDef); + JsonNode jsonNode = new ObjectMapper().readTree("{\"field\": \"pattern-violation\"}"); + + assertEquals(1, jsonSchema.validate(jsonNode).size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue824Test.java b/src/test/java/com/networknt/schema/Issue824Test.java new file mode 100644 index 000000000..8ad539afc --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue824Test.java @@ -0,0 +1,69 @@ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialects; + +class Issue824Test { + @Test + void validate() throws JsonProcessingException { + final Schema v201909SpecSchema = SchemaRegistry + .builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09)) + .schemaIdResolvers(schemaIdResolvers -> { + schemaIdResolvers.mapPrefix("https://json-schema.org", "resource:"); + }).build() + .getSchema(SchemaLocation.of(Dialects.getDraft201909().getId())); + final JsonNode invalidSchema = new ObjectMapper().readTree( + "{"+ + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\","+ + " \"type\": \"cat\" "+ + "}"); + + // Validate same JSON schema against v2019-09 spec schema twice + final List validationErrors1 = v201909SpecSchema.validate(invalidSchema); + final List validationErrors2 = v201909SpecSchema.validate(invalidSchema); + + // Validation errors should be the same + assertEquals(validationErrors1, validationErrors2); + + // Results + // + // 1.0.73 + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: should be valid to any of the schemas + // array] + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: should be valid to any of the schemas + // array] + // + // 1.0.74 + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: string found, array expected] + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: string found, array expected] + // + // 1.0.78 + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: should be valid to any of the schemas + // array] + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: should be valid to any of the schemas + // array] + // + // >= 1.0.82 + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: string found, array expected] + // [$.type: does not have a value in the enumeration [array, boolean, integer, + // null, number, object, string], $.type: should be valid to any of the schemas + // array] + // + // ????? + } +} diff --git a/src/test/java/com/networknt/schema/Issue832Test.java b/src/test/java/com/networknt/schema/Issue832Test.java new file mode 100644 index 000000000..9065decdf --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue832Test.java @@ -0,0 +1,67 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.Format; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +class Issue832Test { + private class NoMatchFormat implements Format { + @Override + public boolean matches(ExecutionContext executionContext, String value) { + return false; + } + + @Override + public String getName() { + return "no_match"; + } + + @Override + public String getMessageKey() { + return "always fail match"; + } + } + + private SchemaRegistry buildV7PlusNoFormatSchemaFactory() { + List formats; + formats = new ArrayList<>(); + formats.add(new NoMatchFormat()); + + Dialect dialect = Dialect.builder( + Dialects.getDraft7().getId(), + Dialects.getDraft7()) + .formats(formats) + .build(); + return SchemaRegistry.withDialect(dialect); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + void testV7WithNonMatchingCustomFormat() throws IOException { + String schemaPath = "/schema/issue832-v7.json"; + String dataPath = "/data/issue832.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + SchemaRegistry factory = buildV7PlusNoFormatSchemaFactory(); + Schema schema = factory.getSchema(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List errors = schema.validate(node); + // Both the custom no_match format and the standard email format should fail. + // This ensures that both the standard and custom formatters have been invoked. + Assertions.assertEquals(2, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue857Test.java b/src/test/java/com/networknt/schema/Issue857Test.java new file mode 100644 index 000000000..946629d3d --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue857Test.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class Issue857Test { + @Test + void test() { + String schema = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"properties\": {\r\n" + + " \"id\": {\r\n" + + " \"not\": {\r\n" + + " \"enum\": [\r\n" + + " \"1\",\r\n" + + " \"2\",\r\n" + + " \"3\"\r\n" + + " ]\r\n" + + " },\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"$id\": \"https://d73abc/filter.json\"\r\n" + + "}"; + + String input = "{\r\n" + + " \"id\": \"4\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().failFast(true).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + List result = factory.getSchema(schema).validate(input, InputFormat.JSON); + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue877Test.java b/src/test/java/com/networknt/schema/Issue877Test.java new file mode 100644 index 000000000..083580106 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue877Test.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.serialization.JsonMapperFactory; + +class Issue877Test { + @Test + void test() throws Exception { + String schemaData = "{\n" + + " \"type\": \"object\",\n" + + " \"unevaluatedProperties\": false\n" + + "}"; + + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = jsonSchemaFactory.getSchema(schemaData); + String input = "{}"; + Result result = schema.walk(JsonMapperFactory.getInstance().readTree(input), true); + assertEquals(0, result.getErrors().size()); + + input = ""; + result = schema.walk(JsonMapperFactory.getInstance().readTree(input), true); + assertEquals(1, result.getErrors().size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue898Test.java b/src/test/java/com/networknt/schema/Issue898Test.java new file mode 100644 index 000000000..45ee0ea23 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue898Test.java @@ -0,0 +1,30 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Locale; + +import static java.util.stream.Collectors.toList; + +class Issue898Test extends BaseJsonSchemaValidatorTest { + + @Test + void testMessagesWithSingleQuotes() throws Exception { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().locale(Locale.FRENCH).build(); + + Schema schema = getJsonSchemaFromClasspath("schema/issue898.json", SpecificationVersion.DRAFT_2020_12, config); + JsonNode node = getJsonNodeFromClasspath("data/issue898.json"); + + List messages = schema.validate(node).stream() + .map(Error::toString) + .collect(toList()); + + Assertions.assertEquals(2, messages.size()); + Assertions.assertEquals("/foo: n'a pas de valeur dans l'énumération [\"foo1\", \"foo2\"]", messages.get(0)); + Assertions.assertEquals("/bar: ne correspond pas au modèle d'expression régulière (bar)+", messages.get(1)); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue927Test.java b/src/test/java/com/networknt/schema/Issue927Test.java new file mode 100644 index 000000000..0d1270375 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue927Test.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Test that the code isn't confused by an anchor in the id. + */ +class Issue927Test { + @Test + void test() throws JsonProcessingException { + String schema = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"$id\": \"id\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"title\": \"title\",\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"required\": [\r\n" + + " \"id\",\r\n" + + " \"type\",\r\n" + + " \"genericSubmission\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"id\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"title\": \"title\"\r\n" + + " },\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"title\": \"title\"\r\n" + + " },\r\n" + + " \"genericSubmission\": {\r\n" + + " \"$ref\": \"#/definitions/genericSubmission\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"genericSubmission\": {\r\n" + + " \"$id\": \"#/definitions/genericSubmission\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"title\": \"title\",\r\n" + + " \"required\": [\r\n" + + " \"transactionReference\",\r\n" + + " \"title\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"transactionReference\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"title\": \"title\",\r\n" + + " \"description\": \"description\"\r\n" + + " },\r\n" + + " \"title\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"minItems\": 1,\r\n" + + " \"items\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\r\n" + + " \"value\",\r\n" + + " \"locale\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"value\": {\r\n" + + " \"$ref\": \"#/definitions/value\"\r\n" + + " },\r\n" + + " \"locale\": {\r\n" + + " \"$ref\": \"#/definitions/locale\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"$id\": \"#/definitions/value\",\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"locale\": {\r\n" + + " \"$id\": \"#/definitions/locale\",\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"fr\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Schema jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) + .getSchema(SchemaLocation.of("http://www.example.org"), JsonMapperFactory.getInstance().readTree(schema)); + + String input = "{\r\n" + + " \"$schema\": \"./mySchema.json\",\r\n" + + " \"_comment\": \"comment\",\r\n" + + " \"id\": \"b34024c4-6103-478c-bad6-83b26d98a892\",\r\n" + + " \"type\": \"genericSubmission\",\r\n" + + " \"genericSubmission\": {\r\n" + + " \"transactionReference\": \"123456\",\r\n" + + " \"title\": [\r\n" + + " {\r\n" + + " \"value\": \"[DE]...\",\r\n" + + " \"locale\": \"de\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"value\": \"[EN]...\",\r\n" + + " \"locale\": \"en\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + "}"; + List messages = jsonSchema.validate(input, InputFormat.JSON); + assertEquals(0, messages.size()); + } + +} diff --git a/src/test/java/com/networknt/schema/Issue928Test.java b/src/test/java/com/networknt/schema/Issue928Test.java new file mode 100644 index 000000000..7bbe0f1e9 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue928Test.java @@ -0,0 +1,57 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class Issue928Test { + private final ObjectMapper mapper = new ObjectMapper(); + + private SchemaRegistry factoryFor(SpecificationVersion version) { + return SchemaRegistry + .builder(SchemaRegistry.withDefaultDialect(version)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.mapPrefix("https://example.org", "classpath:")) + .build(); + } + + @Test + void test_07() { + test_spec(SpecificationVersion.DRAFT_7); + } + + @Test + void test_201909() { + test_spec(SpecificationVersion.DRAFT_2019_09); + } + + @Test + void test_202012() { + test_spec(SpecificationVersion.DRAFT_2020_12); + } + + void test_spec(SpecificationVersion specVersion) { + SchemaRegistry schemaFactory = factoryFor(specVersion); + + String versionId = specVersion.getDialectId(); + String versionStr = versionId.substring(versionId.indexOf("draft") + 6, versionId.indexOf("/schema")); + + String baseUrl = String.format("https://example.org/schema/issue928-v%s.json", versionStr); + System.out.println("baseUrl: " + baseUrl); + + Schema byPointer = schemaFactory.getSchema( + SchemaLocation.of(baseUrl + "#/definitions/example")); + + Assertions.assertEquals(byPointer.validate(mapper.valueToTree("A")).size(), 0); + Assertions.assertEquals(byPointer.validate(mapper.valueToTree("Z")).size(), 1); + + Schema byAnchor = schemaFactory.getSchema( + SchemaLocation.of(baseUrl + "#example")); + + Assertions.assertEquals( + byPointer.getSchemaNode(), + byAnchor.getSchemaNode()); + + Assertions.assertEquals(byAnchor.validate(mapper.valueToTree("A")).size(), 0); + Assertions.assertEquals(byAnchor.validate(mapper.valueToTree("Z")).size(), 1); + } +} diff --git a/src/test/java/com/networknt/schema/Issue935Test.java b/src/test/java/com/networknt/schema/Issue935Test.java new file mode 100644 index 000000000..13ba128ff --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue935Test.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import org.junit.jupiter.api.Test; + +class Issue935Test { + @Test + void shouldThrowInvalidSchemaException() { + String schema = "{ \"$schema\": \"0\" }"; + assertThrowsExactly(InvalidSchemaException.class, + () -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09).getSchema(schema)); + } +} diff --git a/src/test/java/com/networknt/schema/Issue936Test.java b/src/test/java/com/networknt/schema/Issue936Test.java new file mode 100644 index 000000000..925e6c0e3 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue936Test.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import org.junit.jupiter.api.Test; + +class Issue936Test { + @Test + void shouldThrowInvalidSchemaException() { + String schema = "{\r\n" + " \"$id\": \"0\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .schemaIdValidator(SchemaIdValidator.DEFAULT) + .build(); + assertThrowsExactly(InvalidSchemaException.class, + () -> SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema)); + try { + SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schema); + } catch (InvalidSchemaException e) { + assertEquals("/$id: '0' is not a valid $id", e.getError().toString()); + } + } +} diff --git a/src/test/java/com/networknt/schema/Issue939Test.java b/src/test/java/com/networknt/schema/Issue939Test.java new file mode 100644 index 000000000..4c75ce86f --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue939Test.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class Issue939Test { + @Test + void shouldNotThrowException() { + String schema = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"additionalProperties\": false,\r\n" + + " \"required\": [\r\n" + + " \"someUuid\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"someUuid\": {\r\n" + + " \"$ref\": \"#/definitions/uuid\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"uuid\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"pattern\": \"^[0-9a-f]{8}(\\\\\\\\-[0-9a-f]{4}){3}\\\\\\\\-[0-9a-f]{12}$\",\r\n" + + " \"minLength\": 36,\r\n" + + " \"maxLength\": 36\r\n" + + " }\r\n" + + " }\r\n" + + " }"; + Schema jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schema); + assertDoesNotThrow(() -> jsonSchema.initializeValidators()); + List assertions = jsonSchema + .validate("{\"someUuid\":\"invalid\"}", InputFormat.JSON); + assertEquals(2, assertions.size()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue940Test.java b/src/test/java/com/networknt/schema/Issue940Test.java new file mode 100644 index 000000000..8dbd10233 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue940Test.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class Issue940Test { + @Test + void shouldNotThrowException() { + String schema = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$ref\": \"#/$defs/greeting\",\r\n" + + " \"$defs\": {\r\n" + + " \"greeting\": {}\r\n" + + " }\r\n" + + "}"; + Schema jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schema); + assertDoesNotThrow(() -> jsonSchema.initializeValidators()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue943Test.java b/src/test/java/com/networknt/schema/Issue943Test.java new file mode 100644 index 000000000..3e85907d7 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue943Test.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class Issue943Test { + @Test + void test() { + Map external = new HashMap<>(); + + String externalSchemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"$id\": \"https://www.example.org/point.json\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\r\n" + + " \"type\",\r\n" + + " \"coordinates\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"enum\": [\r\n" + + " \"Point\"\r\n" + + " ]\r\n" + + " },\r\n" + + " \"coordinates\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"minItems\": 2,\r\n" + + " \"items\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + external.put("https://www.example.org/point.json", externalSchemaData); + + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$ref\": \"https://www.example.org/point.json\",\r\n" + + " \"unevaluatedProperties\": false\r\n" + + "}"; + + String inputData = "{\r\n" + + " \"type\": \"Point\",\r\n" + + " \"coordinates\": [1, 1]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(external))); + Schema schema = factory.getSchema(schemaData); + assertTrue(schema.validate(inputData, InputFormat.JSON).isEmpty()); + + String badData = "{\r\n" + + " \"type\": \"Point\",\r\n" + + " \"hello\": \"Point\",\r\n" + + " \"coordinates\": [1, 1]\r\n" + + "}"; + assertFalse(schema.validate(badData, InputFormat.JSON).isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/Issue994Test.java b/src/test/java/com/networknt/schema/Issue994Test.java new file mode 100644 index 000000000..f1c4156fe --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue994Test.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.vocabulary.Vocabulary; + +class Issue994Test { + @Test + void test() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"textValue\": {\r\n" + + " \"type\": [\r\n" + + " \"string\",\r\n" + + " \"null\"\r\n" + + " ],\r\n" + + " \"isMandatory\": true\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).vocabularies(vocabularies -> { + vocabularies.remove(Vocabulary.DRAFT_2020_12_VALIDATION.getId()); + }).build(); + JsonNode schemaNode = JsonMapperFactory.getInstance().readTree(schemaData); + Schema schema = SchemaRegistry + .withDialect(dialect).getSchema(schemaNode); + String inputData = "{\r\n" + + " \"textValue\": \"hello\"\r\n" + + "}"; + assertTrue(schema.validate(inputData, InputFormat.JSON, OutputFormat.BOOLEAN)); + } +} diff --git a/src/test/java/com/networknt/schema/ItemsLegacyValidatorTest.java b/src/test/java/com/networknt/schema/ItemsLegacyValidatorTest.java new file mode 100644 index 000000000..4c90f6ddb --- /dev/null +++ b/src/test/java/com/networknt/schema/ItemsLegacyValidatorTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.ItemWalkListenerRunner; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +/** + * ItemsLegacyValidatorTest. + */ +class ItemsLegacyValidatorTest { + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"items\": {\"type\": \"integer\"}" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + String inputData = "[1, \"x\"]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/items/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/items/type", message.getSchemaLocation().toString()); + assertEquals("/1", message.getInstanceLocation().toString()); + assertEquals("\"integer\"", message.getSchemaNode().toString()); + assertEquals("\"x\"", message.getInstanceNode().toString()); + assertEquals("/1: string found, integer expected", message.toString()); + assertNull(message.getProperty()); + } + + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageAdditionalItemsInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"items\": [{}]," + + " \"additionalItems\": {\"type\": \"integer\"}" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + String inputData = "[ null, 2, 3, \"foo\" ]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/additionalItems/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/additionalItems/type", message.getSchemaLocation().toString()); + assertEquals("/3", message.getInstanceLocation().toString()); + assertEquals("\"integer\"", message.getSchemaNode().toString()); + assertEquals("\"foo\"", message.getInstanceNode().toString()); + assertEquals("/3: string found, integer expected", message.toString()); + assertNull(message.getProperty()); + } + + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageAdditionalItemsFalseInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"items\": [{}]," + + " \"additionalItems\": false" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + String inputData = "[ null, 2, 3, \"foo\" ]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/additionalItems", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/additionalItems", message.getSchemaLocation().toString()); + assertEquals("", message.getInstanceLocation().toString()); + assertEquals("false", message.getSchemaNode().toString()); + assertEquals("[null,2,3,\"foo\"]", message.getInstanceNode().toString()); + assertEquals(": index '1' is not defined in the schema and the schema does not allow additional items", message.toString()); + assertNull(message.getProperty()); + } + + @Test + void walk() { + String schemaData = "{\r\n" + + " \"items\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk("[\"the\",\"quick\",\"brown\"]", InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(3, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + } + + @Test + void walkNull() { + String schemaData = "{\r\n" + + " \"items\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(1, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + } + + @Test + void walkNullTupleItemsAdditional() { + String schemaData = "{\r\n" + + " \"items\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n," + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"additionalItems\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(3, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("items", items.get(0).getKeyword()); + assertNull(items.get(0).getInstanceNode()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("items", items.get(1).getKeyword()); + assertNull(items.get(1).getInstanceNode()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + assertEquals("additionalItems", items.get(2).getKeyword()); + assertNull(items.get(2).getInstanceNode()); + } + + @Test + void walkTupleItemsAdditional() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"items\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n," + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"additionalItems\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + JsonNode input = JsonMapperFactory.getInstance().readTree("[\"hello\"]"); + Result result = schema.walk(input, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(3, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("items", items.get(0).getKeyword()); + assertEquals("hello", items.get(0).getInstanceNode().textValue()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("items", items.get(1).getKeyword()); + assertNull(items.get(1).getInstanceNode()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + assertEquals("additionalItems", items.get(2).getKeyword()); + assertNull(items.get(2).getInstanceNode()); + } + + @Test + void walkTupleItemsAdditionalDefaults() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"items\": [\r\n" + + " {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"1\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"integer\",\r\n" + + " \"default\": 2\r\n" + + " }\r\n" + + " ],\r\n" + + " \"additionalItems\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"additional\"\r\n" + + " }\r\n" + + "}"; + + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext().getCollectorContext() + .getData().computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner) + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(schemaData); + JsonNode input = JsonMapperFactory.getInstance().readTree("[null, null, null, null]"); + Result result = schema.walk(input, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(4, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("items", items.get(0).getKeyword()); + assertEquals("1", items.get(0).getInstanceNode().textValue()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("items", items.get(1).getKeyword()); + assertEquals(2, items.get(1).getInstanceNode().intValue()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + assertEquals("additionalItems", items.get(2).getKeyword()); + assertEquals("additional", items.get(2).getInstanceNode().asText()); + assertEquals("/3", items.get(3).getInstanceLocation().toString()); + assertEquals("additionalItems", items.get(3).getKeyword()); + assertEquals("additional", items.get(3).getInstanceNode().asText()); + } +} diff --git a/src/test/java/com/networknt/schema/ItemsValidatorTest.java b/src/test/java/com/networknt/schema/ItemsValidatorTest.java new file mode 100644 index 000000000..75d1bed58 --- /dev/null +++ b/src/test/java/com/networknt/schema/ItemsValidatorTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.path.NodePath; +import com.networknt.schema.walk.ItemWalkListenerRunner; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +/** + * ItemsValidatorTest. + */ +class ItemsValidatorTest { + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"items\": {\"type\": \"integer\"}" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "[1, \"x\"]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/items/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/items/type", message.getSchemaLocation().toString()); + assertEquals("/1", message.getInstanceLocation().toString()); + assertEquals("\"integer\"", message.getSchemaNode().toString()); + assertEquals("\"x\"", message.getInstanceNode().toString()); + assertEquals("/1: string found, integer expected", message.toString()); + assertNull(message.getProperty()); + } + + @Test + void walkNull() { + String schemaData = "{\r\n" + + " \"items\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(1, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + } + + @Test + void walkNullPrefixItems() { + String schemaData = "{\r\n" + + " \"prefixItems\": [\r\n" + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"items\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(2, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(0).getKeyword()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("items", items.get(1).getKeyword()); + } +} diff --git a/src/test/java/com/networknt/schema/JsonMetaSchemaTest.java b/src/test/java/com/networknt/schema/JsonMetaSchemaTest.java deleted file mode 100644 index e8da4deda..000000000 --- a/src/test/java/com/networknt/schema/JsonMetaSchemaTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.networknt.schema; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class JsonMetaSchemaTest { - - @Test - void timeFormatValidation() { - Format timeFormat = null; - for (Format commonBuiltinFormat : JsonMetaSchema.COMMON_BUILTIN_FORMATS) { - if ("time".equals(commonBuiltinFormat.getName())) { - timeFormat = commonBuiltinFormat; - } - } - if (timeFormat == null) { - throw new IllegalArgumentException("time format not found"); - } - - assertTrue(timeFormat.matches("14:16:23")); - assertTrue(timeFormat.matches("14:16:23.123456")); - assertFalse(timeFormat.matches("14:16:23.")); - - } - -} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java b/src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java new file mode 100644 index 000000000..99fd3a603 --- /dev/null +++ b/src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java @@ -0,0 +1,73 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.BasicDialectRegistry; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.ResourceLoader; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class JsonSchemaFactoryUriCacheTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void cacheEnabled() throws JsonProcessingException { + runCacheTest(true); + } + + @Test + void cacheDisabled() throws JsonProcessingException { + runCacheTest(false); + } + + private void runCacheTest(boolean enableCache) throws JsonProcessingException { + CustomURIFetcher fetcher = new CustomURIFetcher(); + SchemaRegistry factory = buildJsonSchemaFactory(fetcher, enableCache); + SchemaLocation schemaUri = SchemaLocation.of("cache:uri_mapping/schema1.json"); + String schema = "{ \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"title\": \"json-object-with-schema\", \"type\": \"string\" }"; + fetcher.addResource(schemaUri.getAbsoluteIri(), schema); + assertEquals(objectMapper.readTree(schema), factory.getSchema(schemaUri).schemaNode); + + String modifiedSchema = "{ \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"title\": \"json-object-with-schema\", \"type\": \"object\" }"; + fetcher.addResource(schemaUri.getAbsoluteIri(), modifiedSchema); + + assertEquals(objectMapper.readTree(enableCache ? schema : modifiedSchema), factory.getSchema(schemaUri).schemaNode); + } + + private SchemaRegistry buildJsonSchemaFactory(CustomURIFetcher uriFetcher, boolean enableSchemaCache) { + return SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12)) + .schemaCacheEnabled(enableSchemaCache) + .resourceLoaders(resourceLoaders -> resourceLoaders.add(uriFetcher)) + .dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())) + .build(); + } + + private class CustomURIFetcher implements ResourceLoader { + + private final Map uriToResource = new HashMap<>(); + + void addResource(AbsoluteIri uri, String schema) { + addResource(uri, new ByteArrayInputStream(schema.getBytes(StandardCharsets.UTF_8))); + } + + void addResource(AbsoluteIri uri, InputStream is) { + uriToResource.put(uri, is); + } + + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + return () -> uriToResource.get(absoluteIri); + } + } +} diff --git a/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java b/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java new file mode 100644 index 000000000..bec207c8e --- /dev/null +++ b/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import org.junit.jupiter.api.Test; + +/** + * Test to control preloading of schemas. + */ +class JsonSchemaPreloadTest { + @Test + void cacheRefsFalse() { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().cacheRefs(false).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); + factory.getSchema(SchemaLocation.of("classpath:/issues/1016/schema.json")); + } + + @Test + void preloadSchemaRefMaxNestingDepth() { + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); + factory.getSchema(SchemaLocation.of("classpath:/issues/1016/schema.json")); + } +} diff --git a/src/test/java/com/networknt/schema/JsonSchemaTestSuiteExtrasTest.java b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteExtrasTest.java new file mode 100644 index 000000000..7242260f9 --- /dev/null +++ b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteExtrasTest.java @@ -0,0 +1,51 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +@DisplayName("JSON Schema Test Suite Extras") +class JsonSchemaTestSuiteExtrasTest extends AbstractJsonSchemaTestSuite { + + private static final Path excluded = Paths.get("src/test/resources/draft4/relativeRefRemote.json"); + + @TestFactory + @DisplayName("Draft 2020-12") + Stream draft2022012() { + return createTests(SpecificationVersion.DRAFT_2020_12, "src/test/resources/draft2020-12"); + } + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/draft2019-09"); + } + + @TestFactory + @DisplayName("Draft 7") + Stream draft7() { + return createTests(SpecificationVersion.DRAFT_7, "src/test/resources/draft7"); + } + + @TestFactory + @DisplayName("Draft 6") + Stream draft6() { + return createTests(SpecificationVersion.DRAFT_6, "src/test/resources/draft6"); + } + + @TestFactory + @DisplayName("Draft 4") + Stream draft4() { + return createTests(SpecificationVersion.DRAFT_4, "src/test/resources/draft4"); + } + + @Override + protected boolean enabled(Path path) { + return !excluded.equals(path); + } + +} diff --git a/src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java new file mode 100644 index 000000000..5ff37905a --- /dev/null +++ b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java @@ -0,0 +1,88 @@ +package com.networknt.schema; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +@DisplayName("JSON Schema Test Suite") +class JsonSchemaTestSuiteTest extends AbstractJsonSchemaTestSuite { + + private final Map disabled; + + JsonSchemaTestSuiteTest() { + this.disabled = new HashMap<>(); + + disableV202012Tests(); + disableV201909Tests(); + disableV7Tests(); + disableV6Tests(); + disableV4Tests(); + } + + @TestFactory + @DisplayName("Draft 2020-12") + Stream draft2022012() { + return createTests(SpecificationVersion.DRAFT_2020_12, "src/test/suite/tests/draft2020-12"); + } + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/suite/tests/draft2019-09"); + } + + @TestFactory + @DisplayName("Draft 7") + Stream draft7() { + return createTests(SpecificationVersion.DRAFT_7, "src/test/suite/tests/draft7"); + } + + @TestFactory + @DisplayName("Draft 6") + Stream draft6() { + return createTests(SpecificationVersion.DRAFT_6, "src/test/suite/tests/draft6"); + } + + @TestFactory + @DisplayName("Draft 4") + Stream draft4() { + return createTests(SpecificationVersion.DRAFT_4, "src/test/suite/tests/draft4"); + } + + @Override + protected boolean enabled(Path path) { + return !this.disabled.containsKey(path); + } + + @Override + protected Optional reason(Path path) { + return Optional.ofNullable(this.disabled.get(path)); + } + + private void disableV202012Tests() { + // nothing here + } + + private void disableV201909Tests() { + // nothing here + } + + private void disableV7Tests() { + // nothing here + } + + private void disableV6Tests() { + // nothing here + } + + private void disableV4Tests() { + // nothing here + } + +} diff --git a/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java b/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java index 4afe9a5c7..12d67548b 100644 --- a/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java @@ -6,41 +6,40 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.WalkConfig; + import java.io.IOException; -import java.util.Set; +import java.util.List; import java.util.stream.Collectors; import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class JsonWalkApplyDefaultsTest { - - @AfterEach - void cleanup() { - CollectorContext.getInstance().reset(); - } - @ParameterizedTest @ValueSource(booleans = { true, false}) void testApplyDefaults3(boolean shouldValidateSchema) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); - JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, true)); - ValidationResult result = jsonSchema.walk(inputNode, shouldValidateSchema); + Schema jsonSchema = createSchema(); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + Result result = jsonSchema.walk(inputNode, shouldValidateSchema, executionContext -> executionContext.setWalkConfig(walkConfig)); if (shouldValidateSchema) { - assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), - Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missingButError: string found, integer expected", - "$.outer.badArray[1]: integer found, string expected")); + assertThat(result.getErrors().stream().map(Error::toString).collect(Collectors.toList()), + Matchers.containsInAnyOrder("/outer/mixedObject/intValue_missingButError: string found, integer expected", + "/outer/badArray/1: integer found, string expected", + "/outer/reference/stringValue_missing_with_default_null: null found, string expected")); } else { - assertThat(result.getValidationMessages(), Matchers.empty()); + assertThat(result.getErrors(), Matchers.empty()); } // TODO: In Java 14 use text blocks assertEquals( objectMapper.readTree( - "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",\"five\"],\"badArray\":[\"hello\",5],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\",\"intValue_missing\":15,\"intValue_missing_notRequired\":25},\"goodArray\":[\"hello\",\"five\"],\"badArray\":[\"hello\",5],\"reference\":{\"stringValue_missing_with_default_null\":null,\"stringValue_missing\":\"hello\"}}}"), inputNode); } @@ -48,15 +47,18 @@ void testApplyDefaults3(boolean shouldValidateSchema) throws IOException { void testApplyDefaults2() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); - JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, false)); - ValidationResult result = jsonSchema.walk(inputNode, true); - assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), - Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missingButError: string found, integer expected", - "$.outer.goodArray[1]: null found, string expected", - "$.outer.badArray[1]: null found, string expected")); + Schema jsonSchema = createSchema(); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, false)).build(); + Result result = jsonSchema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertThat(result.getErrors().stream().map(Error::toString).collect(Collectors.toList()), + Matchers.containsInAnyOrder("/outer/mixedObject/intValue_missingButError: string found, integer expected", + "/outer/goodArray/1: null found, string expected", + "/outer/badArray/1: null found, string expected", + "/outer/reference/stringValue_missing_with_default_null: null found, string expected")); assertEquals( objectMapper.readTree( - "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\",\"intValue_missing\":15,\"intValue_missing_notRequired\":25},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing_with_default_null\":null,\"stringValue_missing\":\"hello\"}}}"), inputNode); } @@ -64,16 +66,19 @@ void testApplyDefaults2() throws IOException { void testApplyDefaults1() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); - JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, false, false)); - ValidationResult result = jsonSchema.walk(inputNode, true); - assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), - Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_null: null found, integer expected", - "$.outer.mixedObject.intValue_missingButError: string found, integer expected", - "$.outer.goodArray[1]: null found, string expected", - "$.outer.badArray[1]: null found, string expected")); + Schema jsonSchema = createSchema(); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, false, false)).build(); + Result result = jsonSchema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertThat(result.getErrors().stream().map(Error::toString).collect(Collectors.toList()), + Matchers.containsInAnyOrder("/outer/mixedObject/intValue_null: null found, integer expected", + "/outer/mixedObject/intValue_missingButError: string found, integer expected", + "/outer/goodArray/1: null found, string expected", + "/outer/badArray/1: null found, string expected", + "/outer/reference/stringValue_missing_with_default_null: null found, string expected")); assertEquals( objectMapper.readTree( - "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":null,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_null\":null,\"intValue_missingButError\":\"forty-five\",\"intValue_missing\":15,\"intValue_missing_notRequired\":25},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing_with_default_null\":null,\"stringValue_missing\":\"hello\"}}}"), inputNode); } @@ -83,34 +88,40 @@ void testApplyDefaults0(String method) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); JsonNode inputNodeOriginal = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); - Set validationMessages; + List errors; switch (method) { case "walkWithEmptyStrategy": { - JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(false, false, false)); - validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages(); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(false, false, false)).build(); + Schema jsonSchema = createSchema(); + errors = jsonSchema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)).getErrors(); break; } case "walkWithNoDefaults": { // same empty strategy, but tests for NullPointerException - JsonSchema jsonSchema = createSchema(null); - validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages(); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(null).build(); + Schema jsonSchema = createSchema(); + errors = jsonSchema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)).getErrors(); break; } case "validateWithApplyAllDefaults": { - JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, true)); - validationMessages = jsonSchema.validate(inputNode); + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + Schema jsonSchema = createSchema(); + errors = jsonSchema.validate(inputNode, executionContext -> executionContext.setWalkConfig(walkConfig)); break; } default: throw new UnsupportedOperationException(); } - assertThat(validationMessages.stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), - Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missing: is missing but it is required", - "$.outer.mixedObject.intValue_missingButError: is missing but it is required", - "$.outer.mixedObject.intValue_null: null found, integer expected", - "$.outer.goodArray[1]: null found, string expected", - "$.outer.badArray[1]: null found, string expected", - "$.outer.reference.stringValue_missing: is missing but it is required")); + assertThat(errors.stream().map(Error::toString).collect(Collectors.toList()), + Matchers.containsInAnyOrder("/outer/mixedObject: required property 'intValue_missing' not found", + "/outer/mixedObject: required property 'intValue_missingButError' not found", + "/outer/mixedObject/intValue_null: null found, integer expected", + "/outer/goodArray/1: null found, string expected", + "/outer/badArray/1: null found, string expected", + "/outer/reference: required property 'stringValue_missing' not found")); assertEquals(inputNodeOriginal, inputNode); } @@ -123,10 +134,9 @@ void testIllegalArgumentException() { } } - private JsonSchema createSchema(ApplyDefaultsStrategy applyDefaultsStrategy) { - JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - schemaValidatorsConfig.setApplyDefaultsStrategy(applyDefaultsStrategy); - return schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema/walk-schema-default.json"), schemaValidatorsConfig); + private Schema createSchema() { + SchemaRegistry schemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + return schemaFactory + .getSchema(getClass().getClassLoader().getResourceAsStream("schema/walk-schema-default.json")); } } diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 13b6d87a0..27ff74ca8 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -2,74 +2,85 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.networknt.schema.walk.JsonSchemaWalkListener; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.AbstractKeywordValidator; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; import com.networknt.schema.walk.WalkEvent; import com.networknt.schema.walk.WalkFlow; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.TreeSet; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -public class JsonWalkTest { +class JsonWalkTest { - private JsonSchema jsonSchema; + private Schema jsonSchema; - private JsonSchema jsonSchema1; + private Schema jsonSchema1; private static final String SAMPLE_WALK_COLLECTOR_TYPE = "sampleWalkCollectorType"; private static final String CUSTOM_KEYWORD = "custom-keyword"; + private WalkConfig walkConfig; + + private WalkConfig walkConfig1; + @BeforeEach - public void setup() { + void setup() { setupSchema(); } - @AfterEach - public void cleanup() { - CollectorContext.getInstance().reset(); - } - private void setupSchema() { - final JsonMetaSchema metaSchema = getJsonMetaSchema(); + final Dialect dialect = getDialect(); // Create Schema. - SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); - schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), + KeywordWalkListenerRunner.Builder keywordWalkListenerRunnerBuilder = KeywordWalkListenerRunner.builder(); + + keywordWalkListenerRunnerBuilder.keywordWalkListener(new AllKeywordListener()); + keywordWalkListenerRunnerBuilder.keywordWalkListener(KeywordType.REF.getValue(), new RefKeywordListener()); + keywordWalkListenerRunnerBuilder.keywordWalkListener(KeywordType.PROPERTIES.getValue(), new PropertiesKeywordListener()); - final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) - .build(); - this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); + SchemaRegistry schemaFactory = SchemaRegistry.withDialect(dialect); + this.jsonSchema = schemaFactory.getSchema(getSchema()); + this.walkConfig = WalkConfig.builder().keywordWalkListenerRunner(keywordWalkListenerRunnerBuilder.build()).build(); + // Create another Schema. - SchemaValidatorsConfig schemaValidatorsConfig1 = new SchemaValidatorsConfig(); - schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); - schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), + KeywordWalkListenerRunner.Builder keywordWalkListenerRunner1Builder = KeywordWalkListenerRunner.builder(); + keywordWalkListenerRunner1Builder.keywordWalkListener(KeywordType.REF.getValue(), new RefKeywordListener()); + keywordWalkListenerRunner1Builder.keywordWalkListener(KeywordType.PROPERTIES.getValue(), new PropertiesKeywordListener()); - this.jsonSchema1 = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig1); + schemaFactory = SchemaRegistry.withDialect(dialect); + this.jsonSchema1 = schemaFactory.getSchema(getSchema()); + this.walkConfig1 = WalkConfig.builder().keywordWalkListenerRunner(keywordWalkListenerRunner1Builder.build()).build(); } - private JsonMetaSchema getJsonMetaSchema() { - return JsonMetaSchema.builder( - "https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV201909()) - .addKeyword(new CustomKeyword()).build(); + private Dialect getDialect() { + return Dialect.builder( + "https://github.com/networknt/json-schema-validator/tests/schemas/example01", Dialects.getDraft201909()) + .keyword(new CustomKeyword()).build(); } @Test - public void testWalk() throws IOException { + void testWalk() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); - ValidationResult result = jsonSchema.walk( - objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); + Result result = jsonSchema.walk( + objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false, + executionContext -> executionContext.setWalkConfig(walkConfig)); JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); assertEquals(collectedNode, (objectMapper.readTree("{" + " \"PROPERTY1\": \"sample1\"," @@ -85,11 +96,12 @@ public void testWalk() throws IOException { } @Test - public void testWalkWithDifferentListeners() throws IOException { + void testWalkWithDifferentListeners() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); // This instance of schema contains all listeners. - ValidationResult result = jsonSchema.walk( - objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); + Result result = jsonSchema.walk( + objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false, + executionContext -> executionContext.setWalkConfig(walkConfig)); JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); assertEquals(collectedNode, (objectMapper.readTree("{" + " \"PROPERTY1\": \"sample1\"," @@ -103,11 +115,10 @@ public void testWalkWithDifferentListeners() throws IOException { + " }" + "}"))); // This instance of schema contains one listener removed. - CollectorContext collectorContext = result.getCollectorContext(); - collectorContext.reset(); result = jsonSchema1.walk( - objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); - collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); + objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false, + executionContext -> executionContext.setWalkConfig(walkConfig1)); + collectedNode = (JsonNode) result.getExecutionContext().getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); assertEquals(collectedNode, (objectMapper.readTree("{" + " \"property3\": {" + " \"street_address\":\"test-address\"," @@ -119,6 +130,27 @@ public void testWalkWithDifferentListeners() throws IOException { + "}"))); } + @Test + void testWalkMissingNodeWithPropertiesSchemaShouldNotThrow() { + String schemaContents = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"field\": {\n" + + " \"anyOf\": [\n" + + " {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " }"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaContents); + JsonNode missingNode = MissingNode.getInstance(); + assertDoesNotThrow(() -> schema.walk(missingNode, true)); + } + private InputStream getSchema() { return getClass().getClassLoader().getResourceAsStream("schema/walk-schema.json"); } @@ -133,10 +165,10 @@ public String getValue() { } @Override - public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) throws SchemaException { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator(); + return new CustomValidator(schemaLocation, schemaNode); } return null; } @@ -147,76 +179,75 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc * This will be helpful in cases where we don't want to revisit the entire JSON * document again just for gathering this kind of information. */ - private static class CustomValidator implements JsonValidator { + private static class CustomValidator extends AbstractKeywordValidator { - @Override - public Set validate(JsonNode node, JsonNode rootNode, String at) { - return new TreeSet(); + CustomValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode, schemaLocation); } @Override - public Set validate(JsonNode rootNode) { - return validate(rootNode, rootNode, BaseJsonValidator.AT_ROOT); + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { + return; } @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, - boolean shouldValidateSchema) { - return new LinkedHashSet(); + public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation, boolean shouldValidateSchema) { + return; } } } - private static class AllKeywordListener implements JsonSchemaWalkListener { + private static class AllKeywordListener implements WalkListener { @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { ObjectMapper mapper = new ObjectMapper(); - String keyWordName = keywordWalkEvent.getKeyWordName(); - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); - CollectorContext collectorContext = CollectorContext.getInstance(); + String keyWordName = keywordWalkEvent.getKeyword(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); + CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext(); if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { - collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); + collectorContext.put(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) { ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); - objectNode.put(keywordWalkEvent.getSchemaNode().get("title").textValue().toUpperCase(), - keywordWalkEvent.getNode().textValue()); + objectNode.put(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toUpperCase(), + keywordWalkEvent.getInstanceNode().textValue()); } return WalkFlow.CONTINUE; } @Override - public void onWalkEnd(WalkEvent keywordWalkEvent, Set validationMessages) { + public void onWalkEnd(WalkEvent keywordWalkEvent, List errors) { } } - private static class RefKeywordListener implements JsonSchemaWalkListener { + private static class RefKeywordListener implements WalkListener { @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { ObjectMapper mapper = new ObjectMapper(); - CollectorContext collectorContext = CollectorContext.getInstance(); + CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext(); if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { - collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); + collectorContext.put(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); - objectNode.set(keywordWalkEvent.getSchemaNode().get("title").textValue().toLowerCase(), - keywordWalkEvent.getNode()); + objectNode.set(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toLowerCase(), + keywordWalkEvent.getInstanceNode()); return WalkFlow.SKIP; } @Override - public void onWalkEnd(WalkEvent keywordWalkEvent, Set validationMessages) { + public void onWalkEnd(WalkEvent keywordWalkEvent, List errors) { } } - private static class PropertiesKeywordListener implements JsonSchemaWalkListener { + private static class PropertiesKeywordListener implements WalkListener { @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); if (schemaNode.get("title").textValue().equals("Property3")) { return WalkFlow.SKIP; } @@ -224,7 +255,7 @@ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { } @Override - public void onWalkEnd(WalkEvent keywordWalkEvent, Set validationMessages) { + public void onWalkEnd(WalkEvent keywordWalkEvent, List errors) { } } diff --git a/src/test/java/com/networknt/schema/KeywordsTest.java b/src/test/java/com/networknt/schema/KeywordsTest.java new file mode 100644 index 000000000..844449cb4 --- /dev/null +++ b/src/test/java/com/networknt/schema/KeywordsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.networknt.schema.keyword.KeywordType; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KeywordsTest { + + @Test + void testFromValueString() { + assertEquals(KeywordType.ADDITIONAL_PROPERTIES, KeywordType.fromValue("additionalProperties")); + } + + @Test + void testFromValueMissing() { + Assertions.assertThrows(IllegalArgumentException.class, () -> assertEquals(KeywordType.ADDITIONAL_PROPERTIES, KeywordType.fromValue("missing"))); + } + + @Test + void testIfThenElseNotInV4() { + List list = KeywordType.getKeywords(SpecificationVersion.DRAFT_4); + Assertions.assertFalse(list.contains(KeywordType.fromValue("if"))); + } + + @Test + void testExclusiveMaximumNotInV4() { + List list = KeywordType.getKeywords(SpecificationVersion.DRAFT_4); + Assertions.assertFalse(list.contains(KeywordType.fromValue("exclusiveMaximum"))); + } + +} diff --git a/src/test/java/com/networknt/schema/LocaleTest.java b/src/test/java/com/networknt/schema/LocaleTest.java new file mode 100644 index 000000000..c28cf5c1c --- /dev/null +++ b/src/test/java/com/networknt/schema/LocaleTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.i18n.Locales; +import com.networknt.schema.serialization.JsonMapperFactory; + +class LocaleTest { + private Schema getSchema(SchemaRegistryConfig config) { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09, builder -> builder.schemaRegistryConfig(config)); + return factory.getSchema( + "{ \"$schema\": \"https://json-schema.org/draft/2019-09/schema\", \"$id\": \"https://json-schema.org/draft/2019-09/schema\", \"type\": \"object\", \"properties\": { \"foo\": { \"type\": \"string\" } } } }" + ); + } + + /** + * Tests that the validation messages are generated based on the execution + * context locale. + * + * @throws JsonMappingException the error + * @throws JsonProcessingException the error + */ + @Test + void executionContextLocale() throws JsonMappingException, JsonProcessingException { + JsonNode rootNode = new ObjectMapper().readTree(" { \"foo\": 123 } "); + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + Schema jsonSchema = getSchema(config); + + Locale locale = Locales.findSupported("it;q=0.9,fr;q=1.0"); // fr + ExecutionContext executionContext = jsonSchema.createExecutionContext(); + assertEquals(config.getLocale(), executionContext.getExecutionConfig().getLocale()); + executionContext.executionConfig(executionConfig -> executionConfig.locale(locale)); + List messages = jsonSchema.validate(executionContext, rootNode, OutputFormat.DEFAULT); + assertEquals(1, messages.size()); + assertEquals("/foo: integer trouvé, string attendu", messages.iterator().next().toString()); + + Locale locale2 = Locales.findSupported("it;q=1.0,fr;q=0.9"); // it + executionContext = jsonSchema.createExecutionContext(); + assertEquals(config.getLocale(), executionContext.getExecutionConfig().getLocale()); + executionContext.executionConfig(executionConfig -> executionConfig.locale(locale2)); + messages = jsonSchema.validate(executionContext, rootNode, OutputFormat.DEFAULT); + assertEquals(1, messages.size()); + assertEquals("/foo: integer trovato, string previsto", messages.iterator().next().toString()); + } + + /** + * Issue 949. + *

+ * Locale.ENGLISH should work despite Locale.getDefault setting. + * + * @throws JsonMappingException the exception + * @throws JsonProcessingException the exception + */ + @Test + void englishLocale() throws JsonMappingException, JsonProcessingException { + Locale locale = Locale.getDefault(); + try { + Locale.setDefault(Locale.GERMAN); + String schema = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"$id\": \"https://www.example.com\",\r\n" + + " \"type\": \"object\"\r\n" + + "}"; + Schema jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) + .getSchema(JsonMapperFactory.getInstance().readTree(schema)); + String input = "1"; + List messages = jsonSchema.validate(input, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals(": integer gefunden, object erwartet", messages.iterator().next().toString()); + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().locale(Locale.ENGLISH).build(); + jsonSchema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)) + .getSchema(JsonMapperFactory.getInstance().readTree(schema)); + messages = jsonSchema.validate(input, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals(": integer found, object expected", messages.iterator().next().toString()); + } finally { + Locale.setDefault(locale); + } + } + + /** + * Tests that the file encoding for the locale files are okay. + *

+ * Java 8 does not support UTF-8 encoded resource bundles. That is only + * supported in Java 9 and above. + */ + @Test + void encoding() { + Map expected = new HashMap<>(); + expected.put("ar",": يجب أن يكون طوله 5 حرفًا على الأكثر"); + expected.put("cs",": musí mít maximálně 5 znaků"); + expected.put("da",": må højst være på 5 tegn"); + expected.put("de",": darf höchstens 5 Zeichen lang sein"); + expected.put("es",": debe tener como máximo 5 caracteres"); + expected.put("fa",": باید حداکثر 5 کاراکتر باشد"); + expected.put("fi",": saa olla enintään 5 merkkiä pitkä"); + expected.put("fr",": doit contenir au plus 5 caractères"); + expected.put("iw",": חייב להיות באורך של 5 תווים לכל היותר"); + expected.put("he",": חייב להיות באורך של 5 תווים לכל היותר"); + expected.put("hr",": mora imati najviše 5 znakova"); + expected.put("hu",": legfeljebb 5 karakter hosszúságú lehet"); + expected.put("it",": deve contenere al massimo 5 caratteri"); + expected.put("ja",": 長さは最大 5 文字でなければなりません"); + expected.put("ko",": 길이는 최대 5자여야 합니다."); + expected.put("nb",": må bestå av maksimalt 5 tegn"); + expected.put("nl",": mag maximaal 5 tekens lang zijn"); + expected.put("pl",": musi mieć maksymalnie 5 znaków"); + expected.put("pt",": deve ter no máximo 5 caracteres"); + expected.put("ro",": trebuie să aibă cel mult 5 caractere"); + expected.put("ru",": длина должна быть не более 5 символов."); + expected.put("sk",": musí mať maximálne 5 znakov"); + expected.put("sv",": får vara högst 5 tecken lång"); + expected.put("th",": ต้องมีความยาวสูงสุด 5 อักขระ"); + expected.put("tr",": en fazla 5 karakter uzunluğunda olmalıdır"); + expected.put("uk",": не більше ніж 5 символів"); + expected.put("vi",": phải dài tối đa 5 ký tự"); + expected.put("zh_CN",": 长度不得超过 5 个字符"); + expected.put("zh_TW",": 長度不得超過 5 個字元"); + + // In later JDK versions the numbers will be formatted + Map expectedAlternate = new HashMap<>(); + expectedAlternate.put("ar",": يجب أن يكون طوله ٥ حرفًا على الأكثر"); + expectedAlternate.put("fa",": باید حداکثر ۵ کاراکتر باشد"); + + String schemaData = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"maxLength\": 5\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + List locales = Locales.getSupportedLocales(); + for (Locale locale : locales) { + List messages = schema.validate("\"aaaaaa\"", InputFormat.JSON, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.locale(locale)); + }); + String msg = messages.iterator().next().toString(); + String expectedMsg = expected.get(locale.toString()); + String expectedMsgAlternate = expectedAlternate.get(locale.toString()); + if (msg.equals(expectedMsg) || msg.equals(expectedMsgAlternate)) { + continue; + } + if ("iw".equals(locale.toString()) || "he".equals(locale.toString())) { + // There are changes in the iso codes across JDK versions that make this + // troublesome to handle + continue; + } + assertEquals(expectedMsg, msg); +// System.out.println(messages.iterator().next().toString()); +// System.out.println("expected.put(\"" +locale.toString() + "\",\"" + messages.iterator().next().toString() + "\");"); + +// OutputUnit outputUnit = schema.validate("\"aaaaaa\"", InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { +// executionContext.getExecutionConfig().setLocale(locale); +// }); +// System.out.println(outputUnit); + + } + } +} diff --git a/src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java b/src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java index 99d90272e..f3e689687 100644 --- a/src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java +++ b/src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java @@ -26,11 +26,11 @@ import java.util.List; @Disabled -public class MaximumValidatorPerfTest { +class MaximumValidatorPerfTest { MaximumValidatorTest test = new MaximumValidatorTest(); @Test - public void testTime() throws InvocationTargetException, IllegalAccessException { + void testTime() throws InvocationTargetException, IllegalAccessException { String[] testMethodsToBeExecuted = {"testMaximumDoubleValue"}; List testMethods = getTestMethods(testMethodsToBeExecuted); long start = System.currentTimeMillis(); @@ -39,7 +39,7 @@ public void testTime() throws InvocationTargetException, IllegalAccessException System.out.println("time to execute all tests using:" + (end - start) + "ms"); } - public void executeTests(List methods, int executeTimes) throws InvocationTargetException, IllegalAccessException { + void executeTests(List methods, int executeTimes) throws InvocationTargetException, IllegalAccessException { for (int i = 0; i < executeTimes; i++) { for (Method testMethod : methods) { @@ -49,7 +49,7 @@ public void executeTests(List methods, int executeTimes) throws Invocati } - public List getTestMethods(String[] methodNames) { + List getTestMethods(String[] methodNames) { Method[] methods = test.getClass().getMethods(); List testMethods = new ArrayList(); if (methodNames.length > 0) { diff --git a/src/test/java/com/networknt/schema/MaximumValidatorTest.java b/src/test/java/com/networknt/schema/MaximumValidatorTest.java index 6f5a9cb7e..634945fca 100644 --- a/src/test/java/com/networknt/schema/MaximumValidatorTest.java +++ b/src/test/java/com/networknt/schema/MaximumValidatorTest.java @@ -23,25 +23,25 @@ import java.io.IOException; import java.math.BigDecimal; -import java.util.Set; +import java.util.List; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class MaximumValidatorTest extends BaseJsonSchemaValidatorTest { +class MaximumValidatorTest extends BaseJsonSchemaValidatorTest { private static final String INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s }"; private static final String NUMBER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }"; private static final String EXCLUSIVE_INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s, \"exclusiveMaximum\": true}"; - private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); - private static ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); // due to a jackson bug, a float number which is larger than Double.POSITIVE_INFINITY cannot be convert to BigDecimal correctly // https://github.com/FasterXML/jackson-databind/issues/1770 // https://github.com/FasterXML/jackson-databind/issues/2087 - private static ObjectMapper bigDecimalMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); - private static ObjectMapper bigIntegerMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS); + private static final ObjectMapper bigDecimalMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + private static final ObjectMapper bigIntegerMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS); static String[][] augmentWithQuotes(String[][] values) { for (int i = 0; i < values.length; i++) { @@ -52,7 +52,7 @@ static String[][] augmentWithQuotes(String[][] values) { } @Test - public void positiveNumber() throws IOException { + void positiveNumber() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value {"1000.1", "1000"}, @@ -64,7 +64,7 @@ public void positiveNumber() throws IOException { } @Test - public void negativeNumber() throws IOException { + void negativeNumber() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value // These values overflow 64bit IEEE 754 @@ -86,7 +86,7 @@ public void negativeNumber() throws IOException { } @Test - public void positiveInteger() throws IOException { + void positiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value {"9223372036854775807", "9223372036854775807"}, @@ -105,7 +105,7 @@ public void positiveInteger() throws IOException { } @Test - public void negativeInteger() throws IOException { + void negativeInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value {"9223372036854775800", "9223372036854775855"}, @@ -123,7 +123,7 @@ public void negativeInteger() throws IOException { } @Test - public void positiveExclusiveInteger() throws IOException { + void positiveExclusiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value {"9223372036854775000", "9223372036854774988"}, @@ -142,7 +142,7 @@ public void positiveExclusiveInteger() throws IOException { } @Test - public void negativeExclusiveInteger() throws IOException { + void negativeExclusiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // maximum, value {"10", "20"}, @@ -160,7 +160,7 @@ public void negativeExclusiveInteger() throws IOException { } @Test - public void negativeDoubleOverflowTest() throws IOException { + void negativeDoubleOverflowTest() throws IOException { String[][] values = new String[][]{ // maximum, value // both of these get parsed into double (with a precision loss) as 1.7976931348623157E+308 @@ -181,22 +181,24 @@ public void negativeDoubleOverflowTest() throws IOException { {"1.000000000000000000000001E+400", "\"1.0000000000000000000000011E+400\""}, }; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); + for (String[] aTestCycle : values) { String maximum = aTestCycle[0]; String value = aTestCycle[1]; String schema = format(NUMBER, maximum); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setTypeLoose(true); + // Schema and document parsed with just double - JsonSchema v = factory.getSchema(mapper.readTree(schema), config); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = mapper.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), format("Maximum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", maximum, value)); // document parsed with BigDecimal doc = bigDecimalMapper.readTree(value); - Set messages2 = v.validate(doc); + List messages2 = v.validate(doc); if (Double.valueOf(maximum).equals(Double.POSITIVE_INFINITY)) { assertTrue(messages2.isEmpty(), format("Maximum %s and value %s are equal, thus no schema violation should be reported", maximum, value)); } else { @@ -205,10 +207,11 @@ public void negativeDoubleOverflowTest() throws IOException { // schema and document parsed with BigDecimal - v = factory.getSchema(bigDecimalMapper.readTree(schema), config); - Set messages3 = v.validate(doc); + v = factory.getSchema(bigDecimalMapper.readTree(schema)); + List messages3 = v.validate(doc); //when the schema and value are both using BigDecimal, the value should be parsed in same mechanism. - if (maximum.toLowerCase().equals(value.toLowerCase()) || Double.valueOf(maximum).equals(Double.POSITIVE_INFINITY)) { + String theValue = value.toLowerCase().replace("\"", ""); + if (maximum.toLowerCase().equals(theValue)) { assertTrue(messages3.isEmpty(), format("Maximum %s and value %s are equal, thus no schema violation should be reported", maximum, value)); } else { assertFalse(messages3.isEmpty(), format("Maximum %s is smaller than value %s , should be validation error reported", maximum, value)); @@ -221,14 +224,14 @@ public void negativeDoubleOverflowTest() throws IOException { * the only way to spot this is to use BigDecimal for schema (and for document) */ @Test - public void doubleValueCoarsing() throws IOException { + void doubleValueCoarsing() throws IOException { String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": 1.7976931348623157e+308 }"; String content = "1.7976931348623158e+308"; JsonNode doc = mapper.readTree(content); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper"); doc = bigDecimalMapper.readTree(content); @@ -253,14 +256,14 @@ public void doubleValueCoarsing() throws IOException { * BigDecimalMapper issue, it doesn't work as expected, it will treat 1.7976931348623159e+308 as INFINITY instead of as it is. */ @Test - public void doubleValueCoarsingExceedRange() throws IOException { + void doubleValueCoarsingExceedRange() throws IOException { String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": 1.7976931348623159e+308 }"; String content = "1.7976931348623160e+308"; JsonNode doc = mapper.readTree(content); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper"); doc = bigDecimalMapper.readTree(content); @@ -278,7 +281,9 @@ public void doubleValueCoarsingExceedRange() throws IOException { */ v = factory.getSchema(bigDecimalMapper.readTree(schema)); messages = v.validate(doc); - assertTrue(messages.isEmpty(), "Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY"); + // Before 2.16.0 messages will be empty due to bug https://github.com/FasterXML/jackson-databind/issues/1770 + // assertTrue(messages.isEmpty(), "Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY"); + assertFalse(messages.isEmpty(), "Validation should fail as Incorrect deserialization for BigDecimal numbers is fixed in 2.16.0"); } private static final String POSITIVE_TEST_CASE_TEMPLATE = "Expecting no validation errors, maximum %s is greater than value %s"; @@ -292,13 +297,12 @@ private static void expectNoMessages(String[][] values, String schemaTemplate, O String maximum = aTestCycle[0]; String value = aTestCycle[1]; String schema = format(schemaTemplate, maximum); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setTypeLoose(true); - - JsonSchema v = factory.getSchema(mapper.readTree(schema), config); + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = mapper.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), format(MaximumValidatorTest.POSITIVE_TEST_CASE_TEMPLATE, maximum, value)); } } @@ -315,10 +319,10 @@ private static void expectSomeMessages(String[][] values, String schemaTemplate, String value = aTestCycle[1]; String schema = format(schemaTemplate, maximum); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = mapper2.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertFalse(messages.isEmpty(), format(MaximumValidatorTest.NEGATIVE_TEST_CASE_TEMPLATE, value, maximum)); } } diff --git a/src/test/java/com/networknt/schema/MessageTest.java b/src/test/java/com/networknt/schema/MessageTest.java new file mode 100644 index 000000000..7a9f42629 --- /dev/null +++ b/src/test/java/com/networknt/schema/MessageTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.BaseKeywordValidator; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.KeywordValidator; +import com.networknt.schema.path.NodePath; + +/** + * Test for messages. + */ +class MessageTest { + static class EqualsValidator extends BaseKeywordValidator { + private final String value; + + EqualsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, Keyword keyword, + SchemaContext schemaContext) { + super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext); + this.value = schemaNode.textValue(); + } + + @Override + public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + NodePath instanceLocation) { + if (!node.asText().equals(value)) { + executionContext.addError(error().message("must be equal to ''{0}''") + .arguments(value) + .instanceLocation(instanceLocation).instanceNode(node).evaluationPath(executionContext.getEvaluationPath()).build()); + } + } + } + + static class EqualsKeyword implements Keyword { + + @Override + public String getValue() { + return "equals"; + } + + @Override + public KeywordValidator newValidator(SchemaLocation schemaLocation, + JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) + throws SchemaException, Exception { + return new EqualsValidator(schemaLocation, schemaNode, parentSchema, this, schemaContext); + } + } + + @Test + void message() { + Dialect dialect = Dialect.builder(Dialects.getDraft202012().getId(), Dialects.getDraft202012()) + .keyword(new EqualsKeyword()).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(dialect); + String schemaData = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"equals\": \"helloworld\"\r\n" + + "}"; + Schema schema = factory.getSchema(schemaData); + List messages = schema.validate("\"helloworlda\"", InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals(": must be equal to 'helloworld'", messages.iterator().next().toString()); + + messages = schema.validate("\"helloworld\"", InputFormat.JSON); + assertEquals(0, messages.size()); + } +} diff --git a/src/test/java/com/networknt/schema/MetaSchemaValidationTest.java b/src/test/java/com/networknt/schema/MetaSchemaValidationTest.java new file mode 100644 index 000000000..f7443d6c0 --- /dev/null +++ b/src/test/java/com/networknt/schema/MetaSchemaValidationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Tests for meta schema validating a schema. + */ +class MetaSchemaValidationTest { + /** + * Validates a OpenAPI 3.1 schema using the OpenAPI 3.1 meta schema. + * + * @throws IOException the exception + */ + @Test + void oas31() throws IOException { + try (InputStream input = MetaSchemaValidationTest.class.getResourceAsStream("/schema/oas/3.1/petstore.json")) { + JsonNode inputData = JsonMapperFactory.getInstance().readTree(input); + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaRegistryConfig(config).schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers + .mapPrefix("https://spec.openapis.org/oas/3.1", "classpath:oas/3.1"))) + .getSchema(SchemaLocation.of("https://spec.openapis.org/oas/3.1/schema-base/2022-10-07")); + List messages = schema.validate(inputData); + assertEquals(0, messages.size()); + } + } +} diff --git a/src/test/java/com/networknt/schema/MinimumValidatorTest.java b/src/test/java/com/networknt/schema/MinimumValidatorTest.java index c43cd653f..b4888358c 100644 --- a/src/test/java/com/networknt/schema/MinimumValidatorTest.java +++ b/src/test/java/com/networknt/schema/MinimumValidatorTest.java @@ -16,35 +16,36 @@ package com.networknt.schema; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.math.BigDecimal; -import java.util.Set; - import static com.networknt.schema.MaximumValidatorTest.augmentWithQuotes; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class MinimumValidatorTest { +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +class MinimumValidatorTest { private static final String NUMBER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }"; private static final String EXCLUSIVE_INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s, \"exclusiveMinimum\": true}"; private static final String INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s }"; private static final String NEGATIVE_MESSAGE_TEMPLATE = "Expecting validation errors, value %s is smaller than minimum %s"; private static final String POSITIVT_MESSAGE_TEMPLATE = "Expecting no validation errors, value %s is greater than minimum %s"; - private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); private static ObjectMapper mapper; private static ObjectMapper bigDecimalMapper; private static ObjectMapper bigIntegerMapper; @BeforeEach - public void setUp() { + void setUp() { mapper = new ObjectMapper(); // due to a jackson bug, a float number which is larger than Double.POSITIVE_INFINITY cannot be convert to BigDecimal correctly // https://github.com/FasterXML/jackson-databind/issues/1770 @@ -55,7 +56,7 @@ public void setUp() { } @Test - public void positiveNumber() throws IOException { + void positiveNumber() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"1000", "1000.1"}, @@ -65,7 +66,7 @@ public void positiveNumber() throws IOException { } @Test - public void negativeNumber() throws IOException { + void negativeNumber() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"-1.7976931348623157e+308", "-1.7976931348623159e+308"}, @@ -84,7 +85,7 @@ public void negativeNumber() throws IOException { } @Test - public void positiveInteger() throws IOException { + void positiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"-1E309", "-1000"}, @@ -96,7 +97,7 @@ public void positiveInteger() throws IOException { } @Test - public void negativeInteger() throws IOException { + void negativeInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"-9223372036854775800", "-9223372036854775855"}, @@ -109,7 +110,7 @@ public void negativeInteger() throws IOException { } @Test - public void positiveExclusiveInteger() throws IOException { + void positiveExclusiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"-9223372036854775000", "-9223372036854774988"}, @@ -126,7 +127,7 @@ public void positiveExclusiveInteger() throws IOException { } @Test - public void negativeExclusiveInteger() throws IOException { + void negativeExclusiveInteger() throws IOException { String[][] values = augmentWithQuotes(new String[][]{ // minimum, value {"20", "10"}, @@ -142,7 +143,7 @@ public void negativeExclusiveInteger() throws IOException { } @Test - public void negativeDoubleOverflowTest() throws IOException { + void negativeDoubleOverflowTest() throws IOException { String[][] values = { // minimum, value {"-1.79769313486231571E+308", "-1.79769313486231572e+308"}, @@ -171,18 +172,18 @@ public void negativeDoubleOverflowTest() throws IOException { String minimum = aTestCycle[0]; String value = aTestCycle[1]; String schema = format(NUMBER, minimum); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setTypeLoose(true); + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); // Schema and document parsed with just double - JsonSchema v = factory.getSchema(mapper.readTree(schema), config); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = mapper.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value)); // document parsed with BigDecimal doc = bigDecimalMapper.readTree(value); - Set messages2 = v.validate(doc); + List messages2 = v.validate(doc); //when the schema and value are both using BigDecimal, the value should be parsed in same mechanism. if (Double.valueOf(minimum).equals(Double.NEGATIVE_INFINITY)) { @@ -196,10 +197,12 @@ public void negativeDoubleOverflowTest() throws IOException { } // schema and document parsed with BigDecimal - v = factory.getSchema(bigDecimalMapper.readTree(schema), config); - Set messages3 = v.validate(doc); + + v = factory.getSchema(bigDecimalMapper.readTree(schema)); + List messages3 = v.validate(doc); //when the schema and value are both using BigDecimal, the value should be parsed in same mechanism. - if (minimum.toLowerCase().equals(value.toLowerCase()) || Double.valueOf(minimum).equals(Double.NEGATIVE_INFINITY)) { + String theValue = value.toLowerCase().replace("\"", ""); + if (minimum.toLowerCase().equals(theValue)) { assertTrue(messages3.isEmpty(), format("Minimum %s and value %s are equal, thus no schema violation should be reported", minimum, value)); } else { assertFalse(messages3.isEmpty(), format("Minimum %s is larger than value %s , should be validation error reported", minimum, value)); @@ -212,14 +215,14 @@ public void negativeDoubleOverflowTest() throws IOException { * the only way to spot this is to use BigDecimal for schema (and for document) */ @Test - public void doubleValueCoarsing() throws IOException { + void doubleValueCoarsing() throws IOException { String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": -1.7976931348623157e+308 }"; String content = "-1.7976931348623158e+308"; JsonNode doc = mapper.readTree(content); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper"); doc = bigDecimalMapper.readTree(content); @@ -242,14 +245,14 @@ public void doubleValueCoarsing() throws IOException { * BigDecimalMapper issue, it doesn't work as expected, it will treat -1.7976931348623157e+309 as INFINITY instead of as it is. */ @Test - public void doubleValueCoarsingExceedRange() throws IOException { + void doubleValueCoarsingExceedRange() throws IOException { String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": -1.7976931348623159e+308 }"; String content = "-1.7976931348623160e+308"; JsonNode doc = mapper.readTree(content); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper"); doc = bigDecimalMapper.readTree(content); @@ -258,7 +261,9 @@ public void doubleValueCoarsingExceedRange() throws IOException { v = factory.getSchema(bigDecimalMapper.readTree(schema)); messages = v.validate(doc); - assertTrue(messages.isEmpty(), "Validation should succeed due to the bug of BigDecimal option of mapper"); + // Before 2.16.0 messages will be empty due to bug https://github.com/FasterXML/jackson-databind/issues/1770 + //assertTrue(messages.isEmpty(), "Validation should succeed due to the bug of BigDecimal option of mapper"); + assertFalse(messages.isEmpty(), "Validation should fail as Incorrect deserialization for BigDecimal numbers is fixed in 2.16.0"); } private void expectSomeMessages(String[][] values, String number, ObjectMapper mapper, ObjectMapper mapper2) throws IOException { @@ -267,10 +272,10 @@ private void expectSomeMessages(String[][] values, String number, ObjectMapper m String value = aTestCycle[1]; String schema = format(number, minimum); - JsonSchema v = factory.getSchema(mapper.readTree(schema)); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = mapper2.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertFalse(messages.isEmpty(), format(MinimumValidatorTest.NEGATIVE_MESSAGE_TEMPLATE, value, minimum)); } } @@ -284,13 +289,12 @@ private void expectNoMessages(String[][] values, String integer, ObjectMapper ma String minimum = aTestCycle[0]; String value = aTestCycle[1]; String schema = format(integer, minimum); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setTypeLoose(true); - - JsonSchema v = factory.getSchema(mapper.readTree(schema), config); + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); + Schema v = factory.getSchema(mapper.readTree(schema)); JsonNode doc = bigIntegerMapper.readTree(value); - Set messages = v.validate(doc); + List messages = v.validate(doc); assertTrue(messages.isEmpty(), format(MinimumValidatorTest.POSITIVT_MESSAGE_TEMPLATE, value, minimum)); } } diff --git a/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java b/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java new file mode 100644 index 000000000..99825a826 --- /dev/null +++ b/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Test MultipleOfValidator validator. + */ +class MultipleOfValidatorTest { + String schemaData = "{" + + " \"type\": \"object\"," + + " \"properties\": {" + + " \"value1\": {" + + " \"type\": \"number\"," + + " \"multipleOf\": 0.01" + + " }," + + " \"value2\": {" + + " \"type\": \"number\"," + + " \"multipleOf\": 0.01" + + " }," + + " \"value3\": {" + + " \"type\": \"number\"," + + " \"multipleOf\": 0.01" + + " }" + + " }" + + "}"; + + @Test + void test() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\"value1\":123.892,\"value2\":123456.2934,\"value3\":123.123}"; + String validData = "{\"value1\":123.89,\"value2\":123456,\"value3\":123.010}"; + + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + assertEquals(3, messages.stream().filter(m -> "multipleOf".equals(m.getKeyword())).count()); + + messages = schema.validate(validData, InputFormat.JSON); + assertEquals(0, messages.size()); + } + + @Test + void testTypeLoose() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = "{\"value1\":\"123.892\",\"value2\":\"123456.2934\",\"value3\":123.123}"; + String validTypeLooseInputData = "{\"value1\":\"123.89\",\"value2\":\"123456.29\",\"value3\":123.12}"; + + // Without type loose this has 2 type and 1 multipleOf errors + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + assertEquals(2, messages.stream().filter(m -> "type".equals(m.getKeyword())).count()); + assertEquals(1, messages.stream().filter(m -> "multipleOf".equals(m.getKeyword())).count()); + + // 2 type errors + messages = schema.validate(validTypeLooseInputData, InputFormat.JSON); + assertEquals(2, messages.size()); + assertEquals(2, messages.stream().filter(m -> "type".equals(m.getKeyword())).count()); + + // With type loose this has 3 multipleOf errors + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + Schema typeLoose = factory.getSchema(schemaData); + messages = typeLoose.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + assertEquals(3, messages.stream().filter(m -> "multipleOf".equals(m.getKeyword())).count()); + + // No errors + messages = typeLoose.validate(validTypeLooseInputData, InputFormat.JSON); + assertEquals(0, messages.size()); + } +} diff --git a/src/test/java/com/networknt/schema/NodePathTest.java b/src/test/java/com/networknt/schema/NodePathTest.java new file mode 100644 index 000000000..ffc9e82ce --- /dev/null +++ b/src/test/java/com/networknt/schema/NodePathTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.path.NodePath; +import com.networknt.schema.path.PathType; + +class NodePathTest { + + @Test + void getNameCount() { + NodePath root = new NodePath(PathType.JSON_POINTER); + NodePath path = root.append("hello").append("world"); + assertEquals(2, path.getNameCount()); + } + + @Test + void getName() { + NodePath root = new NodePath(PathType.JSON_POINTER); + NodePath path = root.append("hello").append("world"); + assertEquals("hello", path.getName(0)); + assertEquals("world", path.getName(1)); + assertEquals("world", path.getName(-1)); + assertThrows(IllegalArgumentException.class, () -> path.getName(2)); + } + + @Test + void compareTo() { + NodePath root = new NodePath(PathType.JSON_POINTER); + NodePath a = root.append("a"); + NodePath aa = a.append("a"); + + NodePath b = root.append("b"); + NodePath bb = b.append("b"); + NodePath b1 = b.append(1); + NodePath bbb = bb.append("b"); + + NodePath c = root.append("c"); + NodePath cc = c.append("c"); + + List paths = new ArrayList<>(); + paths.add(cc); + paths.add(aa); + paths.add(bb); + + paths.add(b1); + + paths.add(bbb); + + paths.add(b); + paths.add(a); + paths.add(c); + + Collections.sort(paths); + + String[] result = paths.stream().map(Object::toString).collect(Collectors.toList()).toArray(new String[0]); + + assertArrayEquals(new String[] { "/a", "/b", "/c", "/a/a", "/b/1", "/b/b", "/c/c", "/b/b/b" }, result); + } + + @Test + void equalsEquals() { + NodePath root = new NodePath(PathType.JSON_POINTER); + NodePath a1 = root.append("a"); + NodePath a2 = root.append("a"); + assertEquals(a1, a2); + } + + @Test + void hashCodeEquals() { + NodePath root = new NodePath(PathType.JSON_POINTER); + NodePath a1 = root.append("a"); + NodePath a2 = root.append("a"); + assertEquals(a1.hashCode(), a2.hashCode()); + } + + @Test + void getPathType() { + NodePath root = new NodePath(PathType.JSON_POINTER); + assertEquals(PathType.JSON_POINTER, root.getPathType()); + } + + @Test + void getElement() { + NodePath root = new NodePath(PathType.JSON_PATH); + NodePath path = root.append("hello").append(1).append("world"); + assertEquals("hello", path.getElement(0)); + assertEquals(Integer.valueOf(1), path.getElement(1)); + assertEquals("world", path.getElement(2)); + assertEquals("world", path.getElement(-1)); + assertEquals("$.hello[1].world", path.toString()); + assertThrows(IllegalArgumentException.class, () -> path.getName(3)); + } + + @Test + void startsWith() { + NodePath root = new NodePath(PathType.JSON_PATH); + NodePath path = root.append("items"); + NodePath other = root.append("unevaluatedItems"); + assertTrue(path.startsWith(other.getParent())); + + path = root.append("allOf").append(0).append("items"); + other = root.append("allOf").append(1).append("unevaluatedItems"); + assertFalse(path.startsWith(other.getParent())); + + path = root.append("allOf").append(0).append("items"); + other = root.append("allOf").append(0).append("unevaluatedItems"); + assertTrue(path.startsWith(other.getParent())); + + path = root.append("items"); + other = root.append("items").append(0); + assertTrue(path.startsWith(other.getParent())); + + path = root.append("allOf"); + other = root.append("allOf").append(0).append("items"); + assertFalse(path.startsWith(other.getParent())); + } +} diff --git a/src/test/java/com/networknt/schema/NotAllowedValidatorTest.java b/src/test/java/com/networknt/schema/NotAllowedValidatorTest.java new file mode 100644 index 000000000..8147e6b4f --- /dev/null +++ b/src/test/java/com/networknt/schema/NotAllowedValidatorTest.java @@ -0,0 +1,28 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.keyword.NotAllowedValidator; +import com.networknt.schema.keyword.KeywordType; + + +/** + * This class test {@link NotAllowedValidator}, + * above-mentioned validator check that schema defined json should not be there in JSON object + */ +class NotAllowedValidatorTest extends AbstractJsonSchemaTest { + + @Override + protected String getDataTestFolder() { + return "/data/notAllowedValidation/"; + } + + + /** + * This test case checks that NotAllowedValidator is working with latest code and able to identify field. + */ + @Test + void testNotAllowedValidatorWorks() { + assertValidatorType("notAllowedJson.json", KeywordType.NOT_ALLOWED); + } +} diff --git a/src/test/java/com/networknt/schema/NotValidatorTest.java b/src/test/java/com/networknt/schema/NotValidatorTest.java new file mode 100644 index 000000000..454924745 --- /dev/null +++ b/src/test/java/com/networknt/schema/NotValidatorTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class NotValidatorTest { + @Test + void walkValidationWithNullNodeShouldNotValidate() { + String schemaContents = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"prop1\": {\r\n" + + " \"not\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false\r\n" + + "}"; + + String jsonContents = "{}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaContents); + Result result = schema.walk(jsonContents, InputFormat.JSON, true); + assertEquals(true, result.getErrors().isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/OneOfValidatorTest.java b/src/test/java/com/networknt/schema/OneOfValidatorTest.java new file mode 100644 index 000000000..83b25637c --- /dev/null +++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.path.PathType; + +/** + * OneOfValidatorTest. + */ +class OneOfValidatorTest { + @Test + void oneOfMultiple() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"hello\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : false\r\n" + + " },\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"world\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"fox\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : { \"type\" : \"string\" }\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + String inputData = "{\r\n" + + " \"fox\" : \"test\",\r\n" + + " \"world\" : \"test\"\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().pathType(PathType.LEGACY).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); // even if more than 1 matches the mismatch errors are still reported + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", assertions.get(0).getKeyword()); + assertEquals("$", assertions.get(0).getInstanceLocation().toString()); + assertEquals("$.oneOf", assertions.get(0).getEvaluationPath().toString()); + assertEquals("$: must be valid to one and only one schema, but 2 are valid with indexes '1, 2'", + assertions.get(0).toString()); + } + + @Test + void oneOfZero() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"hello\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : false\r\n" + + " },\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"world\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"fox\" : { \"type\" : \"string\" }\r\n" + + " },\r\n" + + " \"additionalProperties\" : { \"type\" : \"string\" }\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + String inputData = "{\r\n" + + " \"test\" : 1\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().pathType(PathType.LEGACY).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(4, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", assertions.get(0).getKeyword()); + assertEquals("$", assertions.get(0).getInstanceLocation().toString()); + assertEquals("$.oneOf", assertions.get(0).getEvaluationPath().toString()); + assertEquals("$: must be valid to one and only one schema, but 0 are valid", assertions.get(0).toString()); + + assertEquals("additionalProperties", assertions.get(1).getKeyword()); + assertEquals("$", assertions.get(1).getInstanceLocation().toString()); + assertEquals("$.oneOf[0].additionalProperties", assertions.get(1).getEvaluationPath().toString()); + + assertEquals("type", assertions.get(2).getKeyword()); + assertEquals("$.test", assertions.get(2).getInstanceLocation().toString()); + assertEquals("$.oneOf[1].additionalProperties.type", assertions.get(2).getEvaluationPath().toString()); + + assertEquals("type", assertions.get(3).getKeyword()); + assertEquals("$.test", assertions.get(3).getInstanceLocation().toString()); + assertEquals("$.oneOf[2].additionalProperties.type", assertions.get(3).getEvaluationPath().toString()); + } + + @Test + void invalidTypeShouldThrowJsonSchemaException() { + String schemaData = "{\r\n" + + " \"$defs\": {\r\n" + + " \"User\": true\r\n" + + " },\r\n" + + " \"oneOf\": {\r\n" + + " \"$ref\": \"#/defs/User\"\r\n" + + " }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + SchemaException ex = assertThrows(SchemaException.class, () -> factory.getSchema(schemaData)); + assertEquals("type", ex.getError().getMessageKey()); + } + + /** + * This test checks that the oneOf example at + * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/ + * behaves according to the specification instead of the example. + *

+ * https://github.com/swagger-api/swagger.io/issues/253 + * https://github.com/OAI/OpenAPI-Specification/issues/3477 + * https://github.com/networknt/json-schema-validator/issues/110 + */ + @Test + void invalidSwaggerIoExample() { + String document = "paths:\r\n" + + " /pets:\r\n" + + " patch:\r\n" + + " requestBody:\r\n" + + " content:\r\n" + + " application/json:\r\n" + + " schema:\r\n" + + " oneOf:\r\n" + + " - $ref: '#/components/schemas/Cat'\r\n" + + " - $ref: '#/components/schemas/Dog'\r\n" + + " responses:\r\n" + + " '200':\r\n" + + " description: Updated\r\n" + + "components:\r\n" + + " schemas:\r\n" + + " Dog:\r\n" + + " type: object\r\n" + + " properties:\r\n" + + " bark:\r\n" + + " type: boolean\r\n" + + " breed:\r\n" + + " type: string\r\n" + + " enum: [Dingo, Husky, Retriever, Shepherd]\r\n" + + " Cat:\r\n" + + " type: object\r\n" + + " properties:\r\n" + + " hunts:\r\n" + + " type: boolean\r\n" + + " age:\r\n" + + " type: integer"; + + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders + .resources(Collections.singletonMap("http://example.org/example.yaml", document)))) + .getSchema(SchemaLocation.of( + "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema")); + + String example1 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"breed\": \"Dingo\" \r\n" + + "}"; + assertFalse(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN)); + String example2 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"hunts\": true\r\n" + + "}"; + assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN)); + String example3 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"hunts\": true,\r\n" + + " \"breed\": \"Husky\",\r\n" + + " \"age\": 3 \r\n" + + "}"; + assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN)); + } + + /** + * This test checks that the oneOf example at + * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/ + * behaves according to the specification instead of the example. + *

+ * https://github.com/swagger-api/swagger.io/issues/253 + * https://github.com/OAI/OpenAPI-Specification/issues/3477 + * https://github.com/networknt/json-schema-validator/issues/110 + */ + @Test + void fixedSwaggerIoExample() { + String document = "paths:\r\n" + + " /pets:\r\n" + + " patch:\r\n" + + " requestBody:\r\n" + + " content:\r\n" + + " application/json:\r\n" + + " schema:\r\n" + + " oneOf:\r\n" + + " - $ref: '#/components/schemas/Cat'\r\n" + + " - $ref: '#/components/schemas/Dog'\r\n" + + " responses:\r\n" + + " '200':\r\n" + + " description: Updated\r\n" + + "components:\r\n" + + " schemas:\r\n" + + " Dog:\r\n" + + " type: object\r\n" + + " properties:\r\n" + + " bark:\r\n" + + " type: boolean\r\n" + + " breed:\r\n" + + " type: string\r\n" + + " enum: [Dingo, Husky, Retriever, Shepherd]\r\n" + + " required:\r\n" + + " - bark\r\n" + + " - breed\r\n" + + " Cat:\r\n" + + " type: object\r\n" + + " properties:\r\n" + + " hunts:\r\n" + + " type: boolean\r\n" + + " age:\r\n" + + " type: integer\r\n" + + " required:\r\n" + + " - hunts\r\n" + + " - age"; + + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders + .resources(Collections.singletonMap("http://example.org/example.yaml", document)))) + .getSchema(SchemaLocation.of( + "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema")); + + String example1 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"breed\": \"Dingo\" \r\n" + + "}"; + assertTrue(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN)); + String example2 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"hunts\": true\r\n" + + "}"; + assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN)); + String example3 = "{\r\n" + + " \"bark\": true,\r\n" + + " \"hunts\": true,\r\n" + + " \"breed\": \"Husky\",\r\n" + + " \"age\": 3 \r\n" + + "}"; + assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN)); + } + + /** + * Test for when the discriminator keyword is enabled but no discriminator is + * present in the schema. This should process as a normal oneOf and return the + * error messages. + */ + @Test + void oneOfDiscriminatorEnabled() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + Schema schema = SchemaRegistry.withDialect(Dialects.getOpenApi31()).getSchema(schemaData); + String inputData = "{}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + } + + /** + * Standard case where the discriminator is in the same schema as oneOf. + *

+ * Note that discriminators do not affect the validation result and can only + * affect the messages returned. + */ + @Test + void oneOfDiscriminatorEnabledWithDiscriminator() { + String schemaData = "{\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"type\",\r\n" + + " \"mapping\": {\r\n" + + " \"string\": \"#/$defs/string\",\r\n" + + " \"number\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"string\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"number\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry.withDialect(Dialects.getOpenApi31()).getSchema(schemaData); + // Valid + String inputData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": 1\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Invalid only 1 message returned for number + String inputData2 = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": {}\r\n" + + "}"; + List messages2 = schema.validate(inputData2, InputFormat.JSON); + assertEquals(2, messages2.size()); + + // Invalid both messages for string and object returned + Schema schema2 = SchemaRegistry.withDialect(Dialects.getDraft202012()).getSchema(schemaData); + List messages3 = schema2.validate(inputData2, InputFormat.JSON); + assertEquals(3, messages3.size()); + } + + /** + * Subclass case where the discriminator is in an allOf inside one of the oneOf references. + *

+ * Note that discriminators do not affect the validation result and can only + * affect the messages returned. + */ + @Test + void oneOfDiscriminatorEnabledWithDiscriminatorInSubclass() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"typed\": {\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"type\",\r\n" + + " \"mapping\": {\r\n" + + " \"string\": \"#/$defs/string\",\r\n" + + " \"number\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"string\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/typed\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"number\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/typed\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry.withDialect(Dialects.getOpenApi31()).getSchema(schemaData); + // Valid + String inputData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": 1\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Invalid only 1 message returned for number + String inputData2 = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": {}\r\n" + + "}"; + List messages2 = schema.validate(inputData2, InputFormat.JSON); + assertEquals(2, messages2.size()); + + // Invalid both messages for string and object returned + Schema schema2 = SchemaRegistry.withDialect(Dialects.getDraft202012()).getSchema(schemaData); + List messages3 = schema2.validate(inputData2, InputFormat.JSON); + assertEquals(3, messages3.size()); + } + + @Test + void walkValidationWithNullNodeShouldNotValidate() { + String schemaContents = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"prop1\": {\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": false\r\n" + + " }"; + + String jsonContents = "{}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = factory.getSchema(schemaContents); + Result result = schema.walk(jsonContents, InputFormat.JSON, true); + result.getErrors().forEach(m -> System.out.println(m)); + assertEquals(true, result.getErrors().isEmpty()); + } + +} diff --git a/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java b/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java index f694ba4f8..09075e756 100644 --- a/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java +++ b/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java @@ -1,60 +1,27 @@ package com.networknt.schema; -import java.io.File; import java.io.InputStream; -import java.net.URI; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; -import io.undertow.Undertow; -import io.undertow.server.handlers.resource.FileResourceManager; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import com.networknt.schema.dialect.Dialects; + import org.junit.jupiter.api.Test; -import static io.undertow.Handlers.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -public class OpenAPI30JsonSchemaTest { +class OpenAPI30JsonSchemaTest { protected ObjectMapper mapper = new ObjectMapper(); - protected JsonSchemaFactory validatorFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build(); - protected static Undertow server = null; - - public OpenAPI30JsonSchemaTest() { - } - - @BeforeAll - public static void setUp() { - if (server == null) { - server = Undertow.builder() - .addHttpListener(1234, "localhost") - .setHandler(resource(new FileResourceManager( - new File("./src/test/resources/remotes"), 100))) - .build(); - server.start(); - } - } - @AfterAll - public static void tearDown() throws Exception { - if (server != null) { - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - - } - server.stop(); - } + OpenAPI30JsonSchemaTest() { } private void runTestFile(String testCaseFile) throws Exception { - final URI testCaseFileUri = URI.create("classpath:" + testCaseFile); + final SchemaLocation testCaseFileUri = SchemaLocation.of("classpath:" + testCaseFile); InputStream in = Thread.currentThread().getContextClassLoader() .getResourceAsStream(testCaseFile); ArrayNode testCases = mapper.readValue(in, ArrayNode.class); @@ -62,46 +29,51 @@ private void runTestFile(String testCaseFile) throws Exception { for (int j = 0; j < testCases.size(); j++) { try { JsonNode testCase = testCases.get(j); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - + System.out.println("Test Case ["+(j+1)+"]: "+testCase.get("description")); + //System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testCase.get("schema"))); ArrayNode testNodes = (ArrayNode) testCase.get("tests"); for (int i = 0; i < testNodes.size(); i++) { JsonNode test = testNodes.get(i); - System.out.println("=== " + test.get("description")); + System.out.println("> Test Data ["+(i+1)+"]: "+test); JsonNode node = test.get("data"); JsonNode typeLooseNode = test.get("isTypeLoose"); // Configure the schemaValidator to set typeLoose's value based on the test file, // if test file do not contains typeLoose flag, use default value: true. - config.setTypeLoose(typeLooseNode != null && typeLooseNode.asBoolean()); - config.setOpenAPI3StyleDiscriminators(true); - JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config); + SchemaRegistryConfig.Builder configBuilder = SchemaRegistryConfig.builder(); + configBuilder.typeLoose(typeLooseNode != null && typeLooseNode.asBoolean()); + SchemaRegistry validatorFactory = SchemaRegistry.withDialect(Dialects.getOpenApi30(), + builder -> builder.schemaRegistryConfig(configBuilder.build())); + + Schema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema")); - List errors = new ArrayList(schema.validate(node)); + List errors = new ArrayList(schema.validate(node)); if (test.get("valid").asBoolean()) { if (!errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema.toString()); - System.out.println("data: " + test.get("data")); - System.out.println("errors:"); - for (ValidationMessage error : errors) { + System.out.println("---- Test Data ["+(i+1)+"] FAILED [Unexpected Errors] ----"); + System.out.println("> Schema: " + schema); + System.out.println("> Data : " + test.get("data")); + System.out.println("> Errors:"); + for (Error error : errors) { System.out.println(error); } } assertEquals(0, errors.size()); } else { +// System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testCase.get("schema"))); if (errors.isEmpty()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); + System.out.println("---- Test Data ["+(i+1)+"] FAILED [Unexpected Success] ----"); + System.out.println("> Schema: " + schema); + System.out.println("> Data : " + test.get("data")); } else { JsonNode errorCount = test.get("errorCount"); if (errorCount != null && errorCount.isInt() && errors.size() != errorCount.asInt()) { - System.out.println("---- test case failed ----"); - System.out.println("schema: " + schema); - System.out.println("data: " + test.get("data")); - System.out.println("errors: " + errors); - for (ValidationMessage error : errors) { + System.out.println("---- Test Data [" + (i + 1) + "] FAILED [Expected " + + errorCount.asInt() + " Errors but was " + errors.size() + "] ----"); + System.out.println("> Schema: " + schema); + System.out.println("> Data : " + test.get("data")); + System.out.println("> Errors: " + errors); + for (Error error : errors) { System.out.println(error); } assertEquals(errorCount.asInt(), errors.size(), "expected error count"); @@ -110,14 +82,14 @@ private void runTestFile(String testCaseFile) throws Exception { assertFalse(errors.isEmpty()); } } - } catch (JsonSchemaException e) { + } catch (SchemaException e) { throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e); } } } @Test - public void testDiscriminatorMapping() throws Exception { + void testDiscriminatorMapping() throws Exception { runTestFile("openapi3/discriminator.json"); } } diff --git a/src/test/java/com/networknt/schema/OutputFormatTest.java b/src/test/java/com/networknt/schema/OutputFormatTest.java new file mode 100644 index 000000000..4f144f0c6 --- /dev/null +++ b/src/test/java/com/networknt/schema/OutputFormatTest.java @@ -0,0 +1,118 @@ +package com.networknt.schema; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.utils.CachingSupplier; + +class OutputFormatTest { + + private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + private static final String schemaPath1 = "/schema/output-format-schema.json"; + + private JsonNode getJsonNodeFromJsonData(String jsonFilePath) throws Exception { + InputStream content = getClass().getResourceAsStream(jsonFilePath); + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + @DisplayName("Test Validation Messages") + void testInvalidJson() throws Exception { + InputStream schemaInputStream = OutputFormatTest.class.getResourceAsStream(schemaPath1); + Schema schema = factory.getSchema(schemaInputStream); + JsonNode node = getJsonNodeFromJsonData("/data/output-format-input.json"); + List errors = schema.validate(node); + Assertions.assertEquals(3, errors.size()); + + Set messages = errors.stream().map(m -> new String[] { m.getEvaluationPath().toString(), + m.getSchemaLocation().toString(), m.getInstanceLocation().toString(), m.toString() }) + .collect(Collectors.toSet()); + + assertThat(messages, + Matchers.containsInAnyOrder( + new String[] { "/minItems", "https://example.com/polygon#/minItems", "", ": must have at least 3 items but found 2" }, + new String[] { "/items/$ref/additionalProperties", "https://example.com/polygon#/$defs/point/additionalProperties", "/1", + "/1: property 'z' is not defined in the schema and the schema does not allow additional properties" }, + new String[] { "/items/$ref/required", "https://example.com/polygon#/$defs/point/required", "/1", "/1: required property 'y' not found"})); + } + + public static class Detailed implements OutputFormat> { + private Error format(Error message) { + Supplier messageSupplier = () -> { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append(message.getInstanceLocation().toString()); + builder.append("] "); + JsonNode value = message.getInstanceNode(); + if (!value.isObject() && !value.isArray()) { + builder.append("with value "); + builder.append("'"); + builder.append(value.asText()); + builder.append("'"); + builder.append(" "); + } + builder.append(message.getMessage()); + + return builder.toString(); + }; + return Error.builder() + .messageSupplier(new CachingSupplier<>(messageSupplier)) + .evaluationPath(message.getEvaluationPath()) + .instanceLocation(message.getInstanceLocation()) + .instanceNode(message.getInstanceNode()) + .schemaLocation(message.getSchemaLocation()) + .schemaNode(message.getSchemaNode()) + .arguments(message.getArguments()) + .build(); + } + + @Override + public java.util.List format(Schema jsonSchema, + ExecutionContext executionContext, SchemaContext schemaContext) { + return executionContext.getErrors().stream().map(this::format).collect(Collectors.toCollection(ArrayList::new)); + } + } + + public static final OutputFormat> DETAILED = new Detailed(); + + @Test + void customFormat() { + String schemaData = "{\n" + + " \"properties\": {\n" + + " \"type\": {\n" + + " \"enum\": [\n" + + " \"book\",\n" + + " \"author\"\n" + + " ]\n" + + " },\n" + + " \"id\": {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " }\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12) + .getSchema(schemaData, InputFormat.JSON); + String inputData = "{\n" + + " \"type\": \"cat\",\n" + + " \"id\": 1\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON, DETAILED).stream().collect(Collectors.toList()); + assertEquals("[/type] with value 'cat' does not have a value in the enumeration [\"book\", \"author\"]", messages.get(0).getMessage()); + assertEquals("[/id] with value '1' integer found, string expected", messages.get(1).getMessage()); + } +} diff --git a/src/test/java/com/networknt/schema/OutputUnitTest.java b/src/test/java/com/networknt/schema/OutputUnitTest.java new file mode 100644 index 000000000..b10e5a58b --- /dev/null +++ b/src/test/java/com/networknt/schema/OutputUnitTest.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.networknt.schema.output.OutputUnit; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * OutputUnitTest. + * + * @see A + * Specification for Machine-Readable Output for JSON Schema Validation and + * Annotation + */ +class OutputUnitTest { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://json-schema.org/schemas/example\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"title\": \"root\",\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"allOf\": [\r\n" + + " { \"required\": [\"unspecified-prop\"] },\r\n" + + " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"title\": \"foo-title\",\r\n" + + " \"properties\": {\r\n" + + " \"foo-prop\": {\r\n" + + " \"const\": 1,\r\n" + + " \"title\": \"foo-prop-title\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"additionalProperties\": { \"type\": \"boolean\" }\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"bar\": { \"$ref\": \"#/$defs/bar\" }\r\n" + + " },\r\n" + + " \"$defs\": {\r\n" + + " \"bar\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"title\": \"bar-title\",\r\n" + + " \"properties\": {\r\n" + + " \"bar-prop\": {\r\n" + + " \"type\": \"integer\",\r\n" + + " \"minimum\": 10,\r\n" + + " \"title\": \"bar-prop-title\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + String inputData1 = "{\r\n" + + " \"foo\": { \"foo-prop\": \"not 1\", \"other-prop\": false },\r\n" + + " \"bar\": { \"bar-prop\": 2 }\r\n" + + "}"; + + String inputData2 = "{\r\n" + + " \"foo\": {\r\n" + + " \"foo-prop\": 1,\r\n" + + " \"unspecified-prop\": true\r\n" + + " },\r\n" + + " \"bar\": { \"bar-prop\": 20 }\r\n" + + "}"; + @Test + void annotationCollectionList() throws JsonProcessingException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = inputData1; + + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":false,\"details\":[{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/0\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/0\",\"instanceLocation\":\"/foo\",\"errors\":{\"required\":\"required property 'unspecified-prop' not found\"}},{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/1/properties/foo-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1/properties/foo-prop\",\"instanceLocation\":\"/foo/foo-prop\",\"errors\":{\"const\":\"must be the constant value '1'\"},\"droppedAnnotations\":{\"title\":\"foo-prop-title\"}},{\"valid\":false,\"evaluationPath\":\"/properties/bar/$ref/properties/bar-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar/properties/bar-prop\",\"instanceLocation\":\"/bar/bar-prop\",\"errors\":{\"minimum\":\"must have a minimum value of 10\"},\"droppedAnnotations\":{\"title\":\"bar-prop-title\"}},{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/1\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1\",\"instanceLocation\":\"/foo\",\"droppedAnnotations\":{\"properties\":[\"foo-prop\"],\"title\":\"foo-title\",\"additionalProperties\":[\"foo-prop\",\"other-prop\"]}},{\"valid\":false,\"evaluationPath\":\"/properties/bar/$ref\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar\",\"instanceLocation\":\"/bar\",\"droppedAnnotations\":{\"properties\":[\"bar-prop\"],\"title\":\"bar-title\"}},{\"valid\":false,\"evaluationPath\":\"\",\"schemaLocation\":\"https://json-schema.org/schemas/example#\",\"instanceLocation\":\"\",\"droppedAnnotations\":{\"properties\":[\"foo\",\"bar\"],\"title\":\"root\"}}]}"; + assertEquals(expected, output); + } + + @Test + void annotationCollectionHierarchical() throws JsonProcessingException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = inputData1; + + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":false,\"evaluationPath\":\"\",\"schemaLocation\":\"https://json-schema.org/schemas/example#\",\"instanceLocation\":\"\",\"droppedAnnotations\":{\"properties\":[\"foo\",\"bar\"],\"title\":\"root\"},\"details\":[{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/0\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/0\",\"instanceLocation\":\"/foo\",\"errors\":{\"required\":\"required property 'unspecified-prop' not found\"}},{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/1\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1\",\"instanceLocation\":\"/foo\",\"droppedAnnotations\":{\"properties\":[\"foo-prop\"],\"title\":\"foo-title\",\"additionalProperties\":[\"foo-prop\",\"other-prop\"]},\"details\":[{\"valid\":false,\"evaluationPath\":\"/properties/foo/allOf/1/properties/foo-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1/properties/foo-prop\",\"instanceLocation\":\"/foo/foo-prop\",\"errors\":{\"const\":\"must be the constant value '1'\"},\"droppedAnnotations\":{\"title\":\"foo-prop-title\"}}]},{\"valid\":false,\"evaluationPath\":\"/properties/bar/$ref\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar\",\"instanceLocation\":\"/bar\",\"droppedAnnotations\":{\"properties\":[\"bar-prop\"],\"title\":\"bar-title\"},\"details\":[{\"valid\":false,\"evaluationPath\":\"/properties/bar/$ref/properties/bar-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar/properties/bar-prop\",\"instanceLocation\":\"/bar/bar-prop\",\"errors\":{\"minimum\":\"must have a minimum value of 10\"},\"droppedAnnotations\":{\"title\":\"bar-prop-title\"}}]}]}"; + assertEquals(expected, output); + } + + @Test + void annotationCollectionHierarchical2() throws JsonProcessingException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = inputData2; + + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":true,\"evaluationPath\":\"\",\"schemaLocation\":\"https://json-schema.org/schemas/example#\",\"instanceLocation\":\"\",\"annotations\":{\"properties\":[\"foo\",\"bar\"],\"title\":\"root\"},\"details\":[{\"valid\":true,\"evaluationPath\":\"/properties/foo/allOf/1\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1\",\"instanceLocation\":\"/foo\",\"annotations\":{\"properties\":[\"foo-prop\"],\"title\":\"foo-title\",\"additionalProperties\":[\"foo-prop\",\"unspecified-prop\"]},\"details\":[{\"valid\":true,\"evaluationPath\":\"/properties/foo/allOf/1/properties/foo-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/properties/foo/allOf/1/properties/foo-prop\",\"instanceLocation\":\"/foo/foo-prop\",\"annotations\":{\"title\":\"foo-prop-title\"}}]},{\"valid\":true,\"evaluationPath\":\"/properties/bar/$ref\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar\",\"instanceLocation\":\"/bar\",\"annotations\":{\"properties\":[\"bar-prop\"],\"title\":\"bar-title\"},\"details\":[{\"valid\":true,\"evaluationPath\":\"/properties/bar/$ref/properties/bar-prop\",\"schemaLocation\":\"https://json-schema.org/schemas/example#/$defs/bar/properties/bar-prop\",\"instanceLocation\":\"/bar/bar-prop\",\"annotations\":{\"title\":\"bar-prop-title\"}}]}]}"; + assertEquals(expected, output); + } + + enum FormatInput { + DATE_TIME("date-time"), + DATE("date"), + TIME("time"), + DURATION("duration"), + EMAIL("email"), + IDN_EMAIL("idn-email"), + HOSTNAME("hostname"), + IDN_HOSTNAME("idn-hostname"), + IPV4("ipv4"), + IPV6("ipv6"), + URI("uri"), + URI_REFERENCE("uri-reference"), + IRI("iri"), + IRI_REFERENCE("iri-reference"), + UUID("uuid"), + JSON_POINTER("json-pointer"), + RELATIVE_JSON_POINTER("relative-json-pointer"), + REGEX("regex"); + + String format; + + FormatInput(String format) { + this.format = format; + } + } + + @ParameterizedTest + @EnumSource(FormatInput.class) + void formatAnnotation(FormatInput formatInput) { + String formatSchema = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"format\": \""+formatInput.format+"\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(formatSchema); + OutputUnit outputUnit = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + assertTrue(outputUnit.isValid()); + OutputUnit details = outputUnit.getDetails().get(0); + assertEquals(formatInput.format, details.getAnnotations().get("format")); + } + + @ParameterizedTest + @EnumSource(FormatInput.class) + void formatAssertion(FormatInput formatInput) { + String formatSchema = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"format\": \""+formatInput.format+"\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(formatSchema); + OutputUnit outputUnit = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true) + .formatAssertionsEnabled(true)); + }); + assertFalse(outputUnit.isValid()); + OutputUnit details = outputUnit.getDetails().get(0); + assertEquals(formatInput.format, details.getDroppedAnnotations().get("format")); + assertNotNull(details.getErrors().get("format")); + } + + @Test + void typeUnion() { + String typeSchema = "{\r\n" + + " \"type\": [\"string\",\"array\"]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(typeSchema); + OutputUnit outputUnit = schema.validate("1", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> { + executionConfiguration.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + assertFalse(outputUnit.isValid()); + OutputUnit details = outputUnit.getDetails().get(0); + assertNotNull(details.getErrors().get("type")); + } + + @Test + void unevaluatedProperties() throws JsonProcessingException { + Map external = new HashMap<>(); + + String externalSchemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"$id\": \"https://www.example.org/point.json\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\r\n" + + " \"type\",\r\n" + + " \"coordinates\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"enum\": [\r\n" + + " \"Point\"\r\n" + + " ]\r\n" + + " },\r\n" + + " \"coordinates\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"minItems\": 2,\r\n" + + " \"items\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + external.put("https://www.example.org/point.json", externalSchemaData); + + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$ref\": \"https://www.example.org/point.json\",\r\n" + + " \"unevaluatedProperties\": false\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(external))); + Schema schema = factory.getSchema(schemaData); + + // The following checks if the heirarchical output format is correct with multiple unevaluated properties + String inputData = "{\r\n" + + " \"type\": \"Point\",\r\n" + + " \"hello\": \"Point\",\r\n" + + " \"world\": \"Point\",\r\n" + + " \"coordinates\": [1, 1]\r\n" + + "}"; + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, + executionContext -> executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionFilter(keyword -> true))); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":false,\"evaluationPath\":\"\",\"schemaLocation\":\"#\",\"instanceLocation\":\"\",\"errors\":{\"unevaluatedProperties\":[\"property 'hello' is not evaluated and the schema does not allow unevaluated properties\",\"property 'world' is not evaluated and the schema does not allow unevaluated properties\"]},\"droppedAnnotations\":{\"unevaluatedProperties\":[\"hello\",\"world\"]},\"details\":[{\"valid\":false,\"evaluationPath\":\"/$ref\",\"schemaLocation\":\"https://www.example.org/point.json#\",\"instanceLocation\":\"\",\"droppedAnnotations\":{\"properties\":[\"type\",\"coordinates\"]}}]}"; + assertEquals(expected, output); + } + + /** + * Test that anyOf doesn't short circuit if annotations are turned on. + * + * @see anyOf + * @throws JsonProcessingException the exception + */ + @Test + void anyOf() throws JsonProcessingException { + // Test that any of doesn't short circuit if annotations need to be collected + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"anyOf\": [\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"bar\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = "{\r\n" + + " \"foo\": \"hello\",\r\n" + + " \"bar\": 1\r\n" + + "}"; + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + String output = JsonMapperFactory.getInstance().writeValueAsString(outputUnit); + String expected = "{\"valid\":true,\"evaluationPath\":\"\",\"schemaLocation\":\"#\",\"instanceLocation\":\"\",\"details\":[{\"valid\":true,\"evaluationPath\":\"/anyOf/0\",\"schemaLocation\":\"#/anyOf/0\",\"instanceLocation\":\"\",\"annotations\":{\"properties\":[\"foo\"]}},{\"valid\":true,\"evaluationPath\":\"/anyOf/1\",\"schemaLocation\":\"#/anyOf/1\",\"instanceLocation\":\"\",\"annotations\":{\"properties\":[\"bar\"]}}]}"; + assertEquals(expected, output); + } + + @Test + void listAssertionMapper() { + String formatSchema = "{\r\n" + + " \"type\": \"string\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(formatSchema); + OutputUnit outputUnit = schema.validate("1234", InputFormat.JSON, new OutputFormat.List(a -> a)); + assertFalse(outputUnit.isValid()); + OutputUnit details = outputUnit.getDetails().get(0); + Object assertion = details.getErrors().get("type"); + assertInstanceOf(Error.class, assertion); + } + + @Test + void hierarchicalAssertionMapper() { + String formatSchema = "{\r\n" + + " \"type\": \"string\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(formatSchema); + OutputUnit outputUnit = schema.validate("1234", InputFormat.JSON, new OutputFormat.Hierarchical(a -> a)); + assertFalse(outputUnit.isValid()); + Object assertion = outputUnit.getErrors().get("type"); + assertInstanceOf(Error.class, assertion); + } +} diff --git a/src/test/java/com/networknt/schema/OverrideValidatorTest.java b/src/test/java/com/networknt/schema/OverrideValidatorTest.java index c45de942e..786beab44 100644 --- a/src/test/java/com/networknt/schema/OverrideValidatorTest.java +++ b/src/test/java/com/networknt/schema/OverrideValidatorTest.java @@ -19,51 +19,66 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.format.PatternFormat; + import org.junit.jupiter.api.Test; import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class OverrideValidatorTest { +class OverrideValidatorTest { @Test - public void overrideDefaultValidator() throws JsonProcessingException, IOException { + void overrideDefaultValidator() throws JsonProcessingException, IOException { ObjectMapper objectMapper = new ObjectMapper(); final String URI = "https://github.com/networknt/json-schema-validator/tests/schemas/example01"; final String schema = "{\n" + " \"$schema\":\n" + " \"https://github.com/networknt/json-schema-validator/tests/schemas/example01\",\n" + - " \"properties\": {\"mailaddress\": {\"type\": \"string\", \"format\": \"email\"}}\n" + + " \"properties\": {\n" + + " \"mailaddress\": {\"type\": \"string\", \"format\": \"email\"},\n" + + " \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n" + + " }\n" + "}"; - final JsonNode targetNode = objectMapper.readTree("{\"mailaddress\": \"a-zA-Z0-9.!#$%&'*+@a---a.a--a\"}"); + final JsonNode targetNode = objectMapper.readTree("{\n" + + " \"mailaddress\": \"a-zA-Z0-9.!#$%&'*+@a---a.a--a\",\n" + + " \"timestamp\": \"bad\"\n" + + "}"); // Use Default EmailValidator - final JsonMetaSchema validatorMetaSchema = JsonMetaSchema - .builder(URI, JsonMetaSchema.getV201909()) + final Dialect validatorMetaSchema = Dialect + .builder(URI, Dialects.getDraft201909()) .build(); - final JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(validatorMetaSchema).build(); - final JsonSchema validatorSchema = validatorFactory.getSchema(schema); + final SchemaRegistry validatorFactory = SchemaRegistry.withDialect(validatorMetaSchema); + final Schema validatorSchema = validatorFactory.getSchema(schema); - Set messages = validatorSchema.validate(targetNode); - assertEquals(1, messages.size()); + List messages = validatorSchema.validate(targetNode, OutputFormat.DEFAULT, (executionContext, schemaContext) -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + + assertEquals(2, messages.size(), Arrays.toString(messages.toArray())); + assertTrue(messages.stream().anyMatch(it -> it.getInstanceLocation().getName(-1).equals("mailaddress"))); + assertTrue(messages.stream().anyMatch(it -> it.getInstanceLocation().getName(-1).equals("timestamp"))); // Override EmailValidator - final JsonMetaSchema overrideValidatorMetaSchema = JsonMetaSchema - .builder(URI, JsonMetaSchema.getV201909()) - .addFormat(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$")) + final Dialect overrideValidatorMetaSchema = Dialect + .builder(URI, Dialects.getDraft201909()) + .format(PatternFormat.of("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$", "format.email")) .build(); - final JsonSchemaFactory overrideValidatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(overrideValidatorMetaSchema).build(); - final JsonSchema overrideValidatorSchema = overrideValidatorFactory.getSchema(schema); - - messages = overrideValidatorSchema.validate(targetNode); - assertEquals(0, messages.size()); - + final SchemaRegistry overrideValidatorFactory = SchemaRegistry.withDialect(overrideValidatorMetaSchema); + final Schema overrideValidatorSchema = overrideValidatorFactory.getSchema(schema); + messages = overrideValidatorSchema.validate(targetNode, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + assertTrue(messages.stream().anyMatch(it -> it.getInstanceLocation().getName(-1).equals("timestamp"))); + assertEquals(1, messages.size()); } -} +} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/OverwritingCustomMessageBugTest.java b/src/test/java/com/networknt/schema/OverwritingCustomMessageBugTest.java new file mode 100644 index 000000000..3b14f3032 --- /dev/null +++ b/src/test/java/com/networknt/schema/OverwritingCustomMessageBugTest.java @@ -0,0 +1,57 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.path.PathType; +import com.networknt.schema.regex.JDKRegularExpressionFactory; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class OverwritingCustomMessageBugTest { + private Schema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().pathType(PathType.LEGACY) + .errorMessageKeyword("message") + .regularExpressionFactory(JDKRegularExpressionFactory.getInstance()).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); + return factory.getSchema(schemaContent); + } + + private JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + void customMessageIsNotOverwritten() throws Exception { + List errors = validate(); + Map errorMsgMap = transferErrorMsg(errors); + Assertions.assertTrue(errorMsgMap.containsKey("$.toplevel[1].foos"), "error message must contains key: $.foos"); + Assertions.assertTrue(errorMsgMap.containsKey("$.toplevel[1].bars"), "error message must contains key: $.bars"); + Assertions.assertEquals("$.toplevel[1].foos: Must be a string with the a shape foofoofoofoo... with at least one foo", errorMsgMap.get("$.toplevel[1].foos")); + Assertions.assertEquals("$.toplevel[1].bars: Must be a string with the a shape barbarbar... with at least one bar", errorMsgMap.get("$.toplevel[1].bars")); + } + + + private List validate() throws Exception { + String schemaPath = "/schema/OverwritingCustomMessageBug.json"; + String dataPath = "/data/OverwritingCustomMessageBug.json"; + InputStream schemaInputStream = OverwritingCustomMessageBugTest.class.getResourceAsStream(schemaPath); + Schema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + InputStream dataInputStream = OverwritingCustomMessageBugTest.class.getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + return schema.validate(node); + } + + private Map transferErrorMsg(List errors) { + Map pathToMessage = new HashMap<>(); + errors.forEach(msg -> { + pathToMessage.put(msg.getInstanceLocation().toString(), msg.toString()); + }); + return pathToMessage; + } +} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java index 488d3b138..22f8d33d7 100644 --- a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java +++ b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java @@ -17,39 +17,141 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.output.OutputUnit; +import com.networknt.schema.regex.JoniRegularExpressionFactory; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; +import java.util.List; import java.util.Set; /** * Created by steve on 22/10/16. */ -public class PatternPropertiesValidatorTest extends BaseJsonSchemaValidatorTest { +class PatternPropertiesValidatorTest extends BaseJsonSchemaValidatorTest { @Test - public void testInvalidPatternPropertiesValidator() throws Exception { - Assertions.assertThrows(JsonSchemaException.class, () -> { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - JsonSchema schema = factory.getSchema("{\"patternProperties\":6}"); + void testInvalidPatternPropertiesValidator() throws Exception { + Assertions.assertThrows(SchemaException.class, () -> { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + Schema schema = factory.getSchema("{\"patternProperties\":6}"); JsonNode node = getJsonNodeFromStringContent(""); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(errors.size(), 0); }); } @Test - public void testInvalidPatternPropertiesValidatorECMA262() throws Exception { - Assertions.assertThrows(JsonSchemaException.class, () -> { - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setEcma262Validator(true); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - JsonSchema schema = factory.getSchema("{\"patternProperties\":6}", config); + void testInvalidPatternPropertiesValidatorECMA262() throws Exception { + Assertions.assertThrows(SchemaException.class, () -> { + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .regularExpressionFactory(JoniRegularExpressionFactory.getInstance()) + .build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema("{\"patternProperties\":6}"); JsonNode node = getJsonNodeFromStringContent(""); - Set errors = schema.validate(node); + List errors = schema.validate(node); Assertions.assertEquals(errors.size(), 0); }); } + + @Test + void message() { + String schemaData = "{\n" + + " \"$id\": \"https://www.example.org/schema\",\n" + + " \"type\": \"object\",\n" + + " \"patternProperties\": {\n" + + " \"^valid_\": {\n" + + " \"type\": [\"array\", \"string\"],\n" + + " \"items\": {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\n" + + " \"valid_array\": [\"array1_value\", \"array2_value\"],\n" + + " \"valid_string\": \"string_value\",\n" + + " \"valid_key\": 5\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/patternProperties/^valid_/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/patternProperties/^valid_/type", message.getSchemaLocation().toString()); + assertEquals("/valid_key", message.getInstanceLocation().toString()); + assertEquals("[\"array\",\"string\"]", message.getSchemaNode().toString()); + assertEquals("5", message.getInstanceNode().toString()); + assertEquals("/valid_key: integer found, [array, string] expected", message.toString()); + assertNull(message.getProperty()); + + String inputData2 = "{\n" + + " \"valid_array\": [999, 2],\n" + + " \"valid_string\": \"string_value\",\n" + + " \"valid_key\": 5\n" + + "}"; + messages = schema.validate(inputData2, InputFormat.JSON); + assertFalse(messages.isEmpty()); + message = messages.iterator().next(); + assertEquals("/patternProperties/^valid_/items/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/patternProperties/^valid_/items/type", message.getSchemaLocation().toString()); + assertEquals("/valid_array/0", message.getInstanceLocation().toString()); + assertEquals("\"string\"", message.getSchemaNode().toString()); + assertEquals("999", message.getInstanceNode().toString()); + assertEquals("/valid_array/0: integer found, string expected", message.toString()); + assertNull(message.getProperty()); + } + + @SuppressWarnings("unchecked") + @Test + void annotation() { + String schemaData = "{\n" + + " \"$id\": \"https://www.example.org/schema\",\n" + + " \"type\": \"object\",\n" + + " \"patternProperties\": {\n" + + " \"^valid_\": {\n" + + " \"type\": [\"array\", \"string\"],\n" + + " \"items\": {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\n" + + " \"test\": 5\n" + + "}"; + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + Set patternProperties = (Set) outputUnit.getAnnotations().get("patternProperties"); + assertTrue(patternProperties.isEmpty()); + + inputData = "{\n" + + " \"valid_array\": [\"999\", \"2\"],\n" + + " \"valid_string\": \"string_value\"" + + "}"; + outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + patternProperties = (Set) outputUnit.getAnnotations().get("patternProperties"); + Set all = new HashSet<>(); + all.add("valid_array"); + all.add("valid_string"); + assertTrue(patternProperties.containsAll(all)); + } } diff --git a/src/test/java/com/networknt/schema/PatternValidatorTest.java b/src/test/java/com/networknt/schema/PatternValidatorTest.java new file mode 100644 index 000000000..3dd1e3188 --- /dev/null +++ b/src/test/java/com/networknt/schema/PatternValidatorTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * PatternValidatorTest. + */ +class PatternValidatorTest { + @Test + void failFast() { + String schemaData = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"pattern\": \"^(\\\\([0-9]{3}\\\\))?[0-9]{3}-[0-9]{4}$\"\r\n" + + "}"; + String inputData = "\"hello\""; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + boolean result = schema.validate(inputData, InputFormat.JSON, OutputFormat.BOOLEAN); + assertFalse(result); + } + +} diff --git a/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java b/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java new file mode 100644 index 000000000..dc1281286 --- /dev/null +++ b/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java @@ -0,0 +1,231 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.keyword.PrefixItemsValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.ItemWalkListenerRunner; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This class handles exception case for {@link PrefixItemsValidator} + */ +class PrefixItemsValidatorTest extends AbstractJsonSchemaTestSuite { + + /** + * this method create test cases from JSON and run those test cases with assertion + */ + @Test + void testEmptyPrefixItemsException() { + Stream dynamicNodeStream = createTests(SpecificationVersion.DRAFT_7, "src/test/resources/prefixItemsException"); + dynamicNodeStream.forEach( + dynamicNode -> { + assertThrows(SchemaException.class, () -> { + ((DynamicContainer) dynamicNode).getChildren().forEach(dynamicNode1 -> { + }); + }); + } + ); + } + + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"prefixItems\": [{\"type\": \"string\"},{\"type\": \"integer\"}]" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema(schemaData); + String inputData = "[1, \"x\"]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/prefixItems/0/type", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/prefixItems/0/type", message.getSchemaLocation().toString()); + assertEquals("/0", message.getInstanceLocation().toString()); + assertEquals("\"string\"", message.getSchemaNode().toString()); + assertEquals("1", message.getInstanceNode().toString()); + assertEquals("/0: integer found, string expected", message.toString()); + assertNull(message.getProperty()); + } + + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageValid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"prefixItems\": [{\"type\": \"string\"},{\"type\": \"integer\"}]" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema(schemaData); + String inputData = "[\"x\", 1, 1]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + /** + * Tests that the message contains the correct values when there are invalid + * items. + */ + @Test + void messageInvalidAdditionalItems() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"prefixItems\": [{\"type\": \"string\"},{\"type\": \"integer\"}],\r\n" + + " \"items\": false" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema(schemaData); + String inputData = "[\"x\", 1, 1, 2]"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/items", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/items", message.getSchemaLocation().toString()); + assertEquals("", message.getInstanceLocation().toString()); + assertEquals("false", message.getSchemaNode().toString()); + assertEquals("[\"x\",1,1,2]", message.getInstanceNode().toString()); + assertEquals(": index '2' is not defined in the schema and the schema does not allow additional items", message.toString()); + assertNull(message.getProperty()); + } + + @Test + void walkNull() { + String schemaData = "{\n" + + " \"prefixItems\": [\n" + + " {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " {\n" + + " \"type\": \"string\"\n" + + " },\n" + + " {\n" + + " \"type\": \"integer\"\n" + + " }\n" + + " ]\n" + + "}"; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().itemWalkListenerRunner(itemWalkListenerRunner).build(); + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + Result result = schema.walk(null, true, + executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(3, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(0).getKeyword()); + assertEquals("#/prefixItems/0", items.get(0).getSchema().getSchemaLocation().toString()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(1).getKeyword()); + assertEquals("#/prefixItems/1", items.get(1).getSchema().getSchemaLocation().toString()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(2).getKeyword()); + assertEquals("#/prefixItems/2", items.get(2).getSchema().getSchemaLocation().toString()); + } + + @Test + void walkDefaults() throws JsonProcessingException { + String schemaData = "{\n" + + " \"prefixItems\": [\n" + + " {\n" + + " \"type\": \"integer\",\n" + + " \"default\": 1\n" + + " },\n" + + " {\n" + + " \"type\": \"string\",\n" + + " \"default\": \"2\"\n" + + " },\n" + + " {\n" + + " \"type\": \"integer\",\n" + + " \"default\": 3\n" + + " }\n" + + " ]\n" + + "}"; + + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder() + .itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext().getCollectorContext() + .getData().computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }).build(); + WalkConfig walkConfig = WalkConfig.builder().applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)) + .itemWalkListenerRunner(itemWalkListenerRunner).build(); + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + JsonNode input = JsonMapperFactory.getInstance().readTree("[null, null]"); + Result result = schema.walk(input, true, + executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(3, items.size()); + assertEquals("/0", items.get(0).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(0).getKeyword()); + assertEquals("#/prefixItems/0", items.get(0).getSchema().getSchemaLocation().toString()); + assertEquals("/1", items.get(1).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(1).getKeyword()); + assertEquals("#/prefixItems/1", items.get(1).getSchema().getSchemaLocation().toString()); + assertEquals("/2", items.get(2).getInstanceLocation().toString()); + assertEquals("prefixItems", items.get(2).getKeyword()); + assertEquals("#/prefixItems/2", items.get(2).getSchema().getSchemaLocation().toString()); + } +} diff --git a/src/test/java/com/networknt/schema/PropertiesTest.java b/src/test/java/com/networknt/schema/PropertiesTest.java new file mode 100644 index 000000000..e427ceeec --- /dev/null +++ b/src/test/java/com/networknt/schema/PropertiesTest.java @@ -0,0 +1,18 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.util.stream.Stream; + +@DisplayName("Properties") +class PropertiesTest extends AbstractJsonSchemaTestSuite { + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/draft2019-09/properties.json"); + } + +} diff --git a/src/test/java/com/networknt/schema/PropertyNamesValidatorTest.java b/src/test/java/com/networknt/schema/PropertyNamesValidatorTest.java new file mode 100644 index 000000000..e39cc6a42 --- /dev/null +++ b/src/test/java/com/networknt/schema/PropertyNamesValidatorTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * PropertyNamesValidatorTest. + */ +class PropertyNamesValidatorTest { + /** + * Tests that the message contains the correct values when there are invalid + * property names. + */ + @Test + void messageInvalid() { + String schemaData = "{\r\n" + + " \"$id\": \"https://www.example.org/schema\",\r\n" + + " \"propertyNames\": {\"maxLength\": 3}\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"foo\": {},\r\n" + + " \"foobar\": {}\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + Error message = messages.iterator().next(); + assertEquals("/propertyNames", message.getEvaluationPath().toString()); + assertEquals("https://www.example.org/schema#/propertyNames", message.getSchemaLocation().toString()); + assertEquals("", message.getInstanceLocation().toString()); + assertEquals("{\"maxLength\":3}", message.getSchemaNode().toString()); + assertEquals("{\"foo\":{},\"foobar\":{}}", message.getInstanceNode().toString()); + assertEquals(": property 'foobar' name is not valid: must be at most 3 characters long", message.toString()); + assertEquals("foobar", message.getProperty()); + } +} diff --git a/src/test/java/com/networknt/schema/QuickStartTest.java b/src/test/java/com/networknt/schema/QuickStartTest.java new file mode 100644 index 000000000..6b10c883d --- /dev/null +++ b/src/test/java/com/networknt/schema/QuickStartTest.java @@ -0,0 +1,196 @@ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.serialization.JsonMapperFactory; + +/** + * Quick start test. + */ +class QuickStartTest { + @Test + void addressExample() { + String schemaData = "{\r\n" + + " \"$id\": \"https://example.com/address.schema.json\",\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"description\": \"An address similar to http://microformats.org/wiki/h-card\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"postOfficeBox\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"extendedAddress\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"streetAddress\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"locality\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"region\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"postalCode\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"countryName\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [ \"locality\", \"region\", \"countryName\" ],\r\n" + + " \"dependentRequired\": {\r\n" + + " \"postOfficeBox\": [ \"streetAddress\" ],\r\n" + + " \"extendedAddress\": [ \"streetAddress\" ]\r\n" + + " }\r\n" + + "}\r\n" + + "\r\n" + + ""; + String instanceData = "{\r\n" + + " \"postOfficeBox\": \"123\",\r\n" + + " \"streetAddress\": \"456 Main St\",\r\n" + + " \"locality\": \"Cityville\",\r\n" + + " \"region\": \"State\",\r\n" + + " \"postalCode\": \"12345\",\r\n" + + " \"countryName\": \"Country\"\r\n" + + "}"; + Map schemas = new HashMap<>(); + schemas.put("https://example.com/address.schema.json", schemaData); + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012(), + builder -> builder.schemas(schemas)); + Schema schema = schemaRegistry.getSchema(SchemaLocation.of("https://example.com/address.schema.json")); + List errors = schema.validate(instanceData, InputFormat.JSON); + assertEquals(0, errors.size()); + + String invalidInstanceData = "{\r\n" + + " \"postOfficeBox\": \"123\",\r\n" + + " \"streetAddress\": \"456 Main St\",\r\n" + + " \"region\": \"State\",\r\n" + + " \"postalCode\": \"12345\",\r\n" + + " \"countryName\": \"Country\"\r\n" + + "}"; + errors = schema.validate(invalidInstanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + assertEquals("required", errors.get(0).getKeyword()); + } + + @Test + void schemaFromSchemaLocationMapping() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers + .mapPrefix("https://www.example.com/schema", "classpath:schema"))); + /* + * This should be cached for performance. + */ + Schema schemaFromSchemaLocation = schemaRegistry + .getSchema(SchemaLocation.of("https://www.example.com/schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromSchemaLocation.initializeValidators(); + List errors = schemaFromSchemaLocation.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(1, errors.size()); + } + + @Test + void schemaFromSchemaLocationContent() { + String schemaData = "{\"enum\":[1, 2, 3, 4]}"; + + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemas( + Collections.singletonMap("https://www.example.com/schema/example-ref.json", schemaData))); + /* + * This should be cached for performance. + */ + Schema schemaFromSchemaLocation = schemaRegistry + .getSchema(SchemaLocation.of("https://www.example.com/schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromSchemaLocation.initializeValidators(); + List errors = schemaFromSchemaLocation.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(1, errors.size()); + } + + @Test + void schemaFromClasspath() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + /* + * This should be cached for performance. + * + * Loading from using the retrieval IRI is not recommended as it may cause + * confusing when resolving relative $ref when $id is also used. + */ + Schema schemaFromClasspath = schemaRegistry.getSchema(SchemaLocation.of("classpath:schema/example-ref.json")); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromClasspath.initializeValidators(); + List errors = schemaFromClasspath.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(1, errors.size()); + } + + @Test + void schemaFromString() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + /* + * This should be cached for performance. + * + * Loading from a String is not recommended as there is no base IRI to use for + * resolving relative $ref. + */ + Schema schemaFromString = schemaRegistry.getSchema("{\"enum\":[1, 2, 3, 4]}"); + List errors = schemaFromString.validate("7", InputFormat.JSON, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(1, errors.size()); + } + + @Test + void schemaFromJsonNode() throws JsonProcessingException { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + JsonNode schemaNode = JsonMapperFactory.getInstance().readTree( + "{\"$schema\": \"http://json-schema.org/draft-06/schema#\", \"properties\": { \"id\": {\"type\": \"number\"}}}"); + /* + * This should be cached for performance. + * + * Loading from a JsonNode is not recommended as there is no base IRI to use for + * resolving relative $ref. + * + * Note that the V202012 from the schemaRegistry is the default version if $schema is + * not specified. As $schema is specified in the data, V6 is used. + */ + Schema schemaFromNode = schemaRegistry.getSchema(schemaNode); + /* + * By default all schemas are preloaded eagerly but ref resolve failures are not + * thrown. You check if there are issues with ref resolving using + * initializeValidators() + */ + schemaFromNode.initializeValidators(); + List errors = schemaFromNode.validate("{\"id\": \"2\"}", InputFormat.JSON, + executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(1, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/ReadOnlyValidatorTest.java b/src/test/java/com/networknt/schema/ReadOnlyValidatorTest.java new file mode 100644 index 000000000..f9fab9dbb --- /dev/null +++ b/src/test/java/com/networknt/schema/ReadOnlyValidatorTest.java @@ -0,0 +1,56 @@ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +class ReadOnlyValidatorTest { + + @Test + void givenConfigWriteFalseWhenReadOnlyTrueThenAllows() throws IOException { + ObjectNode node = getJsonNode(); + List errors = loadJsonSchema().validate(node, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.readOnly(false))); + assertTrue(errors.isEmpty()); + } + + @Test + void givenConfigWriteTrueWhenReadOnlyTrueThenDenies() throws IOException { + ObjectNode node = getJsonNode(); + List errors = loadJsonSchema().validate(node, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.readOnly(true))); + assertFalse(errors.isEmpty()); + assertEquals("/firstName: is a readonly field, it cannot be changed", + errors.stream().map(e -> e.toString()).collect(Collectors.toList()).get(0)); + } + + private Schema loadJsonSchema() { + Schema schema = this.getJsonSchema(); + schema.initializeValidators(); + return schema; + + } + + private Schema getJsonSchema() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + InputStream schema = getClass().getClassLoader().getResourceAsStream("schema/read-only-schema.json"); + return factory.getSchema(schema); + } + + private ObjectNode getJsonNode() throws IOException { + InputStream node = getClass().getClassLoader().getResourceAsStream("data/read-only-data.json"); + ObjectMapper mapper = new ObjectMapper(); + return (ObjectNode) mapper.readTree(node); + } + +} diff --git a/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java b/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java new file mode 100644 index 000000000..2b53ac17e --- /dev/null +++ b/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java @@ -0,0 +1,36 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.keyword.RecursiveRefValidator; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * This class handles exception case for {@link RecursiveRefValidator}, + * as recursive ref is detected through #, providing any other keyword throws exception + */ +class RecursiveReferenceValidatorExceptionTest extends AbstractJsonSchemaTestSuite { + + + /** + * this method create test case for handling invalid recursive reference error + */ + @Test + void testInvalidRecursiveReference() { + // Arrange + String invalidSchemaJson = "{ \"$recursiveRef\": \"invalid\" }"; + SchemaRegistry jsonSchemaFactory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema jsonSchema = jsonSchemaFactory.getSchema(invalidSchemaJson); + JsonNode schemaNode = jsonSchema.getSchemaNode(); + SchemaContext schemaContext = new SchemaContext(jsonSchema.getSchemaContext().getDialect(), + jsonSchemaFactory); + + // Act and Assert + assertThrows(SchemaException.class, () -> { + new RecursiveRefValidator(SchemaLocation.of(""), schemaNode, null, schemaContext); + }); + } + +} diff --git a/src/test/java/com/networknt/schema/RefTest.java b/src/test/java/com/networknt/schema/RefTest.java new file mode 100644 index 000000000..ddba8bf53 --- /dev/null +++ b/src/test/java/com/networknt/schema/RefTest.java @@ -0,0 +1,65 @@ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.networknt.schema.dialect.DialectId; + +class RefTest { + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().build(); + + @Test + void shouldLoadRelativeClasspathReference() throws JsonProcessingException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(SchemaLocation.of("classpath:///schema/ref-main.json")); + String input = "{\r\n" + + " \"DriverProperties\": {\r\n" + + " \"CommonProperties\": {\r\n" + + " \"field2\": \"abc-def-xyz\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + assertEquals(DialectId.DRAFT_4, schema.getSchemaContext().getDialect().getId()); + List errors = schema.validate(OBJECT_MAPPER.readTree(input)); + assertEquals(1, errors.size()); + Error error = errors.iterator().next(); + assertEquals("classpath:///schema/ref-ref.json#/definitions/DriverProperties/required", + error.getSchemaLocation().toString()); + assertEquals("/properties/DriverProperties/properties/CommonProperties/$ref/required", + error.getEvaluationPath().toString()); + assertEquals("field1", error.getProperty()); + } + + @Test + void shouldLoadSchemaResource() throws JsonProcessingException { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(SchemaLocation.of("classpath:///schema/ref-main-schema-resource.json")); + String input = "{\r\n" + + " \"DriverProperties\": {\r\n" + + " \"CommonProperties\": {\r\n" + + " \"field2\": \"abc-def-xyz\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + assertEquals(DialectId.DRAFT_4, schema.getSchemaContext().getDialect().getId()); + List errors = schema.validate(OBJECT_MAPPER.readTree(input)); + assertEquals(1, errors.size()); + Error error = errors.iterator().next(); + assertEquals("https://www.example.org/common#/definitions/DriverProperties/required", + error.getSchemaLocation().toString()); + assertEquals("/properties/DriverProperties/properties/CommonProperties/$ref/required", + error.getEvaluationPath().toString()); + assertEquals("field1", error.getProperty()); + Schema driver = schema.getSchemaContext().getSchemaResources().get("https://www.example.org/driver#"); + Schema common = schema.getSchemaContext().getSchemaResources().get("https://www.example.org/common#"); + assertEquals(DialectId.DRAFT_4, driver.getSchemaContext().getDialect().getId()); + assertEquals(DialectId.DRAFT_7, common.getSchemaContext().getDialect().getId()); + + } +} diff --git a/src/test/java/com/networknt/schema/RefValidatorTest.java b/src/test/java/com/networknt/schema/RefValidatorTest.java new file mode 100644 index 000000000..fff84afa8 --- /dev/null +++ b/src/test/java/com/networknt/schema/RefValidatorTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Tests for RefValidator. + */ +class RefValidatorTest { + @Test + void resolveSamePathDotSlash() { + String mainSchema = "{\r\n" + + " \"$id\": \"https://www.example.com/schema/test.json\",\r\n" + + " \"$ref\": \"./integer.json\"\r\n" + + "}"; + + String otherSchema = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources( + Collections.singletonMap("https://www.example.com/schema/integer.json", otherSchema)))); + Schema jsonSchema = factory.getSchema(mainSchema); + List messages = jsonSchema.validate("\"string\"", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void resolveSamePath() { + String mainSchema = "{\r\n" + + " \"$id\": \"https://www.example.com/schema/test.json\",\r\n" + + " \"$ref\": \"integer.json\"\r\n" + + "}"; + + String otherSchema = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources( + Collections.singletonMap("https://www.example.com/schema/integer.json", otherSchema)))); + Schema jsonSchema = factory.getSchema(mainSchema); + List messages = jsonSchema.validate("\"string\"", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void resolveParent() { + String mainSchema = "{\r\n" + + " \"$id\": \"https://www.example.com/schema/test.json\",\r\n" + + " \"$ref\": \"../integer.json\"\r\n" + + "}"; + + String otherSchema = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources( + Collections.singletonMap("https://www.example.com/integer.json", otherSchema)))); + Schema jsonSchema = factory.getSchema(mainSchema); + List messages = jsonSchema.validate("\"string\"", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void resolveComplex() { + String mainSchema = "{\r\n" + + " \"$id\": \"https://www.example.com/schema/test.json\",\r\n" + + " \"$ref\": \"./hello/././world/../integer.json\"\r\n" + + "}"; + + String otherSchema = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources( + Collections.singletonMap("https://www.example.com/schema/hello/integer.json", otherSchema)))); + Schema jsonSchema = factory.getSchema(mainSchema); + List messages = jsonSchema.validate("\"string\"", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void classPathSlash() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(SchemaLocation.of("classpath:/schema/main/main.json")); + String inputData = "{\r\n" + + " \"fields\": {\r\n" + + " \"ids\": {\r\n" + + " \"value\": {\r\n" + + " \"value\": 1\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + assertFalse(schema.validate(inputData, InputFormat.JSON, OutputFormat.BOOLEAN)); + } + + @Test + void classPathNoSlash() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09); + Schema schema = factory.getSchema(SchemaLocation.of("classpath:schema/main/main.json")); + String inputData = "{\r\n" + + " \"fields\": {\r\n" + + " \"ids\": {\r\n" + + " \"value\": {\r\n" + + " \"value\": 1\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + assertFalse(schema.validate(inputData, InputFormat.JSON, OutputFormat.BOOLEAN)); + } +} diff --git a/src/test/java/com/networknt/schema/RequiredValidatorTest.java b/src/test/java/com/networknt/schema/RequiredValidatorTest.java new file mode 100644 index 000000000..ba542c46f --- /dev/null +++ b/src/test/java/com/networknt/schema/RequiredValidatorTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * RequiredValidatorTest. + */ +class RequiredValidatorTest { + /** + * Tests that when validating requests with required read only properties they + * are ignored. + */ + @Test + void validateRequestRequiredReadOnlyShouldBeIgnored() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"amount\": {\r\n" + + " \"type\": \"number\",\r\n" + + " \"writeOnly\": true\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"readOnly\": true\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"amount\",\r\n" + + " \"description\",\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"foo\":\"hello\",\r\n" + + " \"bar\":\"world\"\r\n" + + "}"; + List messages = new ArrayList<>( + schema.validate(inputData, InputFormat.JSON, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.readOnly(true)))); + assertEquals(messages.size(), 2); + Error message = messages.get(0); + assertEquals("/required", message.getEvaluationPath().toString()); + assertEquals("amount", message.getProperty()); + message = messages.get(1); + assertEquals("/required", message.getEvaluationPath().toString()); + assertEquals("description", message.getProperty()); + } + + /** + * Tests that when validating responses with required write only properties they + * are ignored. + */ + @Test + void validateResponseRequiredWriteOnlyShouldBeIgnored() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"amount\": {\r\n" + + " \"type\": \"number\",\r\n" + + " \"writeOnly\": true\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"readOnly\": true\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"amount\",\r\n" + + " \"description\",\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"foo\":\"hello\",\r\n" + + " \"bar\":\"world\"\r\n" + + "}"; + List messages = new ArrayList<>( + schema.validate(inputData, InputFormat.JSON, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.writeOnly(true)))); + assertEquals(messages.size(), 2); + Error message = messages.get(0); + assertEquals("/required", message.getEvaluationPath().toString()); + assertEquals("description", message.getProperty()); + message = messages.get(1); + assertEquals("/required", message.getEvaluationPath().toString()); + assertEquals("name", message.getProperty()); + } + + /** + * Tests that when validating requests with required read only properties they + * are ignored. + */ + @Test + void validateRequestRequired() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"amount\": {\r\n" + + " \"type\": \"number\",\r\n" + + " \"writeOnly\": true\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"readOnly\": true\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"amount\",\r\n" + + " \"description\",\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"amount\":10,\r\n" + + " \"description\":\"world\"\r\n" + + "}"; + List messages = new ArrayList<>( + schema.validate(inputData, InputFormat.JSON, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.readOnly(true)))); + assertEquals(messages.size(), 0); + } + + /** + * Tests that when validating response with required write only properties they + * are ignored. + */ + @Test + void validateResponseRequired() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"amount\": {\r\n" + + " \"type\": \"number\",\r\n" + + " \"writeOnly\": true\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"readOnly\": true\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"amount\",\r\n" + + " \"description\",\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + String inputData = "{\r\n" + + " \"description\":\"world\",\r\n" + + " \"name\":\"hello\"\r\n" + + "}"; + List messages = new ArrayList<>( + schema.validate(inputData, InputFormat.JSON, executionContext -> executionContext + .executionConfig(executionConfig -> executionConfig.writeOnly(true)))); + assertEquals(messages.size(), 0); + } +} diff --git a/src/test/java/com/networknt/schema/SchemaLocationTest.java b/src/test/java/com/networknt/schema/SchemaLocationTest.java new file mode 100644 index 000000000..fc14ce2e2 --- /dev/null +++ b/src/test/java/com/networknt/schema/SchemaLocationTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.path.NodePath; + +class SchemaLocationTest { + + @Test + void ofAbsoluteIri() { + SchemaLocation schemaLocation = SchemaLocation.of("https://json-schema.org/draft/2020-12/schema"); + assertEquals("https://json-schema.org/draft/2020-12/schema", schemaLocation.getAbsoluteIri().toString()); + assertEquals(0, schemaLocation.getFragment().getNameCount()); + assertEquals("https://json-schema.org/draft/2020-12/schema#", schemaLocation.toString()); + } + + @Test + void ofAbsoluteIriWithJsonPointer() { + SchemaLocation schemaLocation = SchemaLocation.of("https://json-schema.org/draft/2020-12/schema#/properties/0"); + assertEquals("https://json-schema.org/draft/2020-12/schema", schemaLocation.getAbsoluteIri().toString()); + assertEquals(2, schemaLocation.getFragment().getNameCount()); + assertEquals("https://json-schema.org/draft/2020-12/schema#/properties/0", schemaLocation.toString()); + assertEquals("/properties/0", schemaLocation.getFragment().toString()); + } + + @Test + void ofAbsoluteIriWithAnchor() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address", schemaLocation.getAbsoluteIri().toString()); + assertEquals(1, schemaLocation.getFragment().getNameCount()); + assertEquals("https://example.com/schemas/address#street_address", schemaLocation.toString()); + assertEquals("street_address", schemaLocation.getFragment().toString()); + } + + @Test + void ofDocument() { + assertEquals(SchemaLocation.DOCUMENT, SchemaLocation.of("#")); + } + + @Test + void document() { + assertEquals(SchemaLocation.DOCUMENT.toString(), "#"); + } + + @Test + void schemaLocationResolveDocument() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#", SchemaLocation.resolve(schemaLocation, "#")); + } + + @Test + void schemaLocationResolveDocumentPointer() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#/allOf/12/properties", + SchemaLocation.resolve(schemaLocation, "#/allOf/12/properties")); + } + + @Test + void schemaLocationResolveHashInFragment() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals( + "https://example.com/schemas/address#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema", + SchemaLocation.resolve(schemaLocation, + "#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema")); + } + + @Test + void schemaLocationResolvePathHashInFragment() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals( + "https://example.com/schemas/hello#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema", + SchemaLocation.resolve(schemaLocation, + "hello#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema")); + } + + @Test + void schemaLocationResolveEmptyString() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#", SchemaLocation.resolve(schemaLocation, "")); + } + + @Test + void schemaLocationResolveRelative() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/test#", SchemaLocation.resolve(schemaLocation, "test")); + } + + @Test + void schemaLocationResolveRelativeIndex() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address/#street_address"); + assertEquals("https://example.com/schemas/address/test#", SchemaLocation.resolve(schemaLocation, "test")); + } + + @Test + void resolveDocument() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#", schemaLocation.resolve("#").toString()); + } + + @Test + void resolveDocumentPointer() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#/allOf/10/properties", + schemaLocation.resolve("#/allOf/10/properties").toString()); + } + + @Test + void resolveHashInFragment() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals( + "https://example.com/schemas/address#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema", + schemaLocation.resolve( + "#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema") + .toString()); + } + + @Test + void resolvePathHashInFragment() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals( + "https://example.com/schemas/hello#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema", + schemaLocation.resolve( + "hello#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema") + .toString()); + } + + @Test + void resolveEmptyString() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/address#", schemaLocation.resolve("").toString()); + } + + @Test + void resolveRelative() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address#street_address"); + assertEquals("https://example.com/schemas/test#", schemaLocation.resolve("test").toString()); + } + + @Test + void resolveRelativeIndex() { + SchemaLocation schemaLocation = SchemaLocation.of("https://example.com/schemas/address/#street_address"); + assertEquals("https://example.com/schemas/address/test#", schemaLocation.resolve("test").toString()); + } + + @Test + void resolveNull() { + SchemaLocation schemaLocation = new SchemaLocation(null); + assertEquals("test#", schemaLocation.resolve("test").toString()); + } + + @Test + void build() { + SchemaLocation schemaLocation = SchemaLocation.builder().absoluteIri("https://example.com/schemas/address/") + .fragment("/allOf/10/properties").build(); + assertEquals("https://example.com/schemas/address/#/allOf/10/properties", schemaLocation.toString()); + assertEquals("https://example.com/schemas/address/", schemaLocation.getAbsoluteIri().toString()); + assertEquals("/allOf/10/properties", schemaLocation.getFragment().toString()); + } + + @Test + void append() { + SchemaLocation schemaLocation = SchemaLocation.builder().absoluteIri("https://example.com/schemas/address/") + .build().append("allOf").append(10).append("properties"); + assertEquals("https://example.com/schemas/address/#/allOf/10/properties", schemaLocation.toString()); + assertEquals("https://example.com/schemas/address/", schemaLocation.getAbsoluteIri().toString()); + assertEquals("/allOf/10/properties", schemaLocation.getFragment().toString()); + } + + @Test + void anchorFragment() { + assertTrue(SchemaLocation.Fragment.isAnchorFragment("#test")); + assertFalse(SchemaLocation.Fragment.isAnchorFragment("#")); + assertFalse(SchemaLocation.Fragment.isAnchorFragment("#/allOf/10/properties")); + assertFalse(SchemaLocation.Fragment.isAnchorFragment("")); + } + + @Test + void jsonPointerFragment() { + assertTrue(SchemaLocation.Fragment.isJsonPointerFragment("#/allOf/10/properties")); + assertFalse(SchemaLocation.Fragment.isJsonPointerFragment("#")); + assertFalse(SchemaLocation.Fragment.isJsonPointerFragment("#test")); + } + + @Test + void fragment() { + assertTrue(SchemaLocation.Fragment.isFragment("#/allOf/10/properties")); + assertTrue(SchemaLocation.Fragment.isFragment("#test")); + assertFalse(SchemaLocation.Fragment.isFragment("test")); + } + + @Test + void documentFragment() { + assertFalse(SchemaLocation.Fragment.isDocumentFragment("#/allOf/10/properties")); + assertFalse(SchemaLocation.Fragment.isDocumentFragment("#test")); + assertFalse(SchemaLocation.Fragment.isDocumentFragment("test")); + assertTrue(SchemaLocation.Fragment.isDocumentFragment("#")); + } + + @Test + void shouldLoadEscapedFragment() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(SchemaLocation + .of("classpath:schema/example-escaped.yaml#/paths/~1users/post/requestBody/application~1json/schema")); + List result = schema.validate("1", InputFormat.JSON); + assertFalse(result.isEmpty()); + result = schema.validate("{}", InputFormat.JSON); + assertTrue(result.isEmpty()); + } + + @Test + void escapedJsonPointerFragment() { + NodePath fragment = SchemaLocation.Fragment.of("/paths/~1users/post/requestBody/application~1json/schema"); + assertEquals("/paths/~1users/post/requestBody/application~1json/schema", fragment.toString()); + assertEquals(6, fragment.getNameCount()); + assertEquals("paths", fragment.getName(0)); + assertEquals("/users", fragment.getName(1)); + assertEquals("post", fragment.getName(2)); + assertEquals("requestBody", fragment.getName(3)); + assertEquals("application/json", fragment.getName(4)); + assertEquals("schema", fragment.getName(5)); + } + + @Test + void ofNull() { + assertNull(SchemaLocation.of(null)); + } + + @Test + void ofEmptyString() { + SchemaLocation schemaLocation = SchemaLocation.of(""); + assertEquals("", schemaLocation.getAbsoluteIri().toString()); + assertEquals("#", schemaLocation.toString()); + } + + @Test + void newNull() { + SchemaLocation schemaLocation = new SchemaLocation(null); + assertEquals("#", schemaLocation.toString()); + } + + @Test + void equalsEquals() { + assertEquals(SchemaLocation.of("https://example.com/schemas/address/#street_address"), + SchemaLocation.of("https://example.com/schemas/address/#street_address")); + } + + @Test + void hashCodeEquals() { + assertEquals(SchemaLocation.of("https://example.com/schemas/address/#street_address").hashCode(), + SchemaLocation.of("https://example.com/schemas/address/#street_address").hashCode()); + } + + @Test + void hashInFragment() { + SchemaLocation location = SchemaLocation.of("https://example.com/example.yaml#/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema"); + assertEquals("/paths/~1subscribe/post/callbacks/myEvent/{request.body#~1callbackUrl}/post/requestBody/content/application~1json/schema", location.getFragment().toString()); + } + + @Test + void trailingHash() { + SchemaLocation location = SchemaLocation.of("https://example.com/example.yaml#"); + assertEquals("", location.getFragment().toString()); + } + + @Test + void equalsEqualsNoFragment() { + assertEquals(SchemaLocation.of("https://example.com/example.yaml#"), + SchemaLocation.of("https://example.com/example.yaml")); + } + + @Test + void equalsEqualsNoFragmentToString() { + assertEquals(SchemaLocation.of("https://example.com/example.yaml#").toString(), + SchemaLocation.of("https://example.com/example.yaml").toString()); + } + +} diff --git a/src/test/java/com/networknt/schema/SchemaRegistryTest.java b/src/test/java/com/networknt/schema/SchemaRegistryTest.java new file mode 100644 index 000000000..4a569eb47 --- /dev/null +++ b/src/test/java/com/networknt/schema/SchemaRegistryTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.dialect.Dialect; + +/** + * Tests for JsonSchemaFactory. + */ +class SchemaRegistryTest { + @Test + void concurrency() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://www.example.com/vocab/validation\": false,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData)))); + AtomicBoolean failed = new AtomicBoolean(false); + Dialect[] instance = new Dialect[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + Dialect dialect = factory.getDialect("https://www.example.com/no-validation-no-format/schema"); + synchronized(instance) { + if (instance[0] == null) { + instance[0] = dialect; + } + // Ensure references are the same despite concurrency + if (!(instance[0] == dialect)) { + failed.set(true); + } + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + assertFalse(failed.get()); + } +} diff --git a/src/test/java/com/networknt/schema/SchemaTest.java b/src/test/java/com/networknt/schema/SchemaTest.java new file mode 100644 index 000000000..ff1ac400b --- /dev/null +++ b/src/test/java/com/networknt/schema/SchemaTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +/** + * Tests for JsonSchemaFactory. + */ +class SchemaTest { + @Test + void concurrency() throws Exception { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"$id\": \"http://example.org/schema.json\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"$ref\": \"ref.json\"\r\n" + + "}"; + String refSchemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"$id\": \"http://example.org/ref.json\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"description\": \"The name\"\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"name\": 1\r\n" + + "}"; + SchemaRegistryConfig config = SchemaRegistryConfig.builder().preloadSchema(false).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaRegistryConfig(config) + .resourceLoaders(resourceLoaders -> resourceLoaders + .resources(Collections.singletonMap("http://example.org/ref.json", refSchemaData)))); + Schema schema = factory.getSchema(schemaData); + Exception[] instance = new Exception[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + List messages = schema.validate(inputData, InputFormat.JSON) + .stream() + .collect(Collectors.toList()); + assertEquals(1, messages.size()); + } catch(Exception e) { + synchronized(instance) { + instance[0] = e; + } + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + if (instance[0] != null) { + throw instance[0]; + } + } +} diff --git a/src/test/java/com/networknt/schema/SelfRefTest.java b/src/test/java/com/networknt/schema/SelfRefTest.java index feb60e681..75ae08d9e 100644 --- a/src/test/java/com/networknt/schema/SelfRefTest.java +++ b/src/test/java/com/networknt/schema/SelfRefTest.java @@ -22,11 +22,11 @@ /** * Created by stevehu on 2016-12-20. */ -public class SelfRefTest extends BaseJsonSchemaValidatorTest { +class SelfRefTest extends BaseJsonSchemaValidatorTest { @Disabled("This test currently is failing because of a StackOverflow caused by a recursive $ref.") @Test() - public void testSelfRef() throws Exception { - JsonSchema node = getJsonSchemaFromClasspath("selfRef.json"); + void testSelfRef() throws Exception { + Schema node = getJsonSchemaFromClasspath("selfRef.json"); System.out.println("node = " + node); } } \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/SharedConfigTest.java b/src/test/java/com/networknt/schema/SharedConfigTest.java new file mode 100644 index 000000000..edd62b88d --- /dev/null +++ b/src/test/java/com/networknt/schema/SharedConfigTest.java @@ -0,0 +1,58 @@ +package com.networknt.schema; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.walk.WalkListener; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; + +/** + * Issue 918. + */ +class SharedConfigTest { + private static class AllKeywordListener implements WalkListener { + boolean wasCalled = false; + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + wasCalled = true; + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + } + + @Test + void shouldCallAllKeywordListenerOnWalkStart() throws Exception { + + AllKeywordListener allKeywordListener = new AllKeywordListener(); + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(allKeywordListener).build(); + WalkConfig walkConfig = WalkConfig.builder().keywordWalkListenerRunner(keywordWalkListenerRunner).build(); + + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + + SchemaLocation draft07Schema = SchemaLocation.of("resource:/draft-07/schema#"); + + // depending on this line the test either passes or fails: + // - if this line is executed, then it passes + // - if this line is not executed (just comment it) - it fails + Schema firstSchema = factory.getSchema(draft07Schema); + firstSchema.walk(new ObjectMapper().readTree("{ \"id\": 123 }"), true); + + // note that only second schema takes overridden schemaValidatorsConfig + Schema secondSchema = factory.getSchema(draft07Schema); + + secondSchema.walk(new ObjectMapper().readTree("{ \"id\": 123 }"), true, + executionContext -> executionContext.setWalkConfig(walkConfig)); + Assertions.assertTrue(allKeywordListener.wasCalled); + } +} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java b/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java deleted file mode 100644 index f49da3a77..000000000 --- a/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class SpecVersionDetectorTest { - - private static final String SCHEMA_TAG_JSON = "schemaTag.json"; - - private static final ObjectMapper mapper = new ObjectMapper(); - - @Test - public void detectV4() throws IOException { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("draft4/" + SCHEMA_TAG_JSON); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - assertEquals(SpecVersion.VersionFlag.V4, flag); - } - - @Test - public void detectV6() throws IOException { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("draft6/" + SCHEMA_TAG_JSON); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - assertEquals(SpecVersion.VersionFlag.V6, flag); - } - - @Test - public void detectV7() throws IOException { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("draft7/" + SCHEMA_TAG_JSON); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - assertEquals(SpecVersion.VersionFlag.V7, flag); - } - - @Test - public void detectV201909() throws IOException { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("draft2019-09/" + SCHEMA_TAG_JSON); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - assertEquals(SpecVersion.VersionFlag.V201909, flag); - } - - @Test - public void detectUnsupportedSchemaVersion() throws IOException { - assertThrows(JsonSchemaException.class, () -> { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("data/" + SCHEMA_TAG_JSON); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - }); - } - - @Test - public void detectMissingSchemaVersion() throws IOException { - assertThrows(JsonSchemaException.class, () -> { - InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("data/" + "schemaTagMissing.json"); - JsonNode node = mapper.readTree(in); - SpecVersion.VersionFlag flag = SpecVersionDetector.detect(node); - }); - } - -} \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/SpecVersionTest.java b/src/test/java/com/networknt/schema/SpecVersionTest.java deleted file mode 100644 index 05dad7771..000000000 --- a/src/test/java/com/networknt/schema/SpecVersionTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.EnumSet; -import java.util.Set; - -public class SpecVersionTest { - @Test - public void testGetVersionValue() { - SpecVersion ds = new SpecVersion(); - Set versionFlags = EnumSet.of( - SpecVersion.VersionFlag.V4, - SpecVersion.VersionFlag.V201909); - Assertions.assertEquals(ds.getVersionValue(versionFlags), 9); // 0001|1000 - } - - @Test - public void testGetVersionFlags() { - SpecVersion ds = new SpecVersion(); - - long numericVersionCode = SpecVersion.VersionFlag.V201909.getVersionFlagValue() - | SpecVersion.VersionFlag.V6.getVersionFlagValue() - | SpecVersion.VersionFlag.V7.getVersionFlagValue(); // 14 - - Set versionFlags = ds.getVersionFlags(numericVersionCode); - - assert !versionFlags.contains(SpecVersion.VersionFlag.V4); - assert versionFlags.contains(SpecVersion.VersionFlag.V6); - assert versionFlags.contains(SpecVersion.VersionFlag.V7); - assert versionFlags.contains(SpecVersion.VersionFlag.V201909); - - } - - @Test - public void testAllVersionValue() { - long numericVersionCode = - SpecVersion.VersionFlag.V201909.getVersionFlagValue() - | SpecVersion.VersionFlag.V4.getVersionFlagValue() - | SpecVersion.VersionFlag.V6.getVersionFlagValue() - | SpecVersion.VersionFlag.V7.getVersionFlagValue(); // 15 - Assertions.assertEquals(numericVersionCode, 15); - - } -} diff --git a/src/test/java/com/networknt/schema/SpecificationVersionDetector.java b/src/test/java/com/networknt/schema/SpecificationVersionDetector.java new file mode 100644 index 000000000..73c89678f --- /dev/null +++ b/src/test/java/com/networknt/schema/SpecificationVersionDetector.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * This class is used to detect schema version + * + * @author Subhajitdas298 + * @since 25/06/20 + */ +public final class SpecificationVersionDetector { + + private static final Map supportedVersions = new HashMap<>(); + private static final String SCHEMA_TAG = "$schema"; + + static { + supportedVersions.put("draft2019-09", SpecificationVersion.DRAFT_2019_09); + supportedVersions.put("draft2020-12", SpecificationVersion.DRAFT_2020_12); + supportedVersions.put("draft4", SpecificationVersion.DRAFT_4); + supportedVersions.put("draft6", SpecificationVersion.DRAFT_6); + supportedVersions.put("draft7", SpecificationVersion.DRAFT_7); + } + + private SpecificationVersionDetector() { + // Prevent instantiation of this utility class + } + + /** + * Detects schema version based on the schema tag: if the schema tag is not present, throws + * {@link SchemaException} with the corresponding message, otherwise - returns the detected spec version. + * + * @param jsonNode JSON Node to read from + * @return Spec version if present, otherwise throws an exception + */ + public static SpecificationVersion detect(JsonNode jsonNode) { + return detectOptionalVersion(jsonNode, true).orElseThrow( + () -> new SchemaException("'" + SCHEMA_TAG + "' tag is not present") + ); + } + + /** + * Detects schema version based on the schema tag: if the schema tag is not present, returns an empty {@link + * Optional} value, otherwise - returns the detected spec version wrapped into {@link Optional}. + * + * @param jsonNode JSON Node to read from + * @param throwIfUnsupported whether to throw an exception if the version is not supported + * @return Spec version if present, otherwise empty + */ + public static Optional detectOptionalVersion(JsonNode jsonNode, boolean throwIfUnsupported) { + return Optional.ofNullable(jsonNode.get(SCHEMA_TAG)).map(schemaTag -> { + + String schemaTagValue = schemaTag.asText(); + String schemaUri = SchemaRegistry.normalizeDialectId(schemaTagValue); + + if (throwIfUnsupported) { + return SpecificationVersion.fromDialectId(schemaUri) + .orElseThrow(() -> new SchemaException("'" + schemaTagValue + "' is unrecognizable schema")); + } else { + return SpecificationVersion.fromDialectId(schemaUri).orElse(null); + } + }); + } + + + // For 2019-09 and later published drafts, implementations that are able to + // detect the draft of each schema via $schema SHOULD be configured to do so + public static SpecificationVersion detectVersion(JsonNode jsonNode, Path specification, SpecificationVersion defaultVersion, boolean throwIfUnsupported) { + return Stream.of( + detectOptionalVersion(jsonNode, throwIfUnsupported), + detectVersionFromPath(specification) + ) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElse(defaultVersion); + } + + // For draft-07 and earlier, draft-next, and implementations unable to + // detect via $schema, implementations MUST be configured to expect the + // draft matching the test directory name + public static Optional detectVersionFromPath(Path path) { + return StreamSupport.stream(path.spliterator(), false) + .map(Path::toString) + .map(supportedVersions::get) + .filter(Objects::nonNull) + .findAny(); + } +} diff --git a/src/test/java/com/networknt/schema/SpecificationVersionDetectorTest.java b/src/test/java/com/networknt/schema/SpecificationVersionDetectorTest.java new file mode 100644 index 000000000..2fbdd6324 --- /dev/null +++ b/src/test/java/com/networknt/schema/SpecificationVersionDetectorTest.java @@ -0,0 +1,56 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class SpecificationVersionDetectorTest { + + private static final ObjectMapper mapper = new ObjectMapper(); + + @ParameterizedTest + @CsvSource({ + "draft4, DRAFT_4", + "draft6, DRAFT_6", + "draft7, DRAFT_7", + "draft2019-09, DRAFT_2019_09", + "draft2020-12, DRAFT_2020_12" + }) + void detectVersion(String resourceDirectory, SpecificationVersion expectedFlag) throws IOException { + InputStream in = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(resourceDirectory + "/schemaTag.json"); + JsonNode node = mapper.readTree(in); + SpecificationVersion flag = SpecificationVersionDetector.detect(node); + assertEquals(expectedFlag, flag); + } + + @ParameterizedTest + @CsvSource({ + "data/schemaTag.json, 'http://json-schema.org/draft-03/schema#' is unrecognizable schema", + "data/schemaTagMissing.json, '$schema' tag is not present" + }) + void detectInvalidSchemaVersion(String schemaPath, String expectedError) throws IOException { + InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(schemaPath); + JsonNode node = mapper.readTree(in); + SchemaException exception = assertThrows(SchemaException.class, () -> SpecificationVersionDetector.detect(node)); + assertEquals(expectedError, exception.getMessage()); + } + + @Test + void detectOptionalSpecVersion() throws IOException { + InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream( + "data/schemaTagMissing.json"); + JsonNode node = mapper.readTree(in); + Optional flag = SpecificationVersionDetector.detectOptionalVersion(node, true); + assertEquals(Optional.empty(), flag); + } +} diff --git a/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java b/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java deleted file mode 100644 index 554e40971..000000000 --- a/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import static java.lang.String.format; -import static java.lang.System.out; - -@Disabled -public class ThresholdMixinPerfTest { - private static long thresholdIntegral = Long.MAX_VALUE - 1; - - - private final LongNode maximumLong = new LongNode(thresholdIntegral); - private final BigIntegerNode maximumBigInt = new BigIntegerNode(BigInteger.valueOf(thresholdIntegral)); - - private final LongNode valueLong = new LongNode(Long.MAX_VALUE); - private final BigIntegerNode valueBigInt = new BigIntegerNode(BigInteger.valueOf(Long.MAX_VALUE)); - - // private final double threshold = Double.MAX_VALUE - 1; - private final double threshold = 1797693.134E+5D; - private final DoubleNode maximumDouble = new DoubleNode(threshold); - private final DecimalNode maximumDecimal = new DecimalNode(BigDecimal.valueOf(threshold)); - - private final double value = threshold + 1; - private final DoubleNode valueDouble = new DoubleNode(value); - private final DecimalNode valueDecimal = new DecimalNode(new BigDecimal(value)); - private final TextNode valueTextual = new TextNode(String.valueOf(value)); - - private final String maximumText = maximumDouble.asText(); - private final BigDecimal max = new BigDecimal(maximumText); - - private final int executeTimes = 200000; - private final boolean excludeEqual = false; - - double baseTimeForDouble; - private double baseTimeForLong; - - @BeforeEach - public void baseTimeEstimate() { - baseTimeForDouble = getAvgTimeViaMixin(asDouble, valueDouble, executeTimes); - out.println(format("Base execution time (comparing two DoubleNodes) %f ns", baseTimeForDouble)); - - baseTimeForLong = getAvgTimeViaMixin(asLong, valueLong, executeTimes); - out.println(format("Base execution time (comparing two LongeNodes) %f ns \n", baseTimeForDouble)); - } - - @Test - public void currentTimeEstimate() { - out.println("Estimating time for current implementation:"); - double currentAvgTimeOnDouble = getAvgTimeViaMixin(currentImplementationDouble, valueDouble, executeTimes); - out.println(format("Current double on double execution time %f ns, %f times slower", currentAvgTimeOnDouble, (currentAvgTimeOnDouble / baseTimeForDouble))); - - double currentAvgTimeOnDecimal = getAvgTimeViaMixin(currentImplementationDouble, valueDecimal, executeTimes); - out.println(format("Current double on decimal execution time %f ns, %f times slower", currentAvgTimeOnDecimal, (currentAvgTimeOnDecimal / baseTimeForDouble))); - - double currentAvgTimeOnText = getAvgTimeViaMixin(currentImplementationDouble, valueTextual, executeTimes); - out.println(format("Current double on text execution time %f ns, %f times slower", currentAvgTimeOnText, (currentAvgTimeOnText / baseTimeForDouble))); - - double currentAvgTimeDecimalOnDouble = getAvgTimeViaMixin(currentImplementationDecimal, valueDouble, executeTimes); - out.println(format("Current decimal on double execution time %f ns, %f times slower", currentAvgTimeDecimalOnDouble, (currentAvgTimeDecimalOnDouble / baseTimeForDouble))); - - double currentAvgTimeDecimalOnDecimal = getAvgTimeViaMixin(currentImplementationDecimal, valueDecimal, executeTimes); - out.println(format("Current decimal on decimal execution time %f ns, %f times slower", currentAvgTimeDecimalOnDecimal, (currentAvgTimeDecimalOnDecimal / baseTimeForDouble))); - - double currentAvgTimeDecimalOnText = getAvgTimeViaMixin(currentImplementationDecimal, valueTextual, executeTimes); - out.println(format("Current decimal on text execution time %f ns, %f times slower", currentAvgTimeDecimalOnText, (currentAvgTimeDecimalOnText / baseTimeForDouble))); - - out.println(format("Cumulative average: %f\n\n", (currentAvgTimeOnDouble + currentAvgTimeOnDecimal + currentAvgTimeOnText + currentAvgTimeDecimalOnDouble + currentAvgTimeDecimalOnDecimal + currentAvgTimeDecimalOnText) / 6.0d)); - } - - @Test - public void allInOneAproachTimeEstimate() { - out.println("Estimating time threshold value agnostic mixin (aka allInOne):"); - double allInOneDoubleOnDouble = getAvgTimeViaMixin(allInOneDouble, valueDouble, executeTimes); - out.println(format("AllInOne double on double execution time %f ns, %f times slower", allInOneDoubleOnDouble, (allInOneDoubleOnDouble / baseTimeForDouble))); - - double allInOneDoubleOnDecimal = getAvgTimeViaMixin(allInOneDouble, valueDecimal, executeTimes); - out.println(format("AllInOne double on decimal execution time %f ns, %f times slower", allInOneDoubleOnDecimal, (allInOneDoubleOnDecimal / baseTimeForDouble))); - - double allInOneDoubleOnText = getAvgTimeViaMixin(allInOneDouble, valueTextual, executeTimes); - out.println(format("AllInOne double on text execution time %f ns, %f times slower", allInOneDoubleOnText, (allInOneDoubleOnText / baseTimeForDouble))); - - double allInOneDecimalOnDouble = getAvgTimeViaMixin(allInOneDecimal, valueDouble, executeTimes); - out.println(format("AllInOne decimal on double execution time %f ns, %f times slower", allInOneDecimalOnDouble, (allInOneDecimalOnDouble / baseTimeForDouble))); - - double allInOneDecimalOnDecimal = getAvgTimeViaMixin(allInOneDecimal, valueDecimal, executeTimes); - out.println(format("AllInOne decimal on decimal execution time %f ns, %f times slower", allInOneDecimalOnDecimal, (allInOneDecimalOnDecimal / baseTimeForDouble))); - - double allInOneDecimalOnText = getAvgTimeViaMixin(allInOneDecimal, valueTextual, executeTimes); - out.println(format("AllInOne decimal on text execution time %f ns, %f times slower", allInOneDecimalOnText, (allInOneDecimalOnText / baseTimeForDouble))); - - out.println(format("Cumulative average: %f\n\n", (allInOneDoubleOnDouble + allInOneDoubleOnDecimal + allInOneDoubleOnText + allInOneDecimalOnDouble + allInOneDecimalOnDecimal + allInOneDecimalOnText) / 6.0d)); - } - - @Test - public void specificCaseForEachThresholdValue() { - out.println("Estimating time for specific cases:"); - double doubleValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDouble, executeTimes); - out.println(format("Typed threshold execution time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime / baseTimeForDouble))); - - double decimalValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDecimal, executeTimes); - out.println(format("Typed threshold execution time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime / baseTimeForDouble))); - - double textValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueTextual, executeTimes); - out.println(format("Typed threshold execution time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime / baseTimeForDouble))); - - out.println(format("Cumulative average: %f\n\n", (doubleValueAvgTime + decimalValueAvgTime + textValueAvgTime) / 3.0d)); - } - - @Test - public void noMixinsFloatingTimeEstimate() { - out.println("Estimating time no mixins at all (floating point values):"); - double doubleValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); - out.println(format("No mixins with double value time %f ns, %f times slower", doubleValueAvgTime, (doubleValueAvgTime / baseTimeForDouble))); - - double decimalValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); - out.println(format("No mixins with decimal value time %f ns, %f times slower", decimalValueAvgTime, (decimalValueAvgTime / baseTimeForDouble))); - - double textValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueTextual, executeTimes); - out.println(format("No mixins with text value time %f ns, %f times slower", textValueAvgTime, (textValueAvgTime / baseTimeForDouble))); - out.println(format("Cumulative average: %f\n\n", - (doubleValueAvgTime + decimalValueAvgTime + textValueAvgTime) / 3.0d)); - } - - @Test - public void noMixinsIntegralTimeEstimate() { - double longValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new LongNode((long) value), executeTimes); - out.println(format("No mixins with long value time %f ns, %f times slower", longValueAvgTime, (longValueAvgTime / baseTimeForLong))); - - double bigIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new BigIntegerNode(BigInteger.valueOf((long) value)), executeTimes); - out.println(format("No mixins with big int value time %f ns, %f times slower", bigIntValueAvgTime, (bigIntValueAvgTime / baseTimeForLong))); - - double textIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new TextNode(String.valueOf((long) value)), executeTimes); - out.println(format("No mixins with text value time %f ns, %f times slower", textIntValueAvgTime, (textIntValueAvgTime / baseTimeForLong))); - out.println(format("Cumulative average: %f\n\n", - (longValueAvgTime + bigIntValueAvgTime + textIntValueAvgTime) / 3.0d)); - } - - ThresholdMixin allInOneDouble = new AllInOneThreshold(maximumDouble, false); - ThresholdMixin allInOneDecimal = new AllInOneThreshold(maximumDecimal, false); - - public static class AllInOneThreshold implements ThresholdMixin { - - private final BigDecimal bigDecimalMax; - JsonNode maximum; - private boolean excludeEqual; - - AllInOneThreshold(JsonNode maximum, boolean exludeEqual) { - this.maximum = maximum; - this.excludeEqual = exludeEqual; - this.bigDecimalMax = new BigDecimal(maximum.asText()); - } - - @Override - public boolean crossesThreshold(JsonNode node) { - if (maximum.isDouble() && maximum.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - if (maximum.isDouble() && maximum.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (maximum.isDouble() && node.isDouble()) { - double lm = maximum.doubleValue(); - double val = node.doubleValue(); - return lm < val || (excludeEqual && lm == val); - } - - if (maximum.isFloatingPointNumber() && node.isFloatingPointNumber()) { - BigDecimal value = node.decimalValue(); - int compare = value.compareTo(bigDecimalMax); - return compare > 0 || (excludeEqual && compare == 0); - } - - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(bigDecimalMax); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return maximum.asText(); - } - } - - ; - - ThresholdMixin asDouble = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - double lm = maximumDouble.doubleValue(); - double val = node.doubleValue(); - return lm < val || (excludeEqual && lm == val); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - - ThresholdMixin asLong = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - long lm = maximumLong.longValue(); - long val = node.longValue(); - return lm < val || (excludeEqual && lm == val); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - - ThresholdMixin typedThreshold = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (node.isDouble()) { - double lm = maximumDouble.doubleValue(); - double val = node.doubleValue(); - return lm < val || (excludeEqual && lm == val); - } - - if (node.isBigDecimal()) { - BigDecimal value = node.decimalValue(); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - - ThresholdMixin currentImplementationDouble = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - final BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - - - ThresholdMixin currentImplementationDecimal = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.POSITIVE_INFINITY) { - return false; - } - if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.NEGATIVE_INFINITY) { - return true; - } - if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { - return false; - } - if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { - return true; - } - final BigDecimal max = new BigDecimal(maximumText); - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return maximumText; - } - }; - - ThresholdMixin oneMixinForIntegerAndNumber = new ThresholdMixin() { - @Override - public boolean crossesThreshold(JsonNode node) { - BigDecimal value = new BigDecimal(node.asText()); - int compare = value.compareTo(max); - return compare > 0 || (excludeEqual && compare == 0); - } - - @Override - public String thresholdValue() { - return null; - } - }; - - private double getAvgTimeViaMixin(ThresholdMixin mixin, JsonNode value, int iterations) { - boolean excludeEqual = false; - long totalTime = 0; - for (int i = 0; i < iterations; i++) { - long start = System.nanoTime(); - try { - mixin.crossesThreshold(value); - } finally { - totalTime += System.nanoTime() - start; - } - } - return totalTime / (iterations * 1.0D); - } -} diff --git a/src/test/java/com/networknt/schema/TypeFactoryTest.java b/src/test/java/com/networknt/schema/TypeFactoryTest.java index 5377bc464..33acd4c33 100755 --- a/src/test/java/com/networknt/schema/TypeFactoryTest.java +++ b/src/test/java/com/networknt/schema/TypeFactoryTest.java @@ -16,30 +16,35 @@ package com.networknt.schema; -import com.fasterxml.jackson.databind.node.DecimalNode; -import org.junit.jupiter.api.Test; +import static com.networknt.schema.utils.TypeFactory.getSchemaNodeType; +import static com.networknt.schema.utils.TypeFactory.getValueNodeType; +import static org.junit.jupiter.api.Assertions.assertSame; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; -import static com.networknt.schema.TypeFactory.getValueNodeType; -import static org.junit.jupiter.api.Assertions.assertSame; +import org.junit.jupiter.api.Test; -public class TypeFactoryTest { +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.utils.JsonType; - private static final String[] validIntegralValues = { - "1", "-1", "0E+1", "0E1", "-0E+1", "-0E1", "10.1E+1", "10.1E1", "-10.1E+1", "-10.1E1", "1E+0", "1E-0", - "1E0", "1E18", "9223372036854775807", "-9223372036854775808", "1.0", "1.00", "-1.0", "-1.00" - }; +/** + * Test for TypeFactory. + */ +class TypeFactoryTest { - private static final String[] validNonIntegralNumberValues = { - "1.1", "-1.1", "1.10" - }; + private static final String[] validIntegralValues = { "1", "-1", "0E+1", "0E1", "-0E+1", "-0E1", "10.1E+1", + "10.1E1", "-10.1E+1", "-10.1E1", "1E+0", "1E-0", "1E0", "1E18", "9223372036854775807", + "-9223372036854775808", "1.0", "1.00", "-1.0", "-1.00" }; - private final SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); + private static final String[] validNonIntegralNumberValues = { "1.1", "-1.1", "1.10" }; @Test - public void testIntegralValuesWithJavaSemantics() { - schemaValidatorsConfig.setJavaSemantics(true); + void testIntegralValuesWithJavaSemantics() { + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(true).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.INTEGER, getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig), @@ -53,8 +58,8 @@ public void testIntegralValuesWithJavaSemantics() { } @Test - public void testIntegralValuesWithoutJavaSemantics() { - schemaValidatorsConfig.setJavaSemantics(false); + void testIntegralValuesWithoutJavaSemantics() { + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(false).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.NUMBER, getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig), @@ -67,33 +72,126 @@ public void testIntegralValuesWithoutJavaSemantics() { } } - @Test - public void testWithLosslessNarrowing() { - schemaValidatorsConfig.setLosslessNarrowing(true); + void testWithLosslessNarrowing() { + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(true).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.INTEGER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig), - validValue); + getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig), validValue); assertSame(JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig), - validValue); + getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig), validValue); } } @Test - public void testWithoutLosslessNarrowing() { - schemaValidatorsConfig.setLosslessNarrowing(false); + void testWithoutLosslessNarrowing() { + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(false).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig), - validValue); + getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig), validValue); assertSame(JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig), - validValue); + getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig), validValue); } } + + @Test + void testObjectValue() { + assertSame(JsonType.OBJECT, getValueNodeType(JsonMapperFactory.getInstance().getNodeFactory().objectNode(), + SchemaRegistryConfig.builder().build())); + } + + @Test + void testArrayValue() { + assertSame(JsonType.ARRAY, + getValueNodeType(JsonMapperFactory.getInstance().getNodeFactory().arrayNode(), SchemaRegistryConfig.builder().build())); + } + + @Test + void testBooleanValue() { + assertSame(JsonType.BOOLEAN, getValueNodeType( + JsonMapperFactory.getInstance().getNodeFactory().booleanNode(true), SchemaRegistryConfig.builder().build())); + } + + @Test + void testNullValue() { + assertSame(JsonType.NULL, + getValueNodeType(JsonMapperFactory.getInstance().getNodeFactory().nullNode(), SchemaRegistryConfig.builder().build())); + } + + @Test + void testMissingValue() { + assertSame(JsonType.UNKNOWN, getValueNodeType(JsonMapperFactory.getInstance().getNodeFactory().missingNode(), + SchemaRegistryConfig.builder().build())); + } + + @Test + void testIntegerValue() { + assertSame(JsonType.INTEGER, getValueNodeType(JsonMapperFactory.getInstance().getNodeFactory().numberNode(10), + SchemaRegistryConfig.builder().build())); + } + + @Test + void testBinaryValue() { + assertSame(JsonType.STRING, getValueNodeType( + JsonMapperFactory.getInstance().getNodeFactory().binaryNode("test".getBytes(StandardCharsets.UTF_8)), + SchemaRegistryConfig.builder().build())); + } + + @Test + void testUnknownSchema() { + assertSame(JsonType.UNKNOWN, getSchemaNodeType(TextNode.valueOf("unexpected"))); + } + + @Test + void testMissingSchema() { + assertSame(JsonType.UNKNOWN, getSchemaNodeType(MissingNode.getInstance())); + } + + @Test + void testStringSchema() { + assertSame(JsonType.STRING, getSchemaNodeType(TextNode.valueOf(JsonType.STRING.toString()))); + } + + @Test + void testObjectSchema() { + assertSame(JsonType.OBJECT, getSchemaNodeType(TextNode.valueOf(JsonType.OBJECT.toString()))); + } + + @Test + void testArraySchema() { + assertSame(JsonType.ARRAY, getSchemaNodeType(TextNode.valueOf(JsonType.ARRAY.toString()))); + } + + @Test + void testBooleanSchema() { + assertSame(JsonType.BOOLEAN, getSchemaNodeType(TextNode.valueOf(JsonType.BOOLEAN.toString()))); + } + + @Test + void testNumberSchema() { + assertSame(JsonType.NUMBER, getSchemaNodeType(TextNode.valueOf(JsonType.NUMBER.toString()))); + } + + @Test + void testIntegerSchema() { + assertSame(JsonType.INTEGER, getSchemaNodeType(TextNode.valueOf(JsonType.INTEGER.toString()))); + } + + @Test + void testAnySchema() { + assertSame(JsonType.ANY, getSchemaNodeType(TextNode.valueOf(JsonType.ANY.toString()))); + } + + @Test + void testNullSchema() { + assertSame(JsonType.NULL, getSchemaNodeType(TextNode.valueOf(JsonType.NULL.toString()))); + } + + @Test + void testUnionSchema() { + assertSame(JsonType.UNION, getSchemaNodeType(JsonMapperFactory.getInstance().getNodeFactory().arrayNode())); + } } diff --git a/src/test/java/com/networknt/schema/TypeValidatorTest.java b/src/test/java/com/networknt/schema/TypeValidatorTest.java old mode 100755 new mode 100644 index 962ca13c8..7c2feb91d --- a/src/test/java/com/networknt/schema/TypeValidatorTest.java +++ b/src/test/java/com/networknt/schema/TypeValidatorTest.java @@ -1,52 +1,182 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import org.junit.jupiter.api.Test; - -import static com.networknt.schema.TypeValidator.isNumeric; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class TypeValidatorTest { - - private static final String[] validNumericValues = { - "1", "-1", "1.1", "-1.1", "0E+1", "0E-1", "0E1", "-0E+1", "-0E-1", "-0E1", "0.1E+1", "0.1E-1", "0.1E1", - "-0.1E+1", "-0.1E-1", "-0.1E1", "10.1", "-10.1", "10E+1", "10E-1", "10E1", "-10E+1", "-10E-1", "-10E1", - "10.1E+1", "10.1E-1", "10.1E1", "-10.1E+1", "-10.1E-1", "-10.1E1", "1E+0", "1E-0", "1E0", - "1E00000000000000000000" - }; - private static final String[] invalidNumericValues = { - "01.1", "1.", ".1", "0.1.1", "E1", "E+1", "E-1", ".E1", ".E+1", ".E-1", ".1E1", ".1E+1", ".1E-1", "1E-", - "1E+", "1E", "+", "-", "1a", "0.1a", "0E1a", "0E-1a", "1.0a", "1.0aE1" - //, "+0", "+1" // for backward compatibility, in violation of JSON spec - }; - - @Test - public void testNumeicValues() { - for (String validValue : validNumericValues) { - assertTrue(isNumeric(validValue), validValue); - } - } - - @Test - public void testNonNumeicValues() { - for (String invalidValue : invalidNumericValues) { - assertFalse(isNumeric(invalidValue), invalidValue); - } - } -} +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Test TypeValidator validator. + */ +class TypeValidatorTest { + String schemaData = "{\r\n" // Issue 415 + + " \"$schema\": \"http://json-schema.org/draft-07/schema\",\r\n" + + " \"$id\": \"http://example.com/example.json\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"array_of_integers\": {\r\n" + + " \"$id\": \"#/properties/array_of_integers\",\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"array_of_objects\": {\r\n" + + " \"$id\": \"#/properties/array_of_objects\",\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"type\": \"object\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + @Test + void testTypeLoose() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema = factory.getSchema(schemaData); + + String inputData = "{\r\n" + + " \"array_of_integers\": 1,\r\n" + + " \"array_of_objects\": {}\r\n" + + "}"; + String validTypeLooseInputData = "{\r\n" + + " \"array_of_integers\": [\"1\"],\r\n" + + " \"array_of_objects\": [{}]\r\n" + + "}"; + String invalidTypeLooseData = "{\r\n" + + " \"array_of_integers\": \"a\",\r\n" + + " \"array_of_objects\": {}\r\n" + + "}"; + // Without type loose this has 2 type errors + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(2, messages.size()); + assertEquals(2, messages.stream().filter(m -> "type".equals(m.getKeyword())).count()); + + // 1 type error in array_of_integers + messages = schema.validate(validTypeLooseInputData, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals(1, messages.stream().filter(m -> "type".equals(m.getKeyword())).count()); + + // With type loose this has 0 type errors as any item can also be interpreted as an array of 1 item + SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); + factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)); + Schema typeLoose = factory.getSchema(schemaData); + messages = typeLoose.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // No errors + messages = typeLoose.validate(validTypeLooseInputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Error because a string cannot be interpreted as an array of integer + messages = typeLoose.validate(invalidTypeLooseData, InputFormat.JSON); + assertEquals(1, messages.size()); + + } + + /** + * Issue 864. + */ + @Test + void integer() { + String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("1", InputFormat.JSON); + assertEquals(0, messages.size()); + messages = schema.validate("2.0", InputFormat.JSON); + assertEquals(0, messages.size()); + messages = schema.validate("2.000001", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + /** + * Issue 864. + *

+ * In draft-04, "integer" is listed as a primitive type and defined as "a JSON + * number without a fraction or exponent part"; in draft-06, "integer" is not + * considered a primitive type and is only defined in the section for keyword + * "type" as "any number with a zero fractional part"; 1.0 is thus not a valid + * "integer" type in draft-04 and earlier, but is a valid "integer" type in + * draft-06 and later; note that both drafts say that integers SHOULD be encoded + * in JSON without fractional parts + * + * @see Draft-06 + * Release Notes + */ + @Test + void integerDraft4() { + String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4).getSchema(schemaData); + List messages = schema.validate("1", InputFormat.JSON); + assertEquals(0, messages.size()); + // The logic in JsonNodeUtil specifically excludes V4 from this handling + messages = schema.validate("2.0", InputFormat.JSON); + assertEquals(1, messages.size()); + messages = schema.validate("2.000001", InputFormat.JSON); + assertEquals(1, messages.size()); + } + + @Test + void walkNull() { + String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4).getSchema(schemaData); + Result result = schema.walk(null, true); + assertTrue(result.getErrors().isEmpty()); + } + + @Test + void nullable() { + String schemaData = "{\r\n" + + " \"$schema\":\"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\":\"object\",\r\n" + + " \"properties\":{\r\n" + + " \"test\":{\r\n" + + " \"type\":\"object\",\r\n" + + " \"properties\":{\r\n" + + " \"nested\":{\r\n" + + " \"type\":\"string\",\r\n" + + " \"nullable\":true,\r\n" + + " \"format\":\"date\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"test\":{\r\n" + + " \"nested\":null\r\n" + + " }\r\n" + + "}"; + // nullable keyword enabled false + final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + final Schema validator = factory.getSchema(schemaData); + + final List errors = validator.validate(inputData, InputFormat.JSON); + assertEquals(1, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java b/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java new file mode 100644 index 000000000..fb6d8b1a3 --- /dev/null +++ b/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java @@ -0,0 +1,18 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.util.stream.Stream; + +@DisplayName("Unevaluated Items") +class UnevaluatedItemsTest extends AbstractJsonSchemaTestSuite { + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json"); + } + +} diff --git a/src/test/java/com/networknt/schema/UnevaluatedItemsValidatorTest.java b/src/test/java/com/networknt/schema/UnevaluatedItemsValidatorTest.java new file mode 100644 index 000000000..159a2925d --- /dev/null +++ b/src/test/java/com/networknt/schema/UnevaluatedItemsValidatorTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +/** + * UnevaluatedItemsValidatorTest. + */ +class UnevaluatedItemsValidatorTest { + @Test + void unevaluatedItemsFalse() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " { \r\n" + + " \"type\" : \"array\" ,\r\n" + + " \"prefixItems\" : [\r\n" + + " { \"type\" : \"integer\" }\r\n" + + " ]\r\n" + + " }\r\n" + + " ],\r\n" + + " \"unevaluatedItems\" : false\r\n" + + "}"; + String inputData = "[1,2,3]"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(2, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("unevaluatedItems", assertions.get(0).getKeyword()); + assertEquals("", assertions.get(0).getInstanceLocation().toString()); + assertEquals("/unevaluatedItems", assertions.get(0).getEvaluationPath().toString()); + assertEquals("unevaluatedItems", assertions.get(1).getKeyword()); + assertEquals("", assertions.get(1).getInstanceLocation().toString()); + assertEquals("/unevaluatedItems", assertions.get(1).getEvaluationPath().toString()); + } + + @Test + void unevaluatedItemsSchema() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " { \r\n" + + " \"type\" : \"array\" ,\r\n" + + " \"prefixItems\" : [\r\n" + + " { \"type\" : \"integer\" }\r\n" + + " ]\r\n" + + " }\r\n" + + " ],\r\n" + + " \"unevaluatedItems\" : { \"type\" : \"string\" }\r\n" + + "}"; + String inputData = "[1,2,3]"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(2, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("type", assertions.get(0).getKeyword()); + assertEquals("/1", assertions.get(0).getInstanceLocation().toString()); + assertEquals("/unevaluatedItems/type", assertions.get(0).getEvaluationPath().toString()); + assertEquals("type", assertions.get(1).getKeyword()); + assertEquals("/2", assertions.get(1).getInstanceLocation().toString()); + assertEquals("/unevaluatedItems/type", assertions.get(1).getEvaluationPath().toString()); + } +} diff --git a/src/test/java/com/networknt/schema/UnevaluatedPropertiesTest.java b/src/test/java/com/networknt/schema/UnevaluatedPropertiesTest.java new file mode 100644 index 000000000..767f59f78 --- /dev/null +++ b/src/test/java/com/networknt/schema/UnevaluatedPropertiesTest.java @@ -0,0 +1,18 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.util.stream.Stream; + +@DisplayName("Unevaluated Properties") +class UnevaluatedPropertiesTest extends AbstractJsonSchemaTestSuite { + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(SpecificationVersion.DRAFT_2019_09, "src/test/resources/schema/unevaluatedTests/unevaluated-tests.json"); + } + +} diff --git a/src/test/java/com/networknt/schema/UnevaluatedPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/UnevaluatedPropertiesValidatorTest.java new file mode 100644 index 000000000..25b4a59b8 --- /dev/null +++ b/src/test/java/com/networknt/schema/UnevaluatedPropertiesValidatorTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.output.OutputUnit; + +/** + * UnevaluatedPropertiesValidatorTest. + */ +class UnevaluatedPropertiesValidatorTest { + /** + * Issue 962. + */ + @Test + void annotationsOnlyDroppedAtTheEndOfSchemaProcessing() { + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\r\n" + + " \"key1\"\r\n" + + " ],\r\n" + + " \"properties\": {\r\n" + + " \"key1\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"key2\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"key3\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"unevaluatedProperties\": false\r\n" + + "}"; + String inputData = "{\r\n" + + " \"key2\": \"value2\",\r\n" + + " \"key3\": \"value3\",\r\n" + + " \"key4\": \"value4\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(2, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("required", assertions.get(0).getKeyword()); + assertEquals("key1", assertions.get(0).getProperty()); + assertEquals("unevaluatedProperties", assertions.get(1).getKeyword()); + assertEquals("key4", assertions.get(1).getProperty()); + } + + /** + * Issue 967. + */ + @Test + void subschemaProcessing() { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"$defs\" : {\r\n" + + " \"subschema\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"required\": [\"group\"],\r\n" + + " \"properties\": {\r\n" + + " \"group\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"additionalProperties\": false,\r\n" + + " \"required\": [\"parentprop\"],\r\n" + + " \"properties\": {\r\n" + + " \"parentprop\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"type\": \"object\",\r\n" + + " \"unevaluatedProperties\": false,\r\n" + + " \"allOf\": [\r\n" + + " {\"properties\": { \"group\" : {\"type\":\"object\"} } },\r\n" + + " {\"$ref\": \"#/$defs/subschema\"}\r\n" + + " ],\r\n" + + " \"required\": [\"childprop\"],\r\n" + + " \"properties\": {\r\n" + + " \"childprop\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"childprop\": \"something\",\r\n" + + " \"group\": {\r\n" + + " \"parentprop\":\"something\",\r\n" + + " \"notallowed\": false\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(1, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("additionalProperties", assertions.get(0).getKeyword()); + assertEquals("notallowed", assertions.get(0).getProperty()); + } + + @Test + void unevaluatedPropertiesSchema() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " { \r\n" + + " \"type\" : \"object\" ,\r\n" + + " \"properties\" : {\r\n" + + " \"prop\" : { \"type\" : \"integer\" }\r\n" + + " }\r\n" + + " }\r\n" + + " ],\r\n" + + " \"unevaluatedProperties\" : { \"type\" : \"string\" }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"prop\": 1,\r\n" + + " \"group\": {\r\n" + + " \"parentprop\":\"something\",\r\n" + + " \"notallowed\": false\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(1, messages.size()); + List assertions = messages.stream().collect(Collectors.toList()); + assertEquals("type", assertions.get(0).getKeyword()); + assertEquals("/unevaluatedProperties/type", assertions.get(0).getEvaluationPath().toString()); + } + + @Test + void ref() { + String schemaData = "{\r\n" + + " \"definitions\": {\r\n" + + " \"other\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"surfboard\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/definitions/other\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"wheels\": {},\r\n" + + " \"headlights\": {}\r\n" + + " }\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"pontoons\": {}\r\n" + + " }\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"wings\": {}\r\n" + + " }\r\n" + + " }\r\n" + + " ],\r\n" + + " \"unevaluatedProperties\": false\r\n" + + "}"; + String inputData = "{ \"pontoons\": {}, \"wheels\": {}, \"surfboard\": \"2\" }"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + } + + @Test + void nestedRef() { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"allOf\": [ { \"$ref\": \"https://www.example.org/PrimaryDeviceConfiguration.json#PrimaryDeviceConfiguration\" } ],\r\n" + + " \"properties\": {\r\n" + + " \"__type\": { \"const\": \"dk.cachet.carp.common.application.devices.Smartphone\" }\r\n" + + " },\r\n" + + " \"unevaluatedProperties\": false\r\n" + + "}"; + String primaryDeviceConfiguration = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"PrimaryDeviceConfiguration\": {\r\n" + + " \"$anchor\": \"PrimaryDeviceConfiguration\",\r\n" + + " \"allOf\": [ { \"$ref\": \"DeviceConfiguration.json#DeviceConfiguration\" } ],\r\n" + + " \"properties\": {\r\n" + + " \"isPrimaryDevice\": { \"const\": true }\r\n" + + " },\r\n" + + " \"required\": [ \"isPrimaryDevice\" ]\r\n" + + " }\r\n" + + " }"; + String deviceConfiguration = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"DeviceConfiguration\": {\r\n" + + " \"properties\": {\r\n" + + " \"roleName\": { \"type\": \"string\" }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Map schemas = new HashMap<>(); + schemas.put("https://www.example.org/PrimaryDeviceConfiguration.json", primaryDeviceConfiguration); + schemas.put("https://www.example.org/DeviceConfiguration.json", deviceConfiguration); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2019_09, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(schemas))) + .getSchema(schemaData); + String inputData = "{ \"isPrimaryDevice\": true, \"roleName\": \"hello\" }"; + OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, + executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(false).annotationCollectionFilter(keyword -> true)); + }); + assertTrue(outputUnit.isValid()); + } +} diff --git a/src/test/java/com/networknt/schema/UnknownKeywordFactoryTest.java b/src/test/java/com/networknt/schema/UnknownKeywordFactoryTest.java new file mode 100644 index 000000000..8f00ea7c0 --- /dev/null +++ b/src/test/java/com/networknt/schema/UnknownKeywordFactoryTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.keyword.Keyword; +import com.networknt.schema.keyword.UnknownKeywordFactory; + +class UnknownKeywordFactoryTest { + + @Test + void shouldReturnAnnotationKeywordForUnknownKeywords() { + UnknownKeywordFactory factory = UnknownKeywordFactory.getInstance(); + Keyword keyword = factory.getKeyword("helloworld", null); + assertInstanceOf(AnnotationKeyword.class, keyword); + } + +} diff --git a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java index 601119d84..0b21409a2 100644 --- a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java +++ b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java @@ -2,110 +2,76 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.dialect.DialectId; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.util.Set; +import java.util.List; -public class UnknownMetaSchemaTest { +class UnknownMetaSchemaTest { - private String schema1 = "{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; - private String schema2 = "{\"$schema\":\"https://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; - private String schema3 = "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; + private final String schema1 = "{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; + private final String schema2 = "{\"$schema\":\"https://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; + private final String schema3 = "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; - private String json = "{\"data\":1}"; + private final String json = "{\"data\":1}"; @Test - public void testSchema1() throws IOException { + void testSchema1() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(this.json); - JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build(); - JsonSchema jsonSchema = factory.getSchema(schema1); + SchemaRegistry factory = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7)).build(); + Schema jsonSchema = factory.getSchema(schema1); - Set errors = jsonSchema.validate(jsonNode); - for(ValidationMessage error:errors) { + List errors = jsonSchema.validate(jsonNode); + for(Error error:errors) { System.out.println(error.getMessage()); } } @Test - public void testSchema2() throws IOException { + void testSchema2() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(this.json); - JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build(); - JsonSchema jsonSchema = factory.getSchema(schema2); + SchemaRegistry factory = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7)).build(); + Schema jsonSchema = factory.getSchema(schema2); - Set errors = jsonSchema.validate(jsonNode); - for(ValidationMessage error:errors) { + List errors = jsonSchema.validate(jsonNode); + for(Error error:errors) { System.out.println(error.getMessage()); } } @Test - public void testSchema3() throws IOException { + void testSchema3() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(this.json); - JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build(); - JsonSchema jsonSchema = factory.getSchema(schema3); + SchemaRegistry factory = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7)).build(); + Schema jsonSchema = factory.getSchema(schema3); - Set errors = jsonSchema.validate(jsonNode); - for(ValidationMessage error:errors) { + List errors = jsonSchema.validate(jsonNode); + for(Error error:errors) { System.out.println(error.getMessage()); } } @Test - public void testNormalize() throws JsonSchemaException { - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = true; + void testNormalize() throws SchemaException { String uri01 = "http://json-schema.org/draft-07/schema"; String uri02 = "http://json-schema.org/draft-07/schema#"; String uri03 = "http://json-schema.org/draft-07/schema?key=value"; String uri04 = "http://json-schema.org/draft-07/schema?key=value&key2=value2"; - String expected = "https://json-schema.org/draft-07/schema"; - - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04, forceHttps, removeEmptyFragmentSuffix)); - - } + String expected = DialectId.DRAFT_7; - @Test - public void testNormalizeForceHttpsDisabled() throws JsonSchemaException { - final boolean forceHttps = false; - final boolean removeEmptyFragmentSuffix = true; - - String uri01 = "http://json-schema.org/draft-07/schema"; - String uri02 = "http://json-schema.org/draft-07/schema#"; - String uri03 = "http://json-schema.org/draft-07/schema?key=value"; - String uri04 = "http://json-schema.org/draft-07/schema?key=value&key2=value2"; - String expected = "http://json-schema.org/draft-07/schema"; - - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04, forceHttps, removeEmptyFragmentSuffix)); - - } - - @Test - public void testNormalizeRemovingEmptyFragmentSuffixDisabled() throws JsonSchemaException { - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = false; - - String uri01 = "http://json-schema.org/draft-07/schema#"; - String uri02 = "http://json-schema.org/draft-07/schema?key=value#"; - String uri03 = "http://json-schema.org/draft-07/schema?key=value&key2=value2#"; - String expected = "https://json-schema.org/draft-07/schema#"; - - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); + Assertions.assertEquals(expected, SchemaRegistry.normalizeDialectId(uri01)); + Assertions.assertEquals(expected, SchemaRegistry.normalizeDialectId(uri02)); + Assertions.assertEquals(expected, SchemaRegistry.normalizeDialectId(uri03)); + Assertions.assertEquals(expected, SchemaRegistry.normalizeDialectId(uri04)); } } diff --git a/src/test/java/com/networknt/schema/UriMappingTest.java b/src/test/java/com/networknt/schema/UriMappingTest.java index 1b18708d0..47821235d 100644 --- a/src/test/java/com/networknt/schema/UriMappingTest.java +++ b/src/test/java/com/networknt/schema/UriMappingTest.java @@ -15,30 +15,31 @@ */ package com.networknt.schema; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchemaFactory.Builder; -import com.networknt.schema.uri.ClasspathURLFactory; -import com.networknt.schema.uri.URLFactory; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; +import java.io.UncheckedIOException; import java.net.URL; -import java.net.UnknownHostException; import java.util.HashMap; -import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.SchemaRegistry.Builder; +import com.networknt.schema.dialect.BasicDialectRegistry; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.resource.InputStreamSource; +import com.networknt.schema.resource.MapSchemaIdResolver; +import com.networknt.schema.resource.ResourceLoader; +import com.networknt.schema.resource.SchemaIdResolver; -public class UriMappingTest { +class UriMappingTest { private final ObjectMapper mapper = new ObjectMapper(); - private final ClasspathURLFactory classpathURLFactory = new ClasspathURLFactory(); - private final URLFactory urlFactory = new URLFactory(); /** * Validate that a JSON URI Mapping file containing the URI Mapping schema is @@ -47,16 +48,15 @@ public class UriMappingTest { * @throws IOException if unable to parse the mapping file */ @Test - public void testBuilderUriMappingUri() throws IOException { - URL mappings = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/uri-mapping.json")); - JsonMetaSchema draftV4 = JsonMetaSchema.getV4(); - Builder builder = JsonSchemaFactory.builder() - .defaultMetaSchemaURI(draftV4.getUri()) - .addMetaSchema(draftV4) - .addUriMappings(getUriMappingsFromUrl(mappings)); - JsonSchemaFactory instance = builder.build(); - JsonSchema schema = instance.getSchema(this.urlFactory.create( + void testBuilderUriMappingUri() throws IOException { + URL mappings = UriMappingTest.class.getResource("/uri_mapping/uri-mapping.json"); + Dialect draftV4 = Dialects.getDraft4(); + Builder builder = SchemaRegistry.builder() + .defaultDialectId(draftV4.getId()) + .dialectRegistry(new BasicDialectRegistry(draftV4)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(getUriMappingsFromUrl(mappings))); + SchemaRegistry instance = builder.build(); + Schema schema = instance.getSchema(SchemaLocation.of( "https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json")); assertEquals(0, schema.validate(mapper.readTree(mappings)).size()); } @@ -64,38 +64,50 @@ public void testBuilderUriMappingUri() throws IOException { /** * Validate that local URI is used when attempting to get a schema that is not * available publicly. Use the URL http://example.com/invalid/schema/url to use - * a public URL that returns a 404 Not Found. The locally mapped schema is a + * a URL that returns a 404 Not Found. The locally mapped schema is a * valid, but empty schema. * * @throws IOException if unable to parse the mapping file */ @Test - public void testBuilderExampleMappings() throws IOException { - JsonSchemaFactory instance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - URI example = this.urlFactory.create("http://example.com/invalid/schema/url"); + void testBuilderExampleMappings() throws IOException { + ResourceLoader schemaLoader = new ResourceLoader() { + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + String iri = absoluteIri.toString(); + if ("https://example.com/invalid/schema/url".equals(iri)) { + return () -> { + throw new FileNotFoundException(iri); + }; + } + return null; + } + }; + SchemaRegistry instance = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.add(schemaLoader))); + SchemaLocation example = SchemaLocation.of("https://example.com/invalid/schema/url"); // first test that attempting to use example URL throws an error try { - JsonSchema schema = instance.getSchema(example); + Schema schema = instance.getSchema(example); schema.validate(mapper.createObjectNode()); fail("Expected exception not thrown"); - } catch (JsonSchemaException ex) { + } catch (SchemaException ex) { Throwable cause = ex.getCause(); - if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) { - fail("Unexpected cause for JsonSchemaException"); + if (!(cause instanceof IOException )) { + fail("Unexpected cause for JsonSchemaException", ex); } // passing, so do nothing } catch (Exception ex) { - fail("Unexpected exception thrown"); + fail("Unexpected exception thrown", ex); } - URL mappings = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/invalid-schema-uri.json")); - JsonMetaSchema draftV4 = JsonMetaSchema.getV4(); - Builder builder = JsonSchemaFactory.builder() - .defaultMetaSchemaURI(draftV4.getUri()) - .addMetaSchema(draftV4) - .addUriMappings(getUriMappingsFromUrl(mappings)); + URL mappings = UriMappingTest.class.getResource("/uri_mapping/invalid-schema-uri.json"); + Dialect draftV4 = Dialects.getDraft4(); + Builder builder = SchemaRegistry.builder() + .defaultDialectId(draftV4.getId()) + .dialectRegistry(new BasicDialectRegistry(draftV4)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(getUriMappingsFromUrl(mappings))); instance = builder.build(); - JsonSchema schema = instance.getSchema(example); + Schema schema = instance.getSchema(example); assertEquals(0, schema.validate(mapper.createObjectNode()).size()); } @@ -106,70 +118,81 @@ public void testBuilderExampleMappings() throws IOException { * @throws IOException if unable to parse the mapping file */ @Test - public void testValidatorConfigUriMappingUri() throws IOException { - JsonSchemaFactory instance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - URL mappings = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/uri-mapping.json")); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setUriMappings(getUriMappingsFromUrl(mappings)); - JsonSchema schema = instance.getSchema(this.urlFactory.create( - "https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json"), - config); + void testValidatorConfigUriMappingUri() throws IOException { + URL mappings = UriMappingTest.class.getResource("/uri_mapping/uri-mapping.json"); + SchemaRegistry instance = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(getUriMappingsFromUrl(mappings))).build(); + Schema schema = instance.getSchema(SchemaLocation.of( + "https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json")); assertEquals(0, schema.validate(mapper.readTree(mappings)).size()); } /** * Validate that local URL is used when attempting to get a schema that is not * available publicly. Use the URL http://example.com/invalid/schema/url to use - * a public URL that returns a 404 Not Found. The locally mapped schema is a + * a URL that returns a 404 Not Found. The locally mapped schema is a * valid, but empty schema. * * @throws IOException if unable to parse the mapping file */ @Test - public void testValidatorConfigExampleMappings() throws IOException { - JsonSchemaFactory instance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - URI example = this.urlFactory.create("http://example.com/invalid/schema/url"); + void testValidatorConfigExampleMappings() throws IOException { + ResourceLoader schemaLoader = new ResourceLoader() { + @Override + public InputStreamSource getResource(AbsoluteIri absoluteIri) { + String iri = absoluteIri.toString(); + if ("https://example.com/invalid/schema/url".equals(iri)) { + return () -> { + throw new FileNotFoundException(iri); + }; + } + return null; + } + }; + URL mappings = UriMappingTest.class.getResource("/uri_mapping/invalid-schema-uri.json"); + SchemaRegistry instance = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.add(schemaLoader)))).build(); + SchemaLocation example = SchemaLocation.of("https://example.com/invalid/schema/url"); // first test that attempting to use example URL throws an error try { - JsonSchema schema = instance.getSchema(example, config); + Schema schema = instance.getSchema(example); schema.validate(mapper.createObjectNode()); fail("Expected exception not thrown"); - } catch (JsonSchemaException ex) { + } catch (SchemaException ex) { Throwable cause = ex.getCause(); - if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) { + if (!(cause instanceof IOException)) { fail("Unexpected cause for JsonSchemaException"); } // passing, so do nothing } catch (Exception ex) { fail("Unexpected exception thrown"); } - URL mappings = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/invalid-schema-uri.json")); - config.setUriMappings(getUriMappingsFromUrl(mappings)); - JsonSchema schema = instance.getSchema(example, config); + instance = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(getUriMappingsFromUrl(mappings))).build(); + Schema schema = instance.getSchema(example); assertEquals(0, schema.validate(mapper.createObjectNode()).size()); } @Test - public void testMappingsForRef() throws IOException { - JsonSchemaFactory instance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - URL mappings = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/schema-with-ref-mapping.json")); - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setUriMappings(getUriMappingsFromUrl(mappings)); - JsonSchema schema = instance.getSchema(this.classpathURLFactory.create("resource:draft4/extra/uri_mapping/schema-with-ref.json"), - config); + void testMappingsForRef() throws IOException { + URL mappings = UriMappingTest.class.getResource("/uri_mapping/schema-with-ref-mapping.json"); + SchemaRegistry instance = SchemaRegistry.builder(SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4)) + .schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(getUriMappingsFromUrl(mappings))).build(); + Schema schema = instance.getSchema(SchemaLocation.of("resource:uri_mapping/schema-with-ref.json") + ); assertEquals(0, schema.validate(mapper.readTree("[]")).size()); } - private Map getUriMappingsFromUrl(URL url) throws MalformedURLException, IOException { + private SchemaIdResolver getUriMappingsFromUrl(URL url) { HashMap map = new HashMap(); - for (JsonNode mapping : mapper.readTree(url)) { - map.put(mapping.get("publicURL").asText(), - mapping.get("localURL").asText()); + try { + for (JsonNode mapping : mapper.readTree(url)) { + map.put(mapping.get("publicURL").asText(), + mapping.get("localURL").asText()); + } + } catch (IOException e) { + throw new UncheckedIOException(e); } - return map; + return new MapSchemaIdResolver(map); } } diff --git a/src/test/java/com/networknt/schema/UrnTest.java b/src/test/java/com/networknt/schema/UrnTest.java index d28ec56e4..b60430674 100644 --- a/src/test/java/com/networknt/schema/UrnTest.java +++ b/src/test/java/com/networknt/schema/UrnTest.java @@ -1,66 +1,25 @@ package com.networknt.schema; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.uri.ClasspathURLFactory; -import com.networknt.schema.uri.URLFactory; -import com.networknt.schema.urn.URNFactory; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; - import static org.junit.jupiter.api.Assertions.assertEquals; -public class UrnTest -{ - private final ObjectMapper mapper = new ObjectMapper(); - private final ClasspathURLFactory classpathURLFactory = new ClasspathURLFactory(); - private final URLFactory urlFactory = new URLFactory(); +import java.io.IOException; - /** - * Validate that a JSON URI Mapping file containing the URI Mapping schema is - * schema valid. - * - * @throws IOException if unable to parse the mapping file - */ - @Test - public void testURNToURI() throws Exception { - URL urlTestData = ClasspathURLFactory.convert( - this.classpathURLFactory.create("resource:draft7/urn/test.json")); - - URNFactory urnFactory = new URNFactory() - { - @Override public URI create(String urn) - { - try { - URL absoluteURL = ClasspathURLFactory.convert(new ClasspathURLFactory().create(String.format("resource:draft7/urn/%s.schema.json", urn))); - return absoluteURL.toURI(); - } catch (Exception ex) { - return null; - } - } - }; +import org.junit.jupiter.api.Test; - InputStream is = null; - try { - is = new URL("https://raw.githubusercontent.com/francesc79/json-schema-validator/feature/urn-management/src/test/resources/draft7/urn/urn.schema.json").openStream(); - JsonMetaSchema draftV7 = JsonMetaSchema.getV7(); - JsonSchemaFactory.Builder builder = JsonSchemaFactory.builder() - .defaultMetaSchemaURI(draftV7.getUri()) - .addMetaSchema(draftV7) - .addUrnFactory(urnFactory); - JsonSchemaFactory instance = builder.build(); - JsonSchema schema = instance.getSchema(is); - assertEquals(0, schema.validate(mapper.readTree(urlTestData)).size()); - } catch( Exception e) { - e.printStackTrace(); - } - finally { - if (is != null) { - is.close(); - } +class UrnTest { + /** + * Validate that a JSON URI Mapping file containing the URI Mapping schema is + * schema valid. + * + * @throws IOException if unable to parse the mapping file + */ + @Test + void testURNToURI() throws Exception { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers.add(value -> { + return AbsoluteIri.of(String.format("%s.schema.json", value.toString())); + }))); + Schema schema = schemaRegistry.getSchema(SchemaLocation.of("classpath:/draft7/urn/urn")); + assertEquals(0, schema.validate(AbsoluteIri.of("classpath:/draft7/urn/test.json"), InputFormat.JSON).size()); } - } } diff --git a/src/test/java/com/networknt/schema/V201909JsonSchemaTest.java b/src/test/java/com/networknt/schema/V201909JsonSchemaTest.java deleted file mode 100644 index d7e3c3cf2..000000000 --- a/src/test/java/com/networknt/schema/V201909JsonSchemaTest.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -public class V201909JsonSchemaTest extends BaseSuiteJsonSchemaTest { - public V201909JsonSchemaTest() { - super(SpecVersion.VersionFlag.V201909); - } - - @Test - public void testOptionalBignumValidator() throws Exception { - runTestFile("draft2019-09/optional/bignum.json"); - } - - @Test - @Disabled - public void testOptionalContentValidator() throws Exception { - runTestFile("draft2019-09/optional/content.json"); - } - - @Test - @Disabled - public void testEcmascriptRegexValidator() throws Exception { - runTestFile("draft2019-09/optional/ecmascript-regex.json"); - } - - @Test - @Disabled - public void testZeroTerminatedFloatsValidator() throws Exception { - runTestFile("draft2019-09/optional/zeroTerminatedFloats.json"); - } - - @Test - public void testOptionalFormatDateValidator() throws Exception { - runTestFile("draft2019-09/optional/format/date.json"); - } - - @Test - public void testOptionalFormatDateTimeValidator() throws Exception { - runTestFile("draft2019-09/optional/format/date-time.json"); - } - - @Test - public void testOptionalFormatEmailValidator() throws Exception { - runTestFile("draft2019-09/optional/format/email.json"); - } - - @Test - public void testOptionalFormatHostnameValidator() throws Exception { - runTestFile("draft2019-09/optional/format/hostname.json"); - } - - @Test - @Disabled - public void testOptionalFormatIdnEmailValidator() throws Exception { - runTestFile("draft2019-09/optional/format/idn-email.json"); - } - - @Test - @Disabled - public void testOptionalFormatIdnHostnameValidator() throws Exception { - runTestFile("draft2019-09/optional/format/idn-hostname.json"); - } - - @Test - public void testOptionalFormatIpv4Validator() throws Exception { - runTestFile("draft2019-09/optional/format/ipv4.json"); - } - - @Test - public void testOptionalFormatIpv6Validator() throws Exception { - runTestFile("draft2019-09/optional/format/ipv6.json"); - } - - @Test - @Disabled - public void testOptionalFormatIriValidator() throws Exception { - runTestFile("draft2019-09/optional/format/iri.json"); - } - - @Test - @Disabled - public void testOptionalFormatIriReferenceValidator() throws Exception { - runTestFile("draft2019-09/optional/format/iri-reference.json"); - } - - @Test - @Disabled - public void testOptionalFormatJsonPointerValidator() throws Exception { - runTestFile("draft2019-09/optional/format/json-pointer.json"); - } - - @Test - @Disabled - public void testOptionalFormatRegexValidator() throws Exception { - runTestFile("draft2019-09/optional/format/regex.json"); - } - - @Test - @Disabled - public void testOptionalFormatRelativeJsonPointerValidator() throws Exception { - runTestFile("draft2019-09/optional/format/relative-json-pointer.json"); - } - - @Test - @Disabled - public void testOptionalFormatTimeValidator() throws Exception { - runTestFile("draft2019-09/optional/format/time.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriValidator() throws Exception { - runTestFile("draft2019-09/optional/format/uri.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriReferenceValidator() throws Exception { - runTestFile("draft2019-09/optional/format/uri-reference.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriTemplateValidator() throws Exception { - runTestFile("draft2019-09/optional/format/uri-template.json"); - } - - @Test - public void testAdditionalItemsValidator() throws Exception { - runTestFile("draft2019-09/additionalItems.json"); - } - - @Test - public void testAdditionalPropertiesValidator() throws Exception { - runTestFile("draft2019-09/additionalProperties.json"); - } - - @Test - public void testAllOfValidator() throws Exception { - runTestFile("draft2019-09/allOf.json"); - } - - @Test - @Disabled - public void testAnchorValidator() throws Exception { - runTestFile("draft2019-09/anchor.json"); - } - - @Test - public void testAnyOfValidator() throws Exception { - runTestFile("draft2019-09/anyOf.json"); - } - - @Test - public void testBooleanSchemaValidator() throws Exception { - runTestFile("draft2019-09/boolean_schema.json"); - } - - @Test - public void testConstValidator() throws Exception { - runTestFile("draft2019-09/const.json"); - } - - @Test - public void testContainsValidator() throws Exception { - runTestFile("draft2019-09/contains.json"); - } - - @Test - public void testDefaultValidator() throws Exception { - runTestFile("draft2019-09/default.json"); - } - - @Test - @Disabled - public void testDefsValidator() throws Exception { - runTestFile("draft2019-09/defs.json"); - } - - @Test - public void testDependenciesValidator() throws Exception { - runTestFile("draft2019-09/dependencies.json"); - } - - @Test - public void testEnumValidator() throws Exception { - runTestFile("draft2019-09/enum.json"); - } - - @Test - public void testExclusiveMaximumValidator() throws Exception { - runTestFile("draft2019-09/exclusiveMaximum.json"); - } - - @Test - public void testExclusiveMinimumValidator() throws Exception { - runTestFile("draft2019-09/exclusiveMinimum.json"); - } - - @Test - public void testFormatValidator() throws Exception { - runTestFile("draft2019-09/format.json"); - } - - @Test - public void testIfThenElseValidator() throws Exception { - runTestFile("draft2019-09/if-then-else.json"); - } - - @Test - public void testItemsValidator() throws Exception { - runTestFile("draft2019-09/items.json"); - } - - @Test - public void testMaximumValidator() throws Exception { - runTestFile("draft2019-09/maximum.json"); - } - - @Test - public void testMaxItemsValidator() throws Exception { - runTestFile("draft2019-09/maxItems.json"); - } - - @Test - public void testMaxLengthValidator() throws Exception { - runTestFile("draft2019-09/maxLength.json"); - } - - @Test - public void testMaxPropertiesValidator() throws Exception { - runTestFile("draft2019-09/maxProperties.json"); - } - - @Test - public void testMinimumValidator() throws Exception { - runTestFile("draft2019-09/minimum.json"); - } - - @Test - public void testMinItemsValidator() throws Exception { - runTestFile("draft2019-09/minItems.json"); - } - - @Test - public void testMinLengthValidator() throws Exception { - runTestFile("draft2019-09/minLength.json"); - } - - @Test - public void testMinPropertiesValidator() throws Exception { - runTestFile("draft2019-09/minProperties.json"); - } - - @Test - public void testMultipleOfValidator() throws Exception { - runTestFile("draft2019-09/multipleOf.json"); - } - - @Test - public void testNotValidator() throws Exception { - runTestFile("draft2019-09/not.json"); - } - - @Test - public void testOneOfValidator() throws Exception { - runTestFile("draft2019-09/oneOf.json"); - } - - @Test - public void testPatternValidator() throws Exception { - runTestFile("draft2019-09/pattern.json"); - } - - @Test - public void testPatternPropertiesValidator() throws Exception { - runTestFile("draft2019-09/patternProperties.json"); - } - - @Test - public void testPropertiesValidator() throws Exception { - runTestFile("draft2019-09/properties.json"); - } - - @Test - public void testPropertyNamesValidator() throws Exception { - runTestFile("draft2019-09/propertyNames.json"); - } - - @Test - @Disabled - public void testRefValidator() throws Exception { - runTestFile("draft2019-09/ref.json"); - } - - @Test - public void testRefRemoteValidator() throws Exception { - runTestFile("draft2019-09/refRemote.json"); - } - - @Test - @Disabled - public void testRefRemoteValidator_Ignored() throws Exception { - runTestFile("draft2019-09/refRemote_ignored.json"); - } - - @Test - public void testRequiredValidator() throws Exception { - runTestFile("draft2019-09/required.json"); - } - - @Test - public void testTypeValidator() throws Exception { - runTestFile("draft2019-09/type.json"); - } - - @Test - public void testUniqueItemsValidator() throws Exception { - runTestFile("draft2019-09/uniqueItems.json"); - } - - @Test - public void testDependentRequiredValidator() throws Exception { - runTestFile("draft2019-09/dependentRequired.json"); - } - - @Test - public void testDependentSchemasValidator() throws Exception { - runTestFile("draft2019-09/dependentSchemas.json"); - } - -} - \ No newline at end of file diff --git a/src/test/java/com/networknt/schema/V4JsonSchemaTest.java b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java index 9e757ffe7..cb203ef8a 100644 --- a/src/test/java/com/networknt/schema/V4JsonSchemaTest.java +++ b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java @@ -18,358 +18,86 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; +import java.io.FileInputStream; import java.io.IOException; -import java.net.URL; +import java.io.InputStream; +import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; -public class V4JsonSchemaTest extends BaseSuiteJsonSchemaTest { - public V4JsonSchemaTest() { - super(SpecVersion.VersionFlag.V4); - } - - @Test(/*expected = java.lang.StackOverflowError.class*/) - public void testLoadingWithId() throws Exception { - URL url = new URL("http://localhost:1234/self_ref/selfRef.json"); - JsonNode schemaJson = mapper.readTree(url); - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); - @SuppressWarnings("unused") - JsonSchema schema = factory.getSchema(schemaJson); - } - - @Test - public void testFormatDateTimeValidator() throws Exception { - runTestFile("draft4/optional/format/date-time.json"); - } - - @Test - public void testFormatEmailValidator() throws Exception { - runTestFile("draft4/optional/format/email.json"); - } - - @Test - public void testFormatHostnameValidator() throws Exception { - runTestFile("draft4/optional/format/hostname.json"); - } - - @Test - public void testFormatIpv4Validator() throws Exception { - runTestFile("draft4/optional/format/ipv4.json"); - } - - @Test - public void testFormatIpv6Validator() throws Exception { - runTestFile("draft4/optional/format/ipv6.json"); - } - - @Test - public void testFormatUnknownValidator() throws Exception { - runTestFile("draft4/optional/format/unknown.json"); - } - - @Test - public void testFormatUriValidator() throws Exception { - runTestFile("draft4/optional/format/uri.json"); - } - - @Test - public void testBignumValidator() throws Exception { - runTestFile("draft4/optional/bignum.json"); - } - - @Test - public void testOptionalFormatValidator() throws Exception { - runTestFile("draft4/optional/format.json"); - } - - @Test - public void testComplexSchema() throws Exception { - runTestFile("draft4/optional/complex.json"); - } - - @Test - public void testFloatOverflowValidator() throws Exception { - runTestFile("draft4/optional/float-overflow.json"); - } - - @Test - public void testNonBmpRegexValidator() throws Exception { - runTestFile("draft4/optional/non-bmp-regex.json"); - } - - @Test - public void testZeroTerminatedFloatsValidator() throws Exception { - runTestFile("draft4/optional/zeroTerminatedFloats.json"); - } - - @Test - public void testAdditionalItemsValidator() throws Exception { - runTestFile("draft4/additionalItems.json"); - } - - @Test - public void testAdditionalPropertiesValidator() throws Exception { - runTestFile("draft4/additionalProperties.json"); - } - - @Test - public void testAllOfValidator() throws Exception { - runTestFile("draft4/allOf.json"); - } - - @Test - public void testAnyOFValidator() throws Exception { - runTestFile("draft4/anyOf.json"); - } - - @Test - public void testDefaultValidator() throws Exception { - runTestFile("draft4/default.json"); - } - - @Test - public void testDefinitionsValidator() throws Exception { - runTestFile("draft4/definitions.json"); - } - - @Test - public void testDependenciesValidator() throws Exception { - runTestFile("draft4/dependencies.json"); - } - - @Test - public void testEnumValidator() throws Exception { - runTestFile("draft4/enum.json"); - } - - @Test - public void testFormatValidator() throws Exception { - runTestFile("draft4/format.json"); - } - - @Test - @Disabled - public void testIdValidator() throws Exception { - runTestFile("draft4/id.json"); - } - - @Test - public void testInfiniteLoopDetectionValidator() throws Exception { - runTestFile("draft4/infinite-loop-detection.json"); - } +class V4JsonSchemaTest { - @Test - public void testItemsValidator() throws Exception { - runTestFile("draft4/items.json"); - } + protected ObjectMapper mapper = new ObjectMapper(); - @Test - public void testMaximumValidator() throws Exception { - runTestFile("draft4/maximum.json"); - } - - @Test - public void testMaxItemsValidator() throws Exception { - runTestFile("draft4/maxItems.json"); - } - - @Test - public void testMaxLengthValidator() throws Exception { - runTestFile("draft4/maxLength.json"); - } - - @Test - public void testMaxPropertiesValidator() throws Exception { - runTestFile("draft4/maxProperties.json"); - } - - @Test - public void testMinimumValidator() throws Exception { - runTestFile("draft4/minimum.json"); - } - - @Test - public void testMinItemsValidator() throws Exception { - runTestFile("draft4/minItems.json"); - } - - @Test - public void testMinLengthValidator() throws Exception { - runTestFile("draft4/minLength.json"); - } - - @Test - public void testMinPropertiesValidator() throws Exception { - runTestFile("draft4/minProperties.json"); - } - - @Test - public void testMultipleOfValidator() throws Exception { - runTestFile("draft4/multipleOf.json"); - } - - @Test - public void testNotValidator() throws Exception { - runTestFile("draft4/not.json"); - } - - @Test - public void testOneOfValidator() throws Exception { - runTestFile("draft4/oneOf.json"); - } - - @Test - public void testPatternValidator() throws Exception { - runTestFile("draft4/pattern.json"); - } - - @Test - public void testPatternPropertiesValidator() throws Exception { - runTestFile("draft4/patternProperties.json"); - } - - @Test - public void testPropertiesValidator() throws Exception { - runTestFile("draft4/properties.json"); - } - - @Test - public void testRefValidator() throws Exception { - runTestFile("draft4/ref.json"); - } - - @Test - public void testRefRemoteValidator() throws Exception { - runTestFile("draft4/refRemote.json"); - } - - @Test - public void testRefIdReference() throws Exception { - runTestFile("draft4/idRef.json"); - } - - @Test - public void testRelativeRefRemoteValidator() throws Exception { - runTestFile("draft4/relativeRefRemote.json"); - } - - @Test - public void testRequiredValidator() throws Exception { - runTestFile("draft4/required.json"); - } - - @Test - public void testTypeValidator() throws Exception { - runTestFile("draft4/type.json"); - } - - @Test - public void testUnionTypeValidator() throws Exception { - runTestFile("draft4/extra/union_type.json"); - } - - @Test - public void testUniqueItemsValidator() throws Exception { - runTestFile("draft4/uniqueItems.json"); - } - - @Test - public void testEnumObject() throws Exception { - runTestFile("draft4/enumObject.json"); - } - - @Test - public void testIdSchemaWithUrl() throws Exception { - runTestFile("draft4/extra/property.json"); - } - - @Test - public void testSchemaFromClasspath() throws Exception { - runTestFile("draft4/extra/classpath/schema.json"); - } - - @Test - public void testUUIDValidator() throws Exception { - runTestFile("draft4/extra/uuid.json"); + @Test(/* expected = java.lang.StackOverflowError.class */) + void testLoadingWithId() throws Exception { + try (InputStream inputStream = new FileInputStream("src/test/resources/remotes/self_ref/selfRef.json")) { + JsonNode schemaJson = mapper.readTree(inputStream); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + @SuppressWarnings("unused") + Schema schema = factory.getSchema(schemaJson); + } } /** * Although, the data file has three errors, but only on is reported */ @Test - public void testFailFast_AllErrors() throws IOException { - try { - validateFailingFastSchemaFor("extra/product/product.schema.json", "extra/product/product-all-errors-data.json"); - fail("Exception must be thrown"); - } catch (JsonSchemaException e) { - final Set messages = e.getValidationMessages(); - assertEquals(1, messages.size()); - } + void testFailFast_AllErrors() throws IOException { + List messages = validateFailingFastSchemaFor("extra/product/product.schema.json", + "extra/product/product-all-errors-data.json"); + assertEquals(1, messages.size()); } /** * File contains only one error and that is reported. */ @Test - public void testFailFast_OneErrors() throws IOException { - try { - validateFailingFastSchemaFor("extra/product/product.schema.json", "extra/product/product-one-error-data.json"); - fail("Exception must be thrown"); - } catch (JsonSchemaException e) { - final Set messages = e.getValidationMessages(); - assertEquals(1, messages.size()); - } + void testFailFast_OneErrors() throws IOException { + List messages = validateFailingFastSchemaFor("extra/product/product.schema.json", + "extra/product/product-one-error-data.json"); + assertEquals(1, messages.size()); } /** * Although, the file contains two errors, but only one is reported */ @Test - public void testFailFast_TwoErrors() throws IOException { - try { - validateFailingFastSchemaFor("extra/product/product.schema.json", "extra/product/product-two-errors-data.json"); - fail("Exception must be thrown"); - } catch (JsonSchemaException e) { - final Set messages = e.getValidationMessages(); - assertEquals(1, messages.size()); - } + void testFailFast_TwoErrors() throws IOException { + List messages = validateFailingFastSchemaFor("extra/product/product.schema.json", + "extra/product/product-two-errors-data.json"); + assertEquals(1, messages.size()); } /** - * The file contains no errors, in ths case {@link Set}<{@link ValidationMessage}> must be empty + * The file contains no errors, in ths case + * {@link Set}<{@link Error}> must be empty */ @Test - public void testFailFast_NoErrors() throws IOException { - try { - final Set messages = validateFailingFastSchemaFor("extra/product/product.schema.json", "extra/product/product-no-errors-data.json"); - assertTrue(messages.isEmpty()); - } catch (JsonSchemaException e) { - fail("Must not get an errors"); - } + void testFailFast_NoErrors() throws IOException { + final List messages = validateFailingFastSchemaFor("extra/product/product.schema.json", + "extra/product/product-no-errors-data.json"); + assertTrue(messages.isEmpty()); } - private Set validateFailingFastSchemaFor(final String schemaFileName, final String dataFileName) throws IOException { + private List validateFailingFastSchemaFor(final String schemaFileName, final String dataFileName) throws IOException { final ObjectMapper objectMapper = new ObjectMapper(); final JsonNode schema = getJsonNodeFromResource(objectMapper, schemaFileName); final JsonNode dataFile = getJsonNodeFromResource(objectMapper, dataFileName); - final SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.setFailFast(true); - return JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)) - .objectMapper(objectMapper) - .build() - .getSchema(schema, config) - .validate(dataFile); + final SchemaRegistryConfig config = SchemaRegistryConfig.builder().failFast(true).build(); + return SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schema) + .validate(dataFile); } private JsonNode getJsonNodeFromResource(final ObjectMapper mapper, final String locationInTestResources) throws IOException { return mapper.readTree( - Thread.currentThread().getContextClassLoader() - .getResourceAsStream("draft4" + System.getProperty("file.separator") + locationInTestResources)); + Thread.currentThread().getContextClassLoader() + .getResourceAsStream("draft4" + System.getProperty("file.separator") + locationInTestResources)); } } diff --git a/src/test/java/com/networknt/schema/V6JsonSchemaTest.java b/src/test/java/com/networknt/schema/V6JsonSchemaTest.java deleted file mode 100644 index 7f3ad8a3a..000000000 --- a/src/test/java/com/networknt/schema/V6JsonSchemaTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -public class V6JsonSchemaTest extends BaseSuiteJsonSchemaTest { - public V6JsonSchemaTest() { - super(SpecVersion.VersionFlag.V6); - } - - @Test - public void testFormatIpv4Validator() throws Exception { - runTestFile("draft4/optional/format/ipv4.json"); - } - - @Test - public void testOptionalBignumValidator() throws Exception { - runTestFile("draft6/optional/bignum.json"); - } - - @Test - @Disabled - public void testEcmascriptRegexValidator() throws Exception { - runTestFile("draft6/optional/ecmascript-regex.json"); - } - - @Test - @Disabled - public void testZeroTerminatedFloatsValidator() throws Exception { - runTestFile("draft6/optional/zeroTerminatedFloats.json"); - } - - @Test - @Disabled - public void testOptionalFormatValidator() throws Exception { - runTestFile("draft6/optional/format.json"); - } - - @Test - public void testAdditionalItemsValidator() throws Exception { - runTestFile("draft6/additionalItems.json"); - } - - @Test - public void testAdditionalPropertiesValidator() throws Exception { - runTestFile("draft6/additionalProperties.json"); - } - - @Test - public void testAllOfValidator() throws Exception { - runTestFile("draft6/allOf.json"); - } - - @Test - public void testAnyOfValidator() throws Exception { - runTestFile("draft6/anyOf.json"); - } - - @Test - public void testBooleanSchemaValidator() throws Exception { - runTestFile("draft6/boolean_schema.json"); - } - - @Test - public void testConstValidator() throws Exception { - runTestFile("draft6/const.json"); - } - - @Test - public void testContainsValidator() throws Exception { - runTestFile("draft6/contains.json"); - } - - @Test - public void testDefaultValidator() throws Exception { - runTestFile("draft6/default.json"); - } - - @Test - public void testDefinitionsValidator() throws Exception { - runTestFile("draft6/definitions.json"); - } - - @Test - public void testDependenciesValidator() throws Exception { - runTestFile("draft6/dependencies.json"); - } - - @Test - public void testEnumValidator() throws Exception { - runTestFile("draft6/enum.json"); - } - - @Test - public void testExclusiveMaximumValidator() throws Exception { - runTestFile("draft6/exclusiveMaximum.json"); - } - - @Test - public void testExclusiveMinimumValidator() throws Exception { - runTestFile("draft6/exclusiveMinimum.json"); - } - - @Test - public void testFormatValidator() throws Exception { - runTestFile("draft6/format.json"); - } - - @Test - public void testItemsValidator() throws Exception { - runTestFile("draft6/items.json"); - } - - @Test - public void testMaximumValidator() throws Exception { - runTestFile("draft6/maximum.json"); - } - - @Test - public void testMaxItemsValidator() throws Exception { - runTestFile("draft6/maxItems.json"); - } - - @Test - public void testMaxLengthValidator() throws Exception { - runTestFile("draft6/maxLength.json"); - } - - @Test - public void testMaxPropertiesValidator() throws Exception { - runTestFile("draft6/maxProperties.json"); - } - - @Test - public void testMinimumValidator() throws Exception { - runTestFile("draft6/minimum.json"); - } - - @Test - public void testMinItemsValidator() throws Exception { - runTestFile("draft6/minItems.json"); - } - - @Test - public void testMinLengthValidator() throws Exception { - runTestFile("draft6/minLength.json"); - } - - @Test - public void testMinPropertiesValidator() throws Exception { - runTestFile("draft6/minProperties.json"); - } - - @Test - public void testMultipleOfValidator() throws Exception { - runTestFile("draft6/multipleOf.json"); - } - - @Test - public void testNotValidator() throws Exception { - runTestFile("draft6/not.json"); - } - - @Test - public void testOneOfValidator() throws Exception { - runTestFile("draft6/oneOf.json"); - } - - @Test - public void testPatternValidator() throws Exception { - runTestFile("draft6/pattern.json"); - } - - @Test - public void testPatternPropertiesValidator() throws Exception { - runTestFile("draft6/patternProperties.json"); - } - - @Test - public void testPropertiesValidator() throws Exception { - runTestFile("draft6/properties.json"); - } - - @Test - public void testPropertyNamesValidator() throws Exception { - runTestFile("draft6/propertyNames.json"); - } - - @Test - @Disabled - public void testRefValidator() throws Exception { - runTestFile("draft6/ref.json"); - } - - @Test - public void testRefRemoteValidator() throws Exception { - runTestFile("draft6/refRemote.json"); - } - - @Test - public void testRefIdReference() throws Exception { - runTestFile("draft6/idRef.json"); - } - - @Test - @Disabled - public void testRefRemoteValidator_Ignored() throws Exception { - runTestFile("draft6/refRemote_ignored.json"); - } - - @Test - public void testRequiredValidator() throws Exception { - runTestFile("draft6/required.json"); - } - - @Test - public void testTypeValidator() throws Exception { - runTestFile("draft6/type.json"); - } - - @Test - public void testUniqueItemsValidator() throws Exception { - runTestFile("draft6/uniqueItems.json"); - } - -} diff --git a/src/test/java/com/networknt/schema/V7JsonSchemaTest.java b/src/test/java/com/networknt/schema/V7JsonSchemaTest.java deleted file mode 100644 index 8890b27f2..000000000 --- a/src/test/java/com/networknt/schema/V7JsonSchemaTest.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (c) 2020 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.networknt.schema; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -public class V7JsonSchemaTest extends BaseSuiteJsonSchemaTest { - public V7JsonSchemaTest() { - super(SpecVersion.VersionFlag.V7); - } - - @Test - public void testOptionalBignumValidator() throws Exception { - runTestFile("draft7/optional/bignum.json"); - } - - @Test - @Disabled - public void testOptionalContentValidator() throws Exception { - runTestFile("draft7/optional/content.json"); - } - - @Test - @Disabled - public void testEcmascriptRegexValidator() throws Exception { - runTestFile("draft7/optional/ecmascript-regex.json"); - } - - @Test - @Disabled - public void testZeroTerminatedFloatsValidator() throws Exception { - runTestFile("draft7/optional/zeroTerminatedFloats.json"); - } - - @Test - public void testOptionalFormatDateValidator() throws Exception { - runTestFile("draft7/optional/format/date.json"); - } - - @Test - public void testOptionalFormatDateTimeValidator() throws Exception { - runTestFile("draft7/optional/format/date-time.json"); - } - - @Test - public void testOptionalFormatEmailValidator() throws Exception { - runTestFile("draft7/optional/format/email.json"); - } - - @Test - public void testOptionalFormatHostnameValidator() throws Exception { - runTestFile("draft7/optional/format/hostname.json"); - } - - @Test - @Disabled - public void testOptionalFormatIdnEmailValidator() throws Exception { - runTestFile("draft7/optional/format/idn-email.json"); - } - - @Test - @Disabled - public void testOptionalFormatIdnHostnameValidator() throws Exception { - runTestFile("draft7/optional/format/idn-hostname.json"); - } - - @Test - public void testOptionalFormatIpv4Validator() throws Exception { - runTestFile("draft7/optional/format/ipv4.json"); - } - - @Test - public void testOptionalFormatIpv6Validator() throws Exception { - runTestFile("draft7/optional/format/ipv6.json"); - } - - @Test - @Disabled - public void testOptionalFormatIriValidator() throws Exception { - runTestFile("draft7/optional/format/iri.json"); - } - - @Test - @Disabled - public void testOptionalFormatIriReferenceValidator() throws Exception { - runTestFile("draft7/optional/format/iri-reference.json"); - } - - @Test - @Disabled - public void testOptionalFormatJsonPointerValidator() throws Exception { - runTestFile("draft7/optional/format/json-pointer.json"); - } - - @Test - @Disabled - public void testOptionalFormatRegexValidator() throws Exception { - runTestFile("draft7/optional/format/regex.json"); - } - - @Test - @Disabled - public void testOptionalFormatRelativeJsonPointerValidator() throws Exception { - runTestFile("draft7/optional/format/relative-json-pointer.json"); - } - - @Test - @Disabled - public void testOptionalFormatTimeValidator() throws Exception { - runTestFile("draft7/optional/format/time.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriValidator() throws Exception { - runTestFile("draft7/optional/format/uri.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriReferenceValidator() throws Exception { - runTestFile("draft7/optional/format/uri-reference.json"); - } - - @Test - @Disabled - public void testOptionalFormatUriTemplateValidator() throws Exception { - runTestFile("draft7/optional/format/uri-template.json"); - } - - @Test - public void testAdditionalItemsValidator() throws Exception { - runTestFile("draft7/additionalItems.json"); - } - - @Test - public void testAdditionalPropertiesValidator() throws Exception { - runTestFile("draft7/additionalProperties.json"); - } - - @Test - public void testAllOfValidator() throws Exception { - runTestFile("draft7/allOf.json"); - } - - @Test - public void testAnyOfValidator() throws Exception { - runTestFile("draft7/anyOf.json"); - } - - @Test - public void testBooleanSchemaValidator() throws Exception { - runTestFile("draft7/boolean_schema.json"); - } - - @Test - public void testConstValidator() throws Exception { - runTestFile("draft7/const.json"); - } - - @Test - public void testContainsValidator() throws Exception { - runTestFile("draft7/contains.json"); - } - - @Test - public void testDefaultValidator() throws Exception { - runTestFile("draft7/default.json"); - } - - @Test - public void testDefsValidator() throws Exception { - runTestFile("draft7/definitions.json"); - } - - @Test - public void testDependenciesValidator() throws Exception { - runTestFile("draft7/dependencies.json"); - } - - @Test - public void testEnumValidator() throws Exception { - runTestFile("draft7/enum.json"); - } - - @Test - public void testExclusiveMaximumValidator() throws Exception { - runTestFile("draft7/exclusiveMaximum.json"); - } - - @Test - public void testExclusiveMinimumValidator() throws Exception { - runTestFile("draft7/exclusiveMinimum.json"); - } - - @Test - public void testFormatValidator() throws Exception { - runTestFile("draft7/format.json"); - } - - @Test - public void testIfThenElseValidator() throws Exception { - runTestFile("draft7/if-then-else.json"); - } - - @Test - public void testItemsValidator() throws Exception { - runTestFile("draft7/items.json"); - } - - @Test - public void testMaximumValidator() throws Exception { - runTestFile("draft7/maximum.json"); - } - - @Test - public void testMaxItemsValidator() throws Exception { - runTestFile("draft7/maxItems.json"); - } - - @Test - public void testMaxLengthValidator() throws Exception { - runTestFile("draft7/maxLength.json"); - } - - @Test - public void testMaxPropertiesValidator() throws Exception { - runTestFile("draft7/maxProperties.json"); - } - - @Test - public void testMinimumValidator() throws Exception { - runTestFile("draft7/minimum.json"); - } - - @Test - public void testMinItemsValidator() throws Exception { - runTestFile("draft7/minItems.json"); - } - - @Test - public void testMinLengthValidator() throws Exception { - runTestFile("draft7/minLength.json"); - } - - @Test - public void testMinPropertiesValidator() throws Exception { - runTestFile("draft7/minProperties.json"); - } - - @Test - public void testMultipleOfValidator() throws Exception { - runTestFile("draft7/multipleOf.json"); - } - - @Test - public void testNotValidator() throws Exception { - runTestFile("draft7/not.json"); - } - - @Test - public void testOneOfValidator() throws Exception { - runTestFile("draft7/oneOf.json"); - } - - @Test - public void testPatternValidator() throws Exception { - runTestFile("draft7/pattern.json"); - } - - @Test - public void testPatternPropertiesValidator() throws Exception { - runTestFile("draft7/patternProperties.json"); - } - - @Test - public void testPropertiesValidator() throws Exception { - runTestFile("draft7/properties.json"); - } - - @Test - public void testPropertyNamesValidator() throws Exception { - runTestFile("draft7/propertyNames.json"); - } - - @Test - @Disabled - public void testRefValidator() throws Exception { - runTestFile("draft7/ref.json"); - } - - @Test - public void testRefRemoteValidator() throws Exception { - runTestFile("draft7/refRemote.json"); - } - - @Test - public void testRefIdReference() throws Exception { - runTestFile("draft7/idRef.json"); - } - - @Test - @Disabled - public void testRefRemoteValidator_Ignored() throws Exception { - runTestFile("draft7/refRemote_ignored.json"); - } - - @Test - public void testRequiredValidator() throws Exception { - runTestFile("draft7/required.json"); - } - - @Test - public void testTypeValidator() throws Exception { - runTestFile("draft7/type.json"); - } - - @Test - public void testUniqueItemsValidator() throws Exception { - runTestFile("draft7/uniqueItems.json"); - } - - @Test - public void testMultipleOfScale() throws Exception { - runTestFile("multipleOfScale.json"); - } - -} diff --git a/src/test/java/com/networknt/schema/ValidatorTypeCodeTest.java b/src/test/java/com/networknt/schema/ValidatorTypeCodeTest.java deleted file mode 100644 index 861529722..000000000 --- a/src/test/java/com/networknt/schema/ValidatorTypeCodeTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Network New Technologies Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.networknt.schema; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class ValidatorTypeCodeTest { - - @Test - public void testFromValueString() { - assertEquals(ValidatorTypeCode.ADDITIONAL_PROPERTIES, ValidatorTypeCode.fromValue("additionalProperties")); - } - - @Test - public void testFromValueAll() { - for (ValidatorTypeCode code : ValidatorTypeCode.values()) { - assertEquals(code, ValidatorTypeCode.fromValue(code.getValue())); - } - } - - @Test - public void testFromValueMissing() { - Assertions.assertThrows(IllegalArgumentException.class, () -> assertEquals(ValidatorTypeCode.ADDITIONAL_PROPERTIES, ValidatorTypeCode.fromValue("missing"))); - } - - @Test - public void testIfThenElseNotInV4() { - List list = ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V4); - Assertions.assertFalse(list.contains(ValidatorTypeCode.fromValue("if"))); - } - - @Test - public void testExclusiveMaximumNotInV4() { - List list = ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V4); - Assertions.assertFalse(list.contains(ValidatorTypeCode.fromValue("exclusiveMaximum"))); - } - - -} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntBasicRunner.java b/src/test/java/com/networknt/schema/benchmark/NetworkntBasicRunner.java new file mode 100644 index 000000000..48c6b8f21 --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntBasicRunner.java @@ -0,0 +1,56 @@ +package com.networknt.schema.benchmark; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; + +/** + * Basic Benchmark. + */ +public class NetworkntBasicRunner implements Callable { + private static final Logger logger = LoggerFactory.getLogger(NetworkntBasicRunner.class); + private Schema jsonSchema; + private JsonNode schemas; + private List schemaNames; + + public NetworkntBasicRunner() { + ObjectMapper objectMapper = new ObjectMapper(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ObjectReader reader = objectMapper.reader(); + JsonNode schemaNode = reader.readTree(classLoader.getResourceAsStream("benchmark/basic/schema-draft4.json")); + jsonSchema = factory.getSchema(schemaNode); + + JsonNode root = reader.readTree(classLoader.getResourceAsStream("benchmark/basic/perftest.json")); + schemas = root.get("schemas"); + + List names = new ArrayList<>(); + schemas.fieldNames().forEachRemaining(names::add); + schemaNames = names; + } catch (IOException e) { + logger.error("Failed to initialize NetworkntBasicRunner", e); + } + } + + @Override + public Object call() { + List results = new ArrayList<>(); + for (String name : schemaNames) { + JsonNode json = schemas.get(name); + results.add(jsonSchema.validate(json)); + } + return results; + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntBenchmark.java b/src/test/java/com/networknt/schema/benchmark/NetworkntBenchmark.java new file mode 100644 index 000000000..f72374653 --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntBenchmark.java @@ -0,0 +1,43 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +public class NetworkntBenchmark { + + @State(Scope.Thread) + public static class BenchmarkState { + private Callable basic = new NetworkntBasicRunner(); + } + + @BenchmarkMode(Mode.Throughput) + @Fork(2) + @Warmup(iterations = 5, time = 5) + @Measurement(iterations = 5, time = 5) + @Benchmark + public void basic(BenchmarkState state, Blackhole blackhole) throws Exception { + blackhole.consume(state.basic.call()); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder().include(NetworkntBenchmark.class.getSimpleName()) + .addProfiler(GCProfiler.class).build(); + + new Runner(opt).run(); + } + +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntPerf.java b/src/test/java/com/networknt/schema/benchmark/NetworkntPerf.java new file mode 100644 index 000000000..df179c032 --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntPerf.java @@ -0,0 +1,10 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +public class NetworkntPerf { + public static void main(String[] args) throws Exception { + Callable runner = new NetworkntBasicRunner(); + runner.call(); + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012OptionalPerf.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012OptionalPerf.java new file mode 100644 index 000000000..d5dee00aa --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012OptionalPerf.java @@ -0,0 +1,13 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +import com.networknt.schema.SpecificationVersion; + +public class NetworkntTestSuite202012OptionalPerf { + public static void main(String[] args) throws Exception { + Callable runner = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases.findTestCases( + SpecificationVersion.DRAFT_2020_12, "src/test/suite/tests/draft2020-12", TestCaseFilter.optionalType())); + runner.call(); + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012RequiredPerf.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012RequiredPerf.java new file mode 100644 index 000000000..d8f8c576f --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuite202012RequiredPerf.java @@ -0,0 +1,13 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +import com.networknt.schema.SpecificationVersion; + +public class NetworkntTestSuite202012RequiredPerf { + public static void main(String[] args) throws Exception { + Callable runner = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases.findTestCases( + SpecificationVersion.DRAFT_2020_12, "src/test/suite/tests/draft2020-12", TestCaseFilter.requiredType())); + runner.call(); + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteOptionalBenchmark.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteOptionalBenchmark.java new file mode 100644 index 000000000..1c639e14c --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteOptionalBenchmark.java @@ -0,0 +1,82 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import com.networknt.schema.SpecificationVersion; + +public class NetworkntTestSuiteOptionalBenchmark { + public static final String VERSION_202012 = "2020-12"; + public static final String VERSION_201909 = "2019-09"; + public static final String VERSION_7 = "7"; + public static final String VERSION_6 = "6"; + public static final String VERSION_4 = "4"; + + @State(Scope.Thread) + public static class BenchmarkState { + @Param({ VERSION_202012, VERSION_201909, VERSION_7, VERSION_6, VERSION_4 }) + private String specification; + + private Callable draft202012Optional = new NetworkntTestSuiteRunner( + NetworkntTestSuiteTestCases.findTestCases(SpecificationVersion.DRAFT_2020_12, "src/test/suite/tests/draft2020-12", + TestCaseFilter.optionalType())); + private Callable draft201909Optional = new NetworkntTestSuiteRunner( + NetworkntTestSuiteTestCases.findTestCases(SpecificationVersion.DRAFT_2019_09, "src/test/suite/tests/draft2019-09", + TestCaseFilter.optionalType())); + private Callable draft7Optional = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_7, "src/test/suite/tests/draft7", TestCaseFilter.optionalType())); + private Callable draft6Optional = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_6, "src/test/suite/tests/draft6", TestCaseFilter.optionalType())); + private Callable draft4Optional = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_4, "src/test/suite/tests/draft4", TestCaseFilter.optionalType())); + + private Callable getTestSuite() { + switch (specification) { + case VERSION_202012: + return draft202012Optional; + case VERSION_201909: + return draft201909Optional; + case VERSION_7: + return draft7Optional; + case VERSION_6: + return draft6Optional; + case VERSION_4: + return draft4Optional; + default: + throw new RuntimeException("No test suite for specification " + specification); + } + } + } + + @BenchmarkMode(Mode.Throughput) + @Fork(2) + @Warmup(iterations = 5, time = 5) + @Measurement(iterations = 5, time = 5) + @Benchmark + public void testsuite(BenchmarkState state, Blackhole blackhole) throws Exception { + blackhole.consume(state.getTestSuite().call()); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder().include(NetworkntTestSuiteOptionalBenchmark.class.getSimpleName()) + .addProfiler(GCProfiler.class).build(); + + new Runner(opt).run(); + } + +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRequiredBenchmark.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRequiredBenchmark.java new file mode 100644 index 000000000..60b24d17b --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRequiredBenchmark.java @@ -0,0 +1,80 @@ +package com.networknt.schema.benchmark; + +import java.util.concurrent.Callable; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import com.networknt.schema.SpecificationVersion; + +public class NetworkntTestSuiteRequiredBenchmark { + public static final String VERSION_202012 = "2020-12"; + public static final String VERSION_201909 = "2019-09"; + public static final String VERSION_7 = "7"; + public static final String VERSION_6 = "6"; + public static final String VERSION_4 = "4"; + + @State(Scope.Thread) + public static class BenchmarkState { + @Param({ VERSION_202012, VERSION_201909, VERSION_7, VERSION_6, VERSION_4 }) + private String specification; + + private Callable draft202012 = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases.findTestCases( + SpecificationVersion.DRAFT_2020_12, "src/test/suite/tests/draft2020-12", TestCaseFilter.requiredType())); + private Callable draft201909 = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases.findTestCases( + SpecificationVersion.DRAFT_2019_09, "src/test/suite/tests/draft2019-09", TestCaseFilter.requiredType())); + private Callable draft7 = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_7, "src/test/suite/tests/draft7", TestCaseFilter.requiredType())); + private Callable draft6 = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_6, "src/test/suite/tests/draft6", TestCaseFilter.requiredType())); + private Callable draft4 = new NetworkntTestSuiteRunner(NetworkntTestSuiteTestCases + .findTestCases(SpecificationVersion.DRAFT_4, "src/test/suite/tests/draft4", TestCaseFilter.requiredType())); + + private Callable getTestSuite() { + switch (specification) { + case VERSION_202012: + return draft202012; + case VERSION_201909: + return draft201909; + case VERSION_7: + return draft7; + case VERSION_6: + return draft6; + case VERSION_4: + return draft4; + default: + throw new RuntimeException("No test suite for specification " + specification); + } + } + } + + @BenchmarkMode(Mode.Throughput) + @Fork(2) + @Warmup(iterations = 5, time = 5) + @Measurement(iterations = 5, time = 5) + @Benchmark + public void testsuite(BenchmarkState state, Blackhole blackhole) throws Exception { + blackhole.consume(state.getTestSuite().call()); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder().include(NetworkntTestSuiteRequiredBenchmark.class.getSimpleName()) + .addProfiler(GCProfiler.class).build(); + + new Runner(opt).run(); + } + +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRunner.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRunner.java new file mode 100644 index 000000000..1f3baa11b --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteRunner.java @@ -0,0 +1,36 @@ +package com.networknt.schema.benchmark; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import com.networknt.schema.OutputFormat; +import com.networknt.schema.suite.TestSpec; + +/** + * Test Suite Test Case Benchmark. + */ +public class NetworkntTestSuiteRunner implements Callable { + private final List testCases; + + public NetworkntTestSuiteRunner(List testCases) { + this.testCases = testCases; + } + + @Override + public Object call() { + List results = new ArrayList<>(); + for (NetworkntTestSuiteTestCase testCase : testCases) { + for (TestSpec testSpec : testCase.getTestCase().getTests()) { + results.add( + testCase.getSchema().validate(testSpec.getData(), OutputFormat.DEFAULT, executionContext -> { + if (testCase.getFormatAssertionsEnabled() != null) { + executionContext.executionConfig(executionConfig -> executionConfig + .formatAssertionsEnabled(testCase.getFormatAssertionsEnabled())); + } + })); + } + } + return results; + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCase.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCase.java new file mode 100644 index 000000000..16345e080 --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCase.java @@ -0,0 +1,28 @@ +package com.networknt.schema.benchmark; + +import com.networknt.schema.Schema; +import com.networknt.schema.suite.TestCase; + +public class NetworkntTestSuiteTestCase { + private final Schema schema; + private final TestCase testCase; + private final Boolean formatAssertionsEnabled; + + public NetworkntTestSuiteTestCase(Schema schema, TestCase testCase, Boolean formatAssertionsEnabled) { + this.schema = schema; + this.testCase = testCase; + this.formatAssertionsEnabled = formatAssertionsEnabled; + } + + public Schema getSchema() { + return schema; + } + + public TestCase getTestCase() { + return testCase; + } + + public Boolean getFormatAssertionsEnabled() { + return formatAssertionsEnabled; + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCases.java b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCases.java new file mode 100644 index 000000000..c54e99dab --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/NetworkntTestSuiteTestCases.java @@ -0,0 +1,78 @@ +package com.networknt.schema.benchmark; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistryConfig; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.regex.JoniRegularExpressionFactory; +import com.networknt.schema.resource.SchemaLoader; +import com.networknt.schema.suite.TestCase; +import com.networknt.schema.suite.TestSource; + +public class NetworkntTestSuiteTestCases { + private static String toForwardSlashPath(Path file) { + return file.toString().replace('\\', '/'); + } + + private static List findTestCasePaths(String basePath, Predicate filter) { + try (Stream paths = Files.walk(Paths.get(basePath))) { + return paths.filter(path -> path.toString().endsWith(".json")).filter(filter).collect(Collectors.toList()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static List findTestCases(SpecificationVersion defaultVersion, String basePath) { + return findTestCases(defaultVersion, basePath, path -> true); + } + + public static List findTestCases(SpecificationVersion defaultVersion, String basePath, + Predicate filter) { + SchemaLoader schemaLoader = new SchemaLoader(location -> { + String iri = location.toString(); + if (iri.startsWith("http://localhost:1234")) { + return () -> { + String path = iri.substring("http://localhost:1234".length()); + return new FileInputStream("src/test/suite/remotes" + path); + }; + } + return null; + }); + List results = new ArrayList<>(); + List testCasePaths = findTestCasePaths(basePath, filter); + for (Path path : testCasePaths) { + Optional optionalTestSource = TestSource.loadFrom(path, false, ""); + if (optionalTestSource.isPresent()) { + TestSource testSource = optionalTestSource.get(); + for (TestCase testCase : testSource.getTestCases()) { + SchemaLocation testCaseFileUri = SchemaLocation + .of("classpath:" + toForwardSlashPath(testCase.getSpecification())); + SchemaRegistryConfig config = SchemaRegistryConfig.builder() + .regularExpressionFactory(JoniRegularExpressionFactory.getInstance()).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(defaultVersion, + builder -> builder.schemaRegistryConfig(config) + .schemaLoader(schemaLoader)) + .getSchema(testCaseFileUri, testCase.getSchema()); + results.add(new NetworkntTestSuiteTestCase(schema, testCase, + testCase.getSource().getPath().getParent().toString().endsWith("format") ? true : null)); + } + } + } + return results; + } +} diff --git a/src/test/java/com/networknt/schema/benchmark/TestCaseFilter.java b/src/test/java/com/networknt/schema/benchmark/TestCaseFilter.java new file mode 100644 index 000000000..e3e1b6ffb --- /dev/null +++ b/src/test/java/com/networknt/schema/benchmark/TestCaseFilter.java @@ -0,0 +1,22 @@ +package com.networknt.schema.benchmark; + +import java.nio.file.Path; +import java.util.function.Predicate; + +public class TestCaseFilter { + public static Predicate requiredType() { + return optionalType().negate(); + } + + public static Predicate optionalType() { + return path -> { + int count = path.getNameCount(); + for (int x = count - 2; x > 0; x--) { + if (path.getName(x).toString().equals("optional")) { + return true; + } + } + return false; + }; + } +} diff --git a/src/test/java/com/networknt/schema/format/IriFormatTest.java b/src/test/java/com/networknt/schema/format/IriFormatTest.java new file mode 100644 index 000000000..84479f45a --- /dev/null +++ b/src/test/java/com/networknt/schema/format/IriFormatTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; + +class IriFormatTest { + @Test + void uriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void queryWithBracketsShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } + + @Test + void queryWithEncodedBracketsShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter%5Btest%5D=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void iriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"http://\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noSchemeNoAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"//\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noPathShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"about:\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java b/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java new file mode 100644 index 000000000..a05a262a5 --- /dev/null +++ b/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SchemaRegistryConfig; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; + +class IriReferenceFormatTest { + @Test + void uriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf\"", + InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void queryWithBracketsShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"", + InputFormat.JSON); + assertFalse(messages.isEmpty()); + } + + @Test + void queryWithEncodedBracketsShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter%5Btest%5D=1\"", + InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void iriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"", + InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void noAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"http://\"", InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void noSchemeNoAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + SchemaRegistryConfig config = SchemaRegistryConfig.builder().formatAssertionsEnabled(true).build(); + Schema schema = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, builder -> builder.schemaRegistryConfig(config)) + .getSchema(schemaData); + List messages = schema.validate("\"//\"", InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void noPathShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"iri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"about:\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + +} diff --git a/src/test/java/com/networknt/schema/format/TimeFormatTest.java b/src/test/java/com/networknt/schema/format/TimeFormatTest.java new file mode 100644 index 000000000..2081d2203 --- /dev/null +++ b/src/test/java/com/networknt/schema/format/TimeFormatTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; + +class TimeFormatTest { + + enum ValidTimeFormatInput { + Z_OFFSET_LEAP_SECOND("23:59:60Z"), + POSITIVE_OFFSET_LEAP_SECOND("07:59:60+08:00"), + NEGATIVE_OFFSET_LEAP_SECOND("15:59:60-08:00"), + Z_OFFSET_MIN_TIME("00:00:00Z"), + Z_OFFSET("23:59:59Z"), + POSITIVE_OFFSET_ZERO("17:00:00+00:00"), + POSITIVE_OFFSET_MAX("17:00:00+23:59"), + NEGATIVE_OFFSET_ZERO("17:00:00-00:00"), + NEGATIVE_OFFSET_ONE_DAY("17:00:00-07:00"), + NEGATIVE_OFFSET_MAX("17:00:00-23:59"); + + String format; + + ValidTimeFormatInput(String format) { + this.format = format; + } + } + + @ParameterizedTest + @EnumSource(ValidTimeFormatInput.class) + void validTimeShouldPass(ValidTimeFormatInput input) { + String schemaData = "{\r\n" + + " \"format\": \"time\"\r\n" + + "}"; + + String inputData = "\""+input.format+"\""; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON, + executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + enum InvalidTimeFormatInput { + NEGATIVE_OFFSET_INVALID_LEAP_SECOND("23:59:60-07:00"), + Z_OFFSET_EXCEED_LEAP_SECOND("23:59:61Z"), + Z_OFFSET_EXCEED_TIME("24:00:00Z"), + POSITIVE_OFFSET_EXCEED_MAX("17:00:00+24:00"), + NEGATIVE_OFFSET_EXCEED_MAX("17:00:00-24:00"); + + String format; + + InvalidTimeFormatInput(String format) { + this.format = format; + } + } + + @ParameterizedTest + @EnumSource(InvalidTimeFormatInput.class) + void invalidTimeShouldFail(InvalidTimeFormatInput input) { + String schemaData = "{\r\n" + + " \"format\": \"time\"\r\n" + + "}"; + + String inputData = "\""+input.format+"\""; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate(inputData, InputFormat.JSON, + executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/format/UriFormatTest.java b/src/test/java/com/networknt/schema/format/UriFormatTest.java new file mode 100644 index 000000000..c7a5c83ca --- /dev/null +++ b/src/test/java/com/networknt/schema/format/UriFormatTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; + +class UriFormatTest { + @Test + void uriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void queryWithBracketsShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } + + @Test + void queryWithEncodedBracketsShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter%5Btest%5D=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void iriShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } + + @Test + void noAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"http://\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noSchemeNoAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"//\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noPathShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"about:\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java b/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java new file mode 100644 index 000000000..5092c3c01 --- /dev/null +++ b/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.format; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; + +class UriReferenceFormatTest { + @Test + void uriShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void queryWithBracketsShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } + + @Test + void queryWithEncodedBracketsShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/product.pdf?filter%5Btest%5D=1\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void iriShouldFail() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"", + InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertFalse(messages.isEmpty()); + } + + @Test + void noAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"http://\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noSchemeNoAuthorityShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"//\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } + + @Test + void noPathShouldPass() { + String schemaData = "{\r\n" + + " \"format\": \"uri-reference\"\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + List messages = schema.validate("\"about:\"", InputFormat.JSON, executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertTrue(messages.isEmpty()); + } +} diff --git a/src/test/java/com/networknt/schema/i18n/LocalesTest.java b/src/test/java/com/networknt/schema/i18n/LocalesTest.java new file mode 100644 index 000000000..56a612afa --- /dev/null +++ b/src/test/java/com/networknt/schema/i18n/LocalesTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Locale; + +import org.junit.jupiter.api.Test; + +class LocalesTest { + + @Test + void unsupportedShouldReturnLocaleRoot() { + Locale result = Locales.findSupported("en-US;q=0.9,en-GB;q=1.0"); + assertEquals("", result.getLanguage()); + } + + @Test + void shouldReturnHigherPriority() { + Locale result = Locales.findSupported("zh-CN;q=0.9,zh-TW;q=1.0"); + assertEquals("zh-TW", result.toLanguageTag()); + } + + @Test + void shouldReturnHigherPriorityToo() { + Locale result = Locales.findSupported("zh-CN;q=1.0,zh-TW;q=0.9"); + assertEquals("zh-CN", result.toLanguageTag()); + } + + @Test + void shouldReturnSpanish() { + Locale result = Locales.findSupported("es;q=1.0,zh-CN;q=0.9,zh-TW;q=0.9"); + assertEquals("es", result.toLanguageTag()); + } + + @Test + void shouldReturnFound() { + Locale result = Locales.findSupported("zh-SG;q=1.0,zh-TW;q=0.9"); + assertEquals("zh-TW", result.toLanguageTag()); + } + + @Test + void shouldReturnFounds() { + Locale result = Locales.findSupported("zh;q=1.0"); + assertEquals("zh", result.getLanguage()); + } +} diff --git a/src/test/java/com/networknt/schema/i18n/ResourceBundleMessageSourceTest.java b/src/test/java/com/networknt/schema/i18n/ResourceBundleMessageSourceTest.java new file mode 100644 index 000000000..86e91e223 --- /dev/null +++ b/src/test/java/com/networknt/schema/i18n/ResourceBundleMessageSourceTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +class ResourceBundleMessageSourceTest { + + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource("jsv-messages", "test-messages"); + + @Test + void messageNoDefault() { + String message = messageSource.getMessage("unknown.key", Locale.getDefault()); + assertEquals("unknown.key", message); + } + + @Test + void messageDefaultSupplier() { + String message = messageSource.getMessage("unknown.key", "default", Locale.getDefault()); + assertEquals("default", message); + } + + @Test + void messageDefaultSupplierArguments() { + String message = messageSource.getMessage("unknown.key", "An error {0}", Locale.getDefault(), "argument"); + assertEquals("An error argument", message); + } + + @Test + void messageFound() { + String message = messageSource.getMessage("atmostOne", Locale.getDefault()); + assertEquals("english", message); + } + + @Test + void messageFallbackOnDefaultLocale() { + String message = messageSource.getMessage("atmostOne", Locale.SIMPLIFIED_CHINESE); + assertEquals("english", message); + } + + @Test + void messageFrench() { + String message = messageSource.getMessage("atmostOne", Locale.FRANCE); + assertEquals("french", message); + } + + @Test + void messageMaxItems() { + String message = messageSource.getMessage("maxItems", Locale.getDefault(), 5, 10); + assertEquals("must have at most 5 items but found 10", message); + } + + @Test + void missingBundleShouldNotThrow() { + MessageSource messageSource = new ResourceBundleMessageSource("missing-bundle"); + assertEquals("missing", messageSource.getMessage("missing", Locale.getDefault())); + } + + @Test + void overrideMessage() { + MessageSource messageSource = new ResourceBundleMessageSource("jsv-messages-override", "jsv-messages"); + assertEquals("path: overridden message value", messageSource.getMessage("allOf", Locale.ROOT, "path", "value")); + assertEquals("path: overridden message value", messageSource.getMessage("allOf", Locale.FRENCH, "path", "value")); + assertEquals("must be valid to any of the schemas value", messageSource.getMessage("anyOf", Locale.ROOT, "value")); + } +} diff --git a/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java b/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java new file mode 100644 index 000000000..6cbbfc600 --- /dev/null +++ b/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java @@ -0,0 +1,123 @@ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.BaseJsonSchemaValidatorTest; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Result; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.PropertyWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; +import com.networknt.schema.walk.WalkListener; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Created by josejulio on 25/04/22. + */ +class PropertiesValidatorTest extends BaseJsonSchemaValidatorTest { + + @Test + void testDoesNotThrowWhenApplyingDefaultPropertiesToNonObjects() throws Exception { + Assertions.assertDoesNotThrow(() -> { + WalkConfig walkConfig = WalkConfig.builder().applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + Schema schema = factory.getSchema("{\"type\":\"object\",\"properties\":{\"foo\":{\"type\":\"object\", \"properties\": {} },\"i-have-default\":{\"type\":\"string\",\"default\":\"foo\"}}}"); + JsonNode node = getJsonNodeFromStringContent("{\"foo\": \"bar\"}"); + Result result = schema.walk(node, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + Assertions.assertEquals(result.getErrors().size(), 1); + }); + } + + @Test + void evaluationPath() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012()); + String schemaData = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"productId\": {\n" + + " \"type\": \"integer\",\n" + + " \"minimum\": 1\n" + + " }\n" + + " }\n" + + "}"; + String instanceData = "{\n" + + " \"productId\": 0\n" + + "}"; + Schema schema = schemaRegistry.getSchema(schemaData, InputFormat.JSON); + List errors = schema.validate(instanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + assertEquals("/properties/productId/minimum", errors.get(0).getEvaluationPath().toString()); + assertEquals("#/properties/productId/minimum", errors.get(0).getSchemaLocation().toString()); + assertEquals("minimum", errors.get(0).getKeyword()); + } + + @Test + void evaluationPathWalk() { + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + + + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012()); + String schemaData = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"productId\": {\n" + + " \"type\": \"integer\",\n" + + " \"minimum\": 1\n" + + " }\n" + + " },\n" + + " \"additionalProperties\": {\n" + + " \"type\": \"integer\"\n" + + " }\n" + + "}"; + String instanceData = "{\n" + + " \"productId\": 0,\n" + + " \"product\": \"hello\"\n" + + "}"; + Schema schema = schemaRegistry.getSchema(schemaData, InputFormat.JSON); + Result result = schema.walk(instanceData, InputFormat.JSON, true, + executionContext -> executionContext + .walkConfig(walkConfig -> walkConfig.propertyWalkListenerRunner(propertyWalkListenerRunner) + .keywordWalkListenerRunner(keywordWalkListenerRunner))); + List errors = result.getErrors(); + assertEquals(2, errors.size()); + assertEquals("/properties/productId/minimum", errors.get(0).getEvaluationPath().toString()); + assertEquals("#/properties/productId/minimum", errors.get(0).getSchemaLocation().toString()); + assertEquals("minimum", errors.get(0).getKeyword()); + assertEquals("/additionalProperties/type", errors.get(1).getEvaluationPath().toString()); + assertEquals("#/additionalProperties/type", errors.get(1).getSchemaLocation().toString()); + assertEquals("type", errors.get(1).getKeyword()); + } +} diff --git a/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java b/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java new file mode 100644 index 000000000..f0bce7bc1 --- /dev/null +++ b/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java @@ -0,0 +1,58 @@ +package com.networknt.schema.keyword; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; + +/** + * Test for propertyDependencies. + */ +public class PropertyDependenciesValidatorTest { + @Test + void evaluationPath() { + Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(KeywordType.PROPERTY_DEPENDENCIES).build(); + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(dialect); + String schemaData = "{\r\n" + + " \"propertyDependencies\": {\r\n" + + " \"foo\": {\r\n" + + " \"aaa\": {\r\n" + + " \"$ref\": \"#/$defs/foo-aaa\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"$defs\": {\r\n" + + " \"foo-aaa\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"foo\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"bar\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String instanceData = "{\r\n" + + " \"foo\": \"aaa\",\r\n" + + " \"bar\": 1\r\n" + + "}"; + Schema schema = schemaRegistry.getSchema(schemaData, InputFormat.JSON); + List errors = schema.validate(instanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + assertEquals("/propertyDependencies/foo/aaa/$ref/properties/bar/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("#/$defs/foo-aaa/properties/bar/type", errors.get(0).getSchemaLocation().toString()); + assertEquals("type", errors.get(0).getKeyword()); + } + +} diff --git a/src/test/java/com/networknt/schema/keyword/ThresholdMixinPerfTest.java b/src/test/java/com/networknt/schema/keyword/ThresholdMixinPerfTest.java new file mode 100644 index 000000000..014c117e0 --- /dev/null +++ b/src/test/java/com/networknt/schema/keyword/ThresholdMixinPerfTest.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.keyword; + +import static java.lang.System.out; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.BigIntegerNode; +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.TextNode; + +@Disabled +class ThresholdMixinPerfTest { + private static final long thresholdIntegral = Long.MAX_VALUE - 1; + + + private final LongNode maximumLong = new LongNode(thresholdIntegral); + @SuppressWarnings("unused") + private final BigIntegerNode maximumBigInt = new BigIntegerNode(BigInteger.valueOf(thresholdIntegral)); + + private final LongNode valueLong = new LongNode(Long.MAX_VALUE); + @SuppressWarnings("unused") + private final BigIntegerNode valueBigInt = new BigIntegerNode(BigInteger.valueOf(Long.MAX_VALUE)); + + // private final double threshold = Double.MAX_VALUE - 1; + private final double threshold = 1797693.134E+5D; + private final DoubleNode maximumDouble = new DoubleNode(threshold); + private final DecimalNode maximumDecimal = new DecimalNode(BigDecimal.valueOf(threshold)); + + private final double value = threshold + 1; + private final DoubleNode valueDouble = new DoubleNode(value); + private final DecimalNode valueDecimal = new DecimalNode(new BigDecimal(value)); + private final TextNode valueTextual = new TextNode(String.valueOf(value)); + + private final String maximumText = maximumDouble.asText(); + private final BigDecimal max = new BigDecimal(maximumText); + + private final int executeTimes = 200000; + private final boolean excludeEqual = false; + + double baseTimeForDouble; + private double baseTimeForLong; + + @BeforeEach + void baseTimeEstimate() { + baseTimeForDouble = getAvgTimeViaMixin(asDouble, valueDouble, executeTimes); + out.printf("Base execution time (comparing two DoubleNodes) %f ns%n", baseTimeForDouble); + + baseTimeForLong = getAvgTimeViaMixin(asLong, valueLong, executeTimes); + out.printf("Base execution time (comparing two LongeNodes) %f ns \n%n", baseTimeForDouble); + } + + @Test + void currentTimeEstimate() { + out.println("Estimating time for current implementation:"); + double currentAvgTimeOnDouble = getAvgTimeViaMixin(currentImplementationDouble, valueDouble, executeTimes); + out.printf("Current double on double execution time %f ns, %f times slower%n", currentAvgTimeOnDouble, (currentAvgTimeOnDouble / baseTimeForDouble)); + + double currentAvgTimeOnDecimal = getAvgTimeViaMixin(currentImplementationDouble, valueDecimal, executeTimes); + out.printf("Current double on decimal execution time %f ns, %f times slower%n", currentAvgTimeOnDecimal, (currentAvgTimeOnDecimal / baseTimeForDouble)); + + double currentAvgTimeOnText = getAvgTimeViaMixin(currentImplementationDouble, valueTextual, executeTimes); + out.printf("Current double on text execution time %f ns, %f times slower%n", currentAvgTimeOnText, (currentAvgTimeOnText / baseTimeForDouble)); + + double currentAvgTimeDecimalOnDouble = getAvgTimeViaMixin(currentImplementationDecimal, valueDouble, executeTimes); + out.printf("Current decimal on double execution time %f ns, %f times slower%n", currentAvgTimeDecimalOnDouble, (currentAvgTimeDecimalOnDouble / baseTimeForDouble)); + + double currentAvgTimeDecimalOnDecimal = getAvgTimeViaMixin(currentImplementationDecimal, valueDecimal, executeTimes); + out.printf("Current decimal on decimal execution time %f ns, %f times slower%n", currentAvgTimeDecimalOnDecimal, (currentAvgTimeDecimalOnDecimal / baseTimeForDouble)); + + double currentAvgTimeDecimalOnText = getAvgTimeViaMixin(currentImplementationDecimal, valueTextual, executeTimes); + out.printf("Current decimal on text execution time %f ns, %f times slower%n", currentAvgTimeDecimalOnText, (currentAvgTimeDecimalOnText / baseTimeForDouble)); + + out.printf("Cumulative average: %f\n\n%n", (currentAvgTimeOnDouble + currentAvgTimeOnDecimal + currentAvgTimeOnText + currentAvgTimeDecimalOnDouble + currentAvgTimeDecimalOnDecimal + currentAvgTimeDecimalOnText) / 6.0d); + } + + @Test + void allInOneAproachTimeEstimate() { + out.println("Estimating time threshold value agnostic mixin (aka allInOne):"); + double allInOneDoubleOnDouble = getAvgTimeViaMixin(allInOneDouble, valueDouble, executeTimes); + out.printf("AllInOne double on double execution time %f ns, %f times slower%n", allInOneDoubleOnDouble, (allInOneDoubleOnDouble / baseTimeForDouble)); + + double allInOneDoubleOnDecimal = getAvgTimeViaMixin(allInOneDouble, valueDecimal, executeTimes); + out.printf("AllInOne double on decimal execution time %f ns, %f times slower%n", allInOneDoubleOnDecimal, (allInOneDoubleOnDecimal / baseTimeForDouble)); + + double allInOneDoubleOnText = getAvgTimeViaMixin(allInOneDouble, valueTextual, executeTimes); + out.printf("AllInOne double on text execution time %f ns, %f times slower%n", allInOneDoubleOnText, (allInOneDoubleOnText / baseTimeForDouble)); + + double allInOneDecimalOnDouble = getAvgTimeViaMixin(allInOneDecimal, valueDouble, executeTimes); + out.printf("AllInOne decimal on double execution time %f ns, %f times slower%n", allInOneDecimalOnDouble, (allInOneDecimalOnDouble / baseTimeForDouble)); + + double allInOneDecimalOnDecimal = getAvgTimeViaMixin(allInOneDecimal, valueDecimal, executeTimes); + out.printf("AllInOne decimal on decimal execution time %f ns, %f times slower%n", allInOneDecimalOnDecimal, (allInOneDecimalOnDecimal / baseTimeForDouble)); + + double allInOneDecimalOnText = getAvgTimeViaMixin(allInOneDecimal, valueTextual, executeTimes); + out.printf("AllInOne decimal on text execution time %f ns, %f times slower%n", allInOneDecimalOnText, (allInOneDecimalOnText / baseTimeForDouble)); + + out.printf("Cumulative average: %f\n\n%n", (allInOneDoubleOnDouble + allInOneDoubleOnDecimal + allInOneDoubleOnText + allInOneDecimalOnDouble + allInOneDecimalOnDecimal + allInOneDecimalOnText) / 6.0d); + } + + @Test + void specificCaseForEachThresholdValue() { + out.println("Estimating time for specific cases:"); + double doubleValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDouble, executeTimes); + out.printf("Typed threshold execution time %f ns, %f times slower%n", doubleValueAvgTime, (doubleValueAvgTime / baseTimeForDouble)); + + double decimalValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueDecimal, executeTimes); + out.printf("Typed threshold execution time %f ns, %f times slower%n", decimalValueAvgTime, (decimalValueAvgTime / baseTimeForDouble)); + + double textValueAvgTime = getAvgTimeViaMixin(typedThreshold, valueTextual, executeTimes); + out.printf("Typed threshold execution time %f ns, %f times slower%n", textValueAvgTime, (textValueAvgTime / baseTimeForDouble)); + + out.printf("Cumulative average: %f\n\n%n", (doubleValueAvgTime + decimalValueAvgTime + textValueAvgTime) / 3.0d); + } + + @Test + void noMixinsFloatingTimeEstimate() { + out.println("Estimating time no mixins at all (floating point values):"); + double doubleValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); + out.printf("No mixins with double value time %f ns, %f times slower%n", doubleValueAvgTime, (doubleValueAvgTime / baseTimeForDouble)); + + double decimalValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueDecimal, executeTimes); + out.printf("No mixins with decimal value time %f ns, %f times slower%n", decimalValueAvgTime, (decimalValueAvgTime / baseTimeForDouble)); + + double textValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, valueTextual, executeTimes); + out.printf("No mixins with text value time %f ns, %f times slower%n", textValueAvgTime, (textValueAvgTime / baseTimeForDouble)); + out.printf("Cumulative average: %f\n\n%n", + (doubleValueAvgTime + decimalValueAvgTime + textValueAvgTime) / 3.0d); + } + + @Test + void noMixinsIntegralTimeEstimate() { + double longValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new LongNode((long) value), executeTimes); + out.printf("No mixins with long value time %f ns, %f times slower%n", longValueAvgTime, (longValueAvgTime / baseTimeForLong)); + + double bigIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new BigIntegerNode(BigInteger.valueOf((long) value)), executeTimes); + out.printf("No mixins with big int value time %f ns, %f times slower%n", bigIntValueAvgTime, (bigIntValueAvgTime / baseTimeForLong)); + + double textIntValueAvgTime = getAvgTimeViaMixin(oneMixinForIntegerAndNumber, new TextNode(String.valueOf((long) value)), executeTimes); + out.printf("No mixins with text value time %f ns, %f times slower%n", textIntValueAvgTime, (textIntValueAvgTime / baseTimeForLong)); + out.printf("Cumulative average: %f\n\n%n", + (longValueAvgTime + bigIntValueAvgTime + textIntValueAvgTime) / 3.0d); + } + + ThresholdMixin allInOneDouble = new AllInOneThreshold(maximumDouble, false); + ThresholdMixin allInOneDecimal = new AllInOneThreshold(maximumDecimal, false); + + public static class AllInOneThreshold implements ThresholdMixin { + + private final BigDecimal bigDecimalMax; + JsonNode maximum; + private final boolean excludeEqual; + + AllInOneThreshold(JsonNode maximum, boolean exludeEqual) { + this.maximum = maximum; + this.excludeEqual = exludeEqual; + this.bigDecimalMax = new BigDecimal(maximum.asText()); + } + + @Override + public boolean crossesThreshold(JsonNode node) { + if (maximum.isDouble() && maximum.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + if (maximum.isDouble() && maximum.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (maximum.isDouble() && node.isDouble()) { + double lm = maximum.doubleValue(); + double val = node.doubleValue(); + return lm < val || (excludeEqual && lm == val); + } + + if (maximum.isFloatingPointNumber() && node.isFloatingPointNumber()) { + BigDecimal value = node.decimalValue(); + int compare = value.compareTo(bigDecimalMax); + return compare > 0 || (excludeEqual && compare == 0); + } + + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(bigDecimalMax); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return maximum.asText(); + } + } + + ThresholdMixin asDouble = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + double lm = maximumDouble.doubleValue(); + double val = node.doubleValue(); + return lm < val || (excludeEqual && lm == val); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + + ThresholdMixin asLong = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + long lm = maximumLong.longValue(); + long val = node.longValue(); + return lm < val || (excludeEqual && lm == val); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + + ThresholdMixin typedThreshold = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + if (node.isDouble()) { + double lm = maximumDouble.doubleValue(); + double val = node.doubleValue(); + return lm < val || (excludeEqual && lm == val); + } + + if (node.isBigDecimal()) { + BigDecimal value = node.decimalValue(); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + + ThresholdMixin currentImplementationDouble = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + if (maximumDouble.isDouble() && maximumDouble.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + final BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + + + ThresholdMixin currentImplementationDecimal = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.POSITIVE_INFINITY) { + return false; + } + if (maximumDecimal.isDouble() && maximumDecimal.doubleValue() == Double.NEGATIVE_INFINITY) { + return true; + } + if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) { + return false; + } + if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) { + return true; + } + final BigDecimal max = new BigDecimal(maximumText); + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return maximumText; + } + }; + + ThresholdMixin oneMixinForIntegerAndNumber = new ThresholdMixin() { + @SuppressWarnings("unused") + @Override + public boolean crossesThreshold(JsonNode node) { + BigDecimal value = new BigDecimal(node.asText()); + int compare = value.compareTo(max); + return compare > 0 || (excludeEqual && compare == 0); + } + + @Override + public String thresholdValue() { + return null; + } + }; + + private double getAvgTimeViaMixin(ThresholdMixin mixin, JsonNode value, int iterations) { +// boolean excludeEqual = false; + long totalTime = 0; + for (int i = 0; i < iterations; i++) { + long start = System.nanoTime(); + try { + mixin.crossesThreshold(value); + } finally { + totalTime += System.nanoTime() - start; + } + } + return totalTime / (iterations * 1.0D); + } +} diff --git a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java new file mode 100644 index 000000000..59ee56140 --- /dev/null +++ b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.oas; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.OutputFormat; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistryConfig; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.path.PathType; +import com.networknt.schema.Error; + +/** + * OpenApi30Test. + */ +class OpenApi30Test { + /** + * Test with the explicitly configured OpenApi30 instance. + */ + @Test + void validateMetaSchema() { + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi30()); + Schema schema = factory.getSchema(SchemaLocation.of( + "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/requestBody/content/application~1json/schema")); + String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"bark\": \"woof\"\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + assertEquals(0, messages.size()); + + String invalid = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"meow\": \"meeeooow\"\r\n" + + "}"; + messages = schema.validate(invalid, InputFormat.JSON); + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("bark", list.get(1).getProperty()); + } + + /** + * Tests that schema location with number in fragment can resolve. + */ + @Test + void jsonPointerWithNumberInFragment() { + SchemaRegistryConfig config = SchemaRegistryConfig.builder().pathType(PathType.JSON_PATH).build(); + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi30(), builder -> builder.schemaRegistryConfig(config)); + Schema schema = factory.getSchema(SchemaLocation.of( + "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/responses/200/content/application~1json/schema") + ); + assertNotNull(schema); + //assertEquals("$.paths['/pet'].post.responses['200'].content['application/json'].schema", + // schema.getEvaluationPath().toString()); + } + + /** + * Exclusive maximum true. + */ + @Test + void exclusiveMaximum() { + String schemaData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"minimum\": 0,\r\n" + + " \"maximum\": 100,\r\n" + + " \"exclusiveMaximum\": true\r\n" + + "}\r\n"; + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi30()); + Schema schema = factory.getSchema(schemaData); + assertFalse(schema.validate("100", InputFormat.JSON, OutputFormat.BOOLEAN)); + } + + /** + * Exclusive minimum true. + */ + @Test + void exclusiveMinimum() { + String schemaData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"minimum\": 0,\r\n" + + " \"maximum\": 100,\r\n" + + " \"exclusiveMinimum\": true\r\n" + + "}\r\n"; + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi30()); + Schema schema = factory.getSchema(schemaData); + assertFalse(schema.validate("0", InputFormat.JSON, OutputFormat.BOOLEAN)); + } +} diff --git a/src/test/java/com/networknt/schema/oas/OpenApi31Test.java b/src/test/java/com/networknt/schema/oas/OpenApi31Test.java new file mode 100644 index 000000000..84ab471b2 --- /dev/null +++ b/src/test/java/com/networknt/schema/oas/OpenApi31Test.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.oas; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.Error; + +/** + * OpenApi31Test. + */ +class OpenApi31Test { + /** + * Test using vocabulary. + */ + @Test + void validateVocabulary() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaIdResolvers(schemaIdResolvers -> schemaIdResolvers + .mapPrefix("https://spec.openapis.org/oas/3.1", "classpath:oas/3.1"))); + Schema schema = factory + .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); + String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"bark\": \"woof\"\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + assertEquals(0, messages.size()); + + String invalid = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"meow\": \"meeeooow\"\r\n" + + "}"; + messages = schema.validate(invalid, InputFormat.JSON); + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("bark", list.get(1).getProperty()); + } + + /** + * Test with the explicitly configured OpenApi31 instance. + */ + @Test + void validateMetaSchema() { + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory + .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); + String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"bark\": \"woof\"\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + assertEquals(0, messages.size()); + + String invalid = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"meow\": \"meeeooow\"\r\n" + + "}"; + messages = schema.validate(invalid, InputFormat.JSON); + assertEquals(2, messages.size()); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("bark", list.get(1).getProperty()); + } + + /** + * Test oneOf with multiple matches should fail. Note that the discriminator + * does not affect the validation outcome. + */ + @Test + void discriminatorOneOfMultipleMatchShouldFail() { + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory + .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); + String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"bark\": \"woof\",\r\n" + + " \"lovesRocks\": true\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + } + + /** + * Test oneOf with no matches should fail with only the errors from the discriminator. + */ + @Test + void discriminatorOneOfNoMatchShouldFail() { + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory + .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); + String input = "{\r\n" + + " \"petType\": \"lizard\",\r\n" + + " \"none\": true\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + List list = messages.stream().collect(Collectors.toList()); + assertEquals("oneOf", list.get(0).getKeyword()); + assertEquals("required", list.get(1).getKeyword()); + assertEquals("lovesRocks", list.get(1).getProperty()); + } + + /** + * Test oneOf with one match but incorrect discriminator should succeed. Note + * that the discriminator does not affect the validation outcome. + */ + @Test + void discriminatorOneOfOneMatchWrongDiscriminatorShouldSucceed() { + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory + .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); + String input = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"lovesRocks\": true\r\n" + + "}"; + List messages = schema.validate(input, InputFormat.JSON); + assertEquals(0, messages.size()); + } + +} diff --git a/src/test/java/com/networknt/schema/path/EvaluationPathTest.java b/src/test/java/com/networknt/schema/path/EvaluationPathTest.java new file mode 100644 index 000000000..ba8e862ce --- /dev/null +++ b/src/test/java/com/networknt/schema/path/EvaluationPathTest.java @@ -0,0 +1,88 @@ +package com.networknt.schema.path; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.Dialects; + +/** + * Tests for evaluation path. + */ +public class EvaluationPathTest { + @Test + void baseUriChange() { + String schemaData = "[\r\n" + + " {\r\n" + + " \"schema\": {\r\n" + + " \"id\": \"http://localhost:1234/\",\r\n" + + " \"items\": {\r\n" + + " \"id\": \"baseUriChange/\",\r\n" + + " \"items\": {\"$ref\": \"folderInteger.json\"}\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "]"; + + String folderIntegerSchemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + Map schemas = new HashMap<>(); + schemas.put("http://www.example.org/refRemote.json", schemaData); + schemas.put("http://localhost:1234/baseUriChange/folderInteger.json", folderIntegerSchemaData); + + String instanceData = "[[1,2,3,4,\"5\"]]"; + + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.schemas(schemas)); + Schema schemaWithIdFromUri = schemaRegistry.getSchema(SchemaLocation.of("http://www.example.org/refRemote.json#/0/schema")); + assertEquals("http://localhost:1234/#", schemaWithIdFromUri.getSchemaLocation().toString()); + List errors = schemaWithIdFromUri.validate(instanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + // Previously the evaluation path was part of the state of the schema so the initial path might not be correct as it matches the schema location fragment + // assertEquals("/0/schema/items/items/$ref/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("/items/items/$ref/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("http://localhost:1234/baseUriChange/folderInteger.json#/type", errors.get(0).getSchemaLocation().toString()); + assertEquals("/0/4", errors.get(0).getInstanceLocation().toString()); + assertEquals("type", errors.get(0).getKeyword()); + } + + @Test + void openapi() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getOpenApi30()); + Schema schema = schemaRegistry.getSchema(SchemaLocation.of( + "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/requestBody/content/application~1json/schema")); + String invalid = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"meow\": \"meeeooow\"\r\n" + + "}"; + + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/requestBody/content/application~1json/schema", schema.getSchemaLocation().toString()); + List errors = schema.validate(invalid, InputFormat.JSON); + assertEquals(2, errors.size()); + assertEquals("oneOf", errors.get(0).getKeyword()); + // Previously the evaluation path was part of the state of the schema so the initial path might not be correct as it matches the schema location fragment + //assertEquals("/paths/~1pet/post/requestBody/content/application~1json/schema/$ref/oneOf", errors.get(0).getEvaluationPath().toString()); + assertEquals("/$ref/oneOf", errors.get(0).getEvaluationPath().toString()); + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/components/schemas/PetRequest/oneOf", errors.get(0).getSchemaLocation().toString()); + assertEquals("", errors.get(0).getInstanceLocation().toString()); + assertEquals("required", errors.get(1).getKeyword()); + assertEquals("bark", errors.get(1).getProperty()); + //assertEquals("/paths/~1pet/post/requestBody/content/application~1json/schema/$ref/oneOf/1/$ref/allOf/1/required", errors.get(1).getEvaluationPath().toString()); + assertEquals("/$ref/oneOf/1/$ref/allOf/1/required", errors.get(1).getEvaluationPath().toString()); + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/components/schemas/Dog/allOf/1/required", errors.get(1).getSchemaLocation().toString()); + assertEquals("", errors.get(1).getInstanceLocation().toString()); + } + +} diff --git a/src/test/java/com/networknt/schema/path/PathTypeTest.java b/src/test/java/com/networknt/schema/path/PathTypeTest.java new file mode 100644 index 000000000..278bf4008 --- /dev/null +++ b/src/test/java/com/networknt/schema/path/PathTypeTest.java @@ -0,0 +1,49 @@ +package com.networknt.schema.path; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class PathTypeTest { + + @Test + void rejectNull() { + Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> { + PathType.fromJsonPath(null); + }); + } + + @Test + void rejectEmptyString() { + Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> { + PathType.fromJsonPath(""); + }); + } + + @Test + void acceptRoot() { + assertEquals("", PathType.fromJsonPath("$")); + } + + @Test + void acceptSimpleIndex() { + assertEquals("/0", PathType.fromJsonPath("$[0]")); + } + + @Test + void acceptSimpleProperty() { + assertEquals("/a", PathType.fromJsonPath("$.a")); + } + + @Test + void acceptEscapedProperty() { + assertEquals("/a", PathType.fromJsonPath("$['a']")); + } + + @Test + void hasSpecialCharacters() { + assertEquals("/a.b/c-d", PathType.fromJsonPath("$['a.b']['c-d']")); + } + +} diff --git a/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java new file mode 100644 index 000000000..44e6116a9 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InvalidSchemaException; + +/** + * Test for AllowRegularExpressionFactory. + */ +class AllowRegularExpressionFactoryTest { + @Test + void getRegularExpression() { + boolean[] called = { false }; + RegularExpressionFactory delegate = (regex) -> { + called[0] = true; + return null; + }; + String allowed = "testing"; + RegularExpressionFactory factory = new AllowRegularExpressionFactory(delegate, allowed::equals); + InvalidSchemaException exception = assertThrows(InvalidSchemaException.class, () -> factory.getRegularExpression("hello")); + assertEquals("hello", exception.getError().getArguments()[0]); + + assertDoesNotThrow(() -> factory.getRegularExpression(allowed)); + assertTrue(called[0]); + } + +} diff --git a/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java new file mode 100644 index 000000000..15eaad34d --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/ECMAScriptRegularExpressionFactoryTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +/** + * Test for ECMAScriptRegularExpressionFactory. + */ +class ECMAScriptRegularExpressionFactoryTest { + + @Test + void shouldPreferGraalJS() { + RegularExpression regularExpression = ECMAScriptRegularExpressionFactory.getInstance() + .getRegularExpression("[a-z]"); + assertInstanceOf(GraalJSRegularExpression.class, regularExpression); + } + +} diff --git a/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java new file mode 100644 index 000000000..a1c4de0c8 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionFactoryTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.graalvm.polyglot.Context; +import org.junit.jupiter.api.Test; + +/** + * GraalJSRegularExpressionFactoryTest. + */ +class GraalJSRegularExpressionFactoryTest { + + @Test + void constructorContext() { + try (Context context = Context.newBuilder("js").option("engine.WarnInterpreterOnly", "false") + .build()) { + GraalJSRegularExpressionFactory factory = new GraalJSRegularExpressionFactory(context); + RegularExpression regex = factory.getRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?"); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + } + +} diff --git a/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java new file mode 100644 index 000000000..4f18b14dd --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/GraalJSRegularExpressionTest.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * Test for GraalJSRegularExpression. + */ +class GraalJSRegularExpressionTest { + private static final GraalJSRegularExpressionContext CONTEXT = new GraalJSRegularExpressionContext( + GraalJSContextFactory.getInstance()); + + enum InvalidEscapeInput { + A("\\a"), + HELLOA("hello\\a"), + C("\\c"), + E("\\e"), + G("\\g"), + H("\\h"), + I("\\i"), + J("\\j"), + K("\\k"), + L("\\l"), + M("\\m"), + O("\\o"), + Q("\\q"), + U("\\u"), + X("\\x"), + X1("\\x1"), + XGG("\\xgg"), + X1G("\\x1g"), + Y("\\y"), + Z("\\z"), + _1("\\1"), + _2("\\2"), + _3("\\3"), + _4("\\4"), + _5("\\5"), + _6("\\6"), + _7("\\7"), + _8("\\8"), + _9("\\9"); + + String value; + + InvalidEscapeInput(String value) { + this.value = value; + } + } + + @ParameterizedTest + @EnumSource(InvalidEscapeInput.class) + void invalidEscape(InvalidEscapeInput input) { + RuntimeException e = assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression(input.value, CONTEXT)); + assertTrue(e.getMessage().startsWith("SyntaxError")); + } + + enum ValidEscapeInput { + B("\\b"), + D("\\d"), + CAP_D("\\D"), + W("\\w"), + CAP_W("\\W"), + S("\\s"), + CAP_S("\\S"), + T("\\t"), + U1234("\\u1234"), + R("\\r"), + N("\\n"), + V("\\v"), + F("\\f"), + X12("\\x12"), + X1F("\\x1f"), + X1234("\\x1234"), + P("\\p{Letter}cole"), // unicode property + CAP_P("\\P{Letter}cole"), // unicode property + _0("\\0"), + CA("\\cA"), // control + CB("\\cB"), // control + CC("\\cC"), // control + CG("\\cG"); // control + + String value; + + ValidEscapeInput(String value) { + this.value = value; + } + } + + @ParameterizedTest + @EnumSource(ValidEscapeInput.class) + void validEscape(ValidEscapeInput input) { + assertDoesNotThrow(() -> new GraalJSRegularExpression(input.value, CONTEXT)); + } + + @Test + void invalidPropertyName() { + assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\p", CONTEXT)); + assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\P", CONTEXT)); + assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\pa", CONTEXT)); + assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\Pa", CONTEXT)); + } + + @Test + void digit() { + RegularExpression regex = new GraalJSRegularExpression("\\d", CONTEXT); + assertTrue(regex.matches("1")); + assertFalse(regex.matches("a")); + } + + @Test + void invalidEscape() { + RuntimeException e = assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("\\a", CONTEXT)); + assertEquals("SyntaxError: Invalid escape", e.getMessage()); + } + + @Test + void namedCapturingGroup() { + RegularExpression regex = new GraalJSRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?", CONTEXT); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + + @Test + void invalidNamedCapturingGroup() { + assertThrows(RuntimeException.class, () -> new GraalJSRegularExpression("(?)(?)", CONTEXT)); + } + + @Test + void namedBackreference() { + RegularExpression regex = new GraalJSRegularExpression("title=(?[\"'])(.*?)\\k", CONTEXT); + assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\"")); + } + + @Test + void anchorShouldNotMatchMultilineInput() { + RegularExpression regex = new GraalJSRegularExpression("^[a-z]{1,10}$", CONTEXT); + assertFalse(regex.matches("abc\n")); + } + + /** + * This test is because the JDK regex matches function implicitly adds anchors + * which isn't expected. + */ + @Test + void noImplicitAnchors() { + RegularExpression regex = new GraalJSRegularExpression("[a-z]{1,10}", CONTEXT); + assertTrue(regex.matches("1abc1")); + } + + + @Test + void concurrency() throws Exception { + RegularExpression regex = new GraalJSRegularExpression("\\d", CONTEXT); + Exception[] instance = new Exception[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + assertTrue(regex.matches("1")); + } catch (RuntimeException e) { + instance[0] = e; + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + if (instance[0] != null) { + throw instance[0]; + } + } +} diff --git a/src/test/java/com/networknt/schema/regex/Issue814Test.java b/src/test/java/com/networknt/schema/regex/Issue814Test.java new file mode 100644 index 000000000..15ab31ce9 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/Issue814Test.java @@ -0,0 +1,69 @@ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class Issue814Test { + + @Test + void jdkTypePattern() { + JDKRegularExpression ex = new JDKRegularExpression("^(list|date|time|string|enum|int|double|long|boolean|number)$"); + assertTrue(ex.matches("list")); + assertTrue(ex.matches("string")); + assertTrue(ex.matches("boolean")); + assertTrue(ex.matches("number")); + assertTrue(ex.matches("enum")); + assertFalse(ex.matches("listZ")); + assertFalse(ex.matches("AenumZ")); + assertFalse(ex.matches("Anumber")); + } + + @Test + void jdkOptionsPattern() { + JDKRegularExpression ex = new JDKRegularExpression("^\\d|[a-zA-Z_]$"); + assertTrue(ex.matches("5")); + assertTrue(ex.matches("55")); + assertTrue(ex.matches("5%")); + assertTrue(ex.matches("a")); + assertTrue(ex.matches("aa")); + assertTrue(ex.matches("%a")); + assertTrue(ex.matches("%_")); + assertTrue(ex.matches("55aa")); + assertTrue(ex.matches("5%%a")); + assertFalse(ex.matches("")); + assertFalse(ex.matches("%")); + assertFalse(ex.matches("a5")); + } + + @Test + void joniTypePattern() { + JoniRegularExpression ex = new JoniRegularExpression("^(list|date|time|string|enum|int|double|long|boolean|number)$"); + assertTrue(ex.matches("list")); + assertTrue(ex.matches("string")); + assertTrue(ex.matches("boolean")); + assertTrue(ex.matches("number")); + assertTrue(ex.matches("enum")); + assertFalse(ex.matches("listZ")); + assertFalse(ex.matches("AenumZ")); + assertFalse(ex.matches("Anumber")); + } + + @Test + void joniOptionsPattern() { + JoniRegularExpression ex = new JoniRegularExpression("^\\d|[a-zA-Z_]$"); + assertTrue(ex.matches("5")); + assertTrue(ex.matches("55")); + assertTrue(ex.matches("5%")); + assertTrue(ex.matches("a")); + assertTrue(ex.matches("aa")); + assertTrue(ex.matches("%a")); + assertTrue(ex.matches("%_")); + assertTrue(ex.matches("55aa")); + assertTrue(ex.matches("5%%a")); + assertFalse(ex.matches("")); + assertFalse(ex.matches("%")); + assertFalse(ex.matches("a5")); + } + +} diff --git a/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java new file mode 100644 index 000000000..76fcc5f47 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionFactoryTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * JDKRegularExpressionFactoryTest. + */ +class JDKRegularExpressionFactoryTest { + + @Test + void getRegularExpression() { + JDKRegularExpressionFactory factory = new JDKRegularExpressionFactory(); + RegularExpression regex = factory + .getRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?"); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + + @Test + void getInstance() { + assertSame(JDKRegularExpressionFactory.getInstance(), JDKRegularExpressionFactory.getInstance()); + } +} diff --git a/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java new file mode 100644 index 000000000..92b4dcc41 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/JDKRegularExpressionTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Tests for JDKRegularExpression. + */ +class JDKRegularExpressionTest { + @Test + void namedCapturingGroup() { + RegularExpression regex = new JDKRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?"); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + + @Test + void invalidNamedCapturingGroup() { + assertThrows(RuntimeException.class, () -> new JDKRegularExpression("(?)(?)")); + } + + @Test + void namedBackreference() { + RegularExpression regex = new JDKRegularExpression("title=(?[\"'])(.*?)\\k"); + assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\"")); + } + + @Test + @Disabled + void anchorShouldNotMatchMultilineInput() { + RegularExpression regex = new JDKRegularExpression("^[a-z]{1,10}$"); + assertFalse(regex.matches("abc\n")); + } + + /** + * This test is because the JDK regex matches function implicitly adds anchors + * which isn't expected. + */ + @Test + void noImplicitAnchors() { + RegularExpression regex = new JDKRegularExpression("[a-z]{1,10}"); + assertTrue(regex.matches("1abc1")); + } +} diff --git a/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java new file mode 100644 index 000000000..fb9c19bc6 --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionFactoryTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * JoniRegularExpressionFactoryTest. + */ +class JoniRegularExpressionFactoryTest { + + @Test + void getRegularExpression() { + JoniRegularExpressionFactory factory = new JoniRegularExpressionFactory(); + RegularExpression regex = factory + .getRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?"); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + + @Test + void getInstance() { + assertSame(JoniRegularExpressionFactory.getInstance(), JoniRegularExpressionFactory.getInstance()); + } +} diff --git a/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java new file mode 100644 index 000000000..11e66c5af --- /dev/null +++ b/src/test/java/com/networknt/schema/regex/JoniRegularExpressionTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.regex; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.joni.exception.SyntaxException; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * Tests for JoniRegularExpression. + */ +class JoniRegularExpressionTest { + + enum InvalidEscapeInput { + A("\\a"), + HELLOA("hello\\a"), + C("\\c"), + E("\\e"), + G("\\g"), + H("\\h"), + I("\\i"), + J("\\j"), + K("\\k"), + L("\\l"), + M("\\m"), + O("\\o"), + Q("\\q"), + U("\\u"), + X("\\x"), + X1("\\x1"), + XGG("\\xgg"), + X1G("\\x1g"), + Y("\\y"), + Z("\\z"), + _1("\\1"), + _2("\\2"), + _3("\\3"), + _4("\\4"), + _5("\\5"), + _6("\\6"), + _7("\\7"), + _8("\\8"), + _9("\\9"); + + String value; + + InvalidEscapeInput(String value) { + this.value = value; + } + } + + @ParameterizedTest + @EnumSource(InvalidEscapeInput.class) + void invalidEscape(InvalidEscapeInput input) { + SyntaxException e = assertThrows(SyntaxException.class, () -> new JoniRegularExpression(input.value)); + assertEquals("Invalid escape", e.getMessage()); + } + + enum ValidEscapeInput { + B("\\b"), + D("\\d"), + CAP_D("\\D"), + W("\\w"), + CAP_W("\\W"), + S("\\s"), + CAP_S("\\S"), + T("\\t"), + U1234("\\u1234"), + R("\\r"), + N("\\n"), + V("\\v"), + F("\\f"), + X12("\\x12"), + X1F("\\x1f"), + X1234("\\x1234"), + P("\\p{Letter}cole"), // unicode property + CAP_P("\\P{Letter}cole"), // unicode property + _0("\\0"), + CA("\\cA"), // control + CB("\\cB"), // control + CC("\\cC"), // control + CG("\\cG"); // control + + String value; + + ValidEscapeInput(String value) { + this.value = value; + } + } + + @ParameterizedTest + @EnumSource(ValidEscapeInput.class) + void validEscape(ValidEscapeInput input) { + assertDoesNotThrow(() -> new JoniRegularExpression(input.value)); + } + + @Test + void invalidPropertyName() { + assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\p")); + assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\P")); + assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\pa")); + assertThrows(SyntaxException.class, () -> new JoniRegularExpression("\\Pa")); + } + + /** + * Named capturing group: (?...). + * + * @see org.joni.constants.SyntaxProperties#OP2_QMARK_LT_NAMED_GROUP + * @see Named + * capturing group + */ + @Test + void namedCapturingGroup() { + RegularExpression regex = new JoniRegularExpression("((?[^,. ]+)\\s*\\.\\s*(?[^,. ]+))(?:\\s*,\\s*)?"); + assertTrue(regex.matches("FFFF.12645,AAAA.6456")); + } + + @Test + void invalidNamedCapturingGroup() { + assertThrows(RuntimeException.class, () -> new JoniRegularExpression("(?)(?)")); + } + + /** + * Named capturing group: (?...). + * + * @see org.joni.constants.SyntaxProperties#OP2_ESC_K_NAMED_BACKREF + * @see Named + * backreference + */ + @Test + void namedBackreference() { + RegularExpression regex = new JoniRegularExpression("title=(?[\"'])(.*?)\\k"); + assertTrue(regex.matches("title=\"Named capturing groups\\' advantages\"")); + } + + @Test + @Disabled // This test should pass but currently doesn't see issue #495 + void anchorShouldNotMatchMultilineInput() { + RegularExpression regex = new JoniRegularExpression("^[a-z]{1,10}$"); + assertFalse(regex.matches("abc\n")); + } + + /** + * This test is because the JDK regex matches function implicitly adds anchors + * which isn't expected. + */ + @Test + void noImplicitAnchors() { + RegularExpression regex = new JoniRegularExpression("[a-z]{1,10}"); + assertTrue(regex.matches("1abc1")); + } + + @Test + void digitCharacterClassShouldNotMatchUnicodeDigit() { + RegularExpression regex = new JoniRegularExpression("\\d"); + assertFalse(regex.matches("߀")); + } + + @Test + void wordCharacterClassShouldNotMatchUnicodeDigit() { + RegularExpression regex = new JoniRegularExpression("\\w"); + assertFalse(regex.matches("߀")); + } + + @Test + void unicodeNumberCharacterClassShouldMatchUnicodeDigit() { + RegularExpression regex = new JoniRegularExpression("\\p{N}"); + assertTrue(regex.matches("߀")); + } + + @Test + void unicodeNumberDigitCharacterClassShouldMatchUnicodeDigit() { + RegularExpression regex = new JoniRegularExpression("\\p{digit}"); + assertTrue(regex.matches("߀")); + } + + @Test + void unicodeNdCharacterClassShouldMatchUnicodeDigit() { + RegularExpression regex = new JoniRegularExpression("\\p{Nd}"); + assertTrue(regex.matches("߀")); + } + + @Test + void digitCharacterClassShouldMatchAsciiDigit() { + RegularExpression regex = new JoniRegularExpression("\\d"); + assertTrue(regex.matches("0")); + } + + @Test + void digitCharacterClassShouldMatchAsciiDigitInCharacterSet() { + RegularExpression regex = new JoniRegularExpression("[\\d]"); + assertTrue(regex.matches("0")); + } + + @Test + void whitespaceClassShouldMatchWhitespace() { + RegularExpression regex = new JoniRegularExpression("\\s"); + assertTrue(regex.matches(" ")); + } + + @Test + void whitespaceClassShouldMatchLatin1NonBreakingSpace() { + RegularExpression regex = new JoniRegularExpression("\\s"); + assertTrue(regex.matches("\u00a0")); + } + + @Test + void whitespaceClassShouldMatchWhitespaceInCharacterSet() { + RegularExpression regex = new JoniRegularExpression("[\\s]"); + assertTrue(regex.matches(" ")); + } + + @Test + void whitespaceClassShouldMatchLatin1NonBreakingSpaceInCharacterSet() { + RegularExpression regex = new JoniRegularExpression("[\\s]"); + assertTrue(regex.matches("\u00a0")); + } + + @Test + void nonWhitespaceClassShouldNotMatchWhitespaceInCharacterSet() { + RegularExpression regex = new JoniRegularExpression("[\\S]"); + assertFalse(regex.matches(" ")); + } + + @Test + void nonWhitespaceClassShouldNotMatchLatin1NonBreakingSpaceInCharacterSet() { + RegularExpression regex = new JoniRegularExpression("[\\S]"); + assertFalse(regex.matches("\u00a0")); + } +} diff --git a/src/test/java/com/networknt/schema/resource/IriResourceLoaderTest.java b/src/test/java/com/networknt/schema/resource/IriResourceLoaderTest.java new file mode 100644 index 000000000..04eab650e --- /dev/null +++ b/src/test/java/com/networknt/schema/resource/IriResourceLoaderTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.networknt.schema.AbsoluteIri; + +/** + * Tests for URI schema Loader. + */ +class IriResourceLoaderTest { + /** + * This test should only be run manually so as not to always hit the remote + * server. + * + * @throws IOException the exception + */ + @Test + @Disabled("manual") + void shouldLoadAbsoluteIri() throws IOException { + IriResourceLoader schemaLoader = new IriResourceLoader(); + InputStreamSource inputStreamSource = schemaLoader.getResource(AbsoluteIri.of("https://私の団体も.jp/")); + try (InputStream inputStream = inputStreamSource.getInputStream()) { + String result = new BufferedReader(new InputStreamReader(inputStream)).lines() + .collect(Collectors.joining("\n")); + assertNotNull(result); + } + } + + @Test + void shouldNotThrowAbsoluteIri() throws IOException { + IriResourceLoader schemaLoader = new IriResourceLoader(); + assertDoesNotThrow(() -> schemaLoader.getResource(AbsoluteIri.of("https://私の団体も.jp/"))); + } + + @Test + void shouldThrowRelativeIri() throws IOException { + IriResourceLoader schemaLoader = new IriResourceLoader(); + assertThrows(IllegalArgumentException.class, () -> schemaLoader.getResource(AbsoluteIri.of("私の団体も.jp/"))); + } +} diff --git a/src/test/java/com/networknt/schema/resource/MapResourceLoaderTest.java b/src/test/java/com/networknt/schema/resource/MapResourceLoaderTest.java new file mode 100644 index 000000000..ba02ed9fb --- /dev/null +++ b/src/test/java/com/networknt/schema/resource/MapResourceLoaderTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.AbsoluteIri; + +class MapResourceLoaderTest { + static class Result { + private final String schema; + + Result(String schema) { + this.schema = schema; + } + + String getSchema() { + return this.schema; + } + } + + @Test + void testMappingsWithTwoFunctions() throws IOException { + Map mappings = new HashMap<>(); + mappings.put("http://www.example.org/test.json", new Result("test")); + mappings.put("http://www.example.org/hello.json", new Result("hello")); + + MapResourceLoader loader = new MapResourceLoader(mappings::get, Result::getSchema); + InputStreamSource source = loader.getResource(AbsoluteIri.of("http://www.example.org/test.json")); + try (InputStream inputStream = source.getInputStream()) { + byte[] r = new byte[4]; + inputStream.read(r); + String value = new String(r, StandardCharsets.UTF_8); + assertEquals("test", value); + } + + InputStreamSource result = loader.getResource(AbsoluteIri.of("http://www.example.org/not-found.json")); + assertNull(result); + } +} diff --git a/src/test/java/com/networknt/schema/resource/MapSchemaIdResolverTest.java b/src/test/java/com/networknt/schema/resource/MapSchemaIdResolverTest.java new file mode 100644 index 000000000..896bf6299 --- /dev/null +++ b/src/test/java/com/networknt/schema/resource/MapSchemaIdResolverTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.AbsoluteIri; + +class MapSchemaIdResolverTest { + + @Test + void predicateMapping() { + MapSchemaIdResolver mapper = new MapSchemaIdResolver(test -> test.startsWith("http://www.example.org/"), + original -> original.replaceFirst("http://www.example.org/", "classpath:")); + AbsoluteIri result = mapper.resolve(AbsoluteIri.of("http://www.example.org/hello")); + assertEquals("classpath:hello", result.toString()); + result = mapper.resolve(AbsoluteIri.of("notmatchingprefixhttp://www.example.org/hello")); + assertNull(result); + } + +} diff --git a/src/test/java/com/networknt/schema/resource/MetaSchemaIdResolverTest.java b/src/test/java/com/networknt/schema/resource/MetaSchemaIdResolverTest.java new file mode 100644 index 000000000..8aac3c8ea --- /dev/null +++ b/src/test/java/com/networknt/schema/resource/MetaSchemaIdResolverTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.resource; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.io.InputStream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import com.networknt.schema.AbsoluteIri; +import com.networknt.schema.dialect.DialectId; + +/** + * MetaSchemaMapperTest. + */ +class MetaSchemaIdResolverTest { + + enum MapInput { + V4(DialectId.DRAFT_4), + V6(DialectId.DRAFT_6), + V7(DialectId.DRAFT_7), + V201909(DialectId.DRAFT_2019_09), + V202012(DialectId.DRAFT_2020_12); + + String iri; + + MapInput(String iri) { + this.iri = iri; + } + } + + @ParameterizedTest + @EnumSource(MapInput.class) + void map(MapInput input) throws IOException { + MetaSchemaIdResolver mapper = new MetaSchemaIdResolver(); + AbsoluteIri result = mapper.resolve(AbsoluteIri.of(input.iri)); + ClasspathResourceLoader loader = new ClasspathResourceLoader(); + InputStreamSource source = loader.getResource(result); + assertNotNull(source); + try (InputStream inputStream = source.getInputStream()) { + inputStream.read(); + } + } + +} diff --git a/src/test/java/com/networknt/schema/resource/SchemaLoaderTest.java b/src/test/java/com/networknt/schema/resource/SchemaLoaderTest.java new file mode 100644 index 000000000..4174a7ba8 --- /dev/null +++ b/src/test/java/com/networknt/schema/resource/SchemaLoaderTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.resource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; + +public class SchemaLoaderTest { + @Test + void allow() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaLoader( + schemaLoader -> schemaLoader.allow(iri -> iri.toString().startsWith("classpath:")))); + InvalidSchemaException invalidSchemaException = assertThrows(InvalidSchemaException.class, + () -> factory.getSchema(SchemaLocation.of("http://www.example.org/schema"))); + assertEquals("http://www.example.org/schema", invalidSchemaException.getError().getArguments()[0].toString()); + Schema schema = factory.getSchema(SchemaLocation.of("classpath:schema/example-main.json")); + assertNotNull(schema); + } + + @Test + void block() { + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.schemaLoader(schemaLoader -> schemaLoader.block(iri -> true))); + InvalidSchemaException invalidSchemaException = assertThrows(InvalidSchemaException.class, + () -> factory.getSchema(SchemaLocation.of("classpath:schema/example-main.json"))); + assertEquals("classpath:schema/example-main.json", + invalidSchemaException.getError().getArguments()[0].toString()); + } +} diff --git a/src/test/java/com/networknt/schema/serialization/DefaultNodeReaderTest.java b/src/test/java/com/networknt/schema/serialization/DefaultNodeReaderTest.java new file mode 100644 index 000000000..e6a61f0e9 --- /dev/null +++ b/src/test/java/com/networknt/schema/serialization/DefaultNodeReaderTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.serialization; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.InputFormat; +import com.networknt.schema.utils.JsonNodes; + +/** + * Test for Default Object Reader. + */ +class DefaultNodeReaderTest { + @Test + void location() throws JsonParseException, IOException { + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/myschema\",\r\n" + + " \"properties\": {\r\n" + + " \"startDate\": {\r\n" + + " \"format\": \"date\",\r\n" + + " \"minLength\": 6\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + JsonNode jsonNode = NodeReader.builder().locationAware().build().readTree(schemaData, InputFormat.JSON); + JsonNode idNode = jsonNode.at("/$id"); + JsonLocation location = JsonNodes.tokenStreamLocationOf(idNode); + assertEquals(2, location.getLineNr()); + assertEquals(10, location.getColumnNr()); + + JsonNode formatNode = jsonNode.at("/properties/startDate/format"); + location = JsonNodes.tokenStreamLocationOf(formatNode); + assertEquals(5, location.getLineNr()); + assertEquals(17, location.getColumnNr()); + + JsonNode minLengthNode = jsonNode.at("/properties/startDate/minLength"); + location = JsonNodes.tokenStreamLocationOf(minLengthNode); + assertEquals(6, location.getLineNr()); + assertEquals(20, location.getColumnNr()); + } + + @Test + void jsonLocation() throws IOException { + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/myschema\",\r\n" + + " \"properties\": {\r\n" + + " \"startDate\": {\r\n" + + " \"format\": \"date\",\r\n" + + " \"minLength\": 6\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + JsonNode jsonNode = NodeReader.builder().locationAware().build().readTree(schemaData, InputFormat.JSON); + + JsonLocation formatSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(jsonNode.at("/properties/startDate/format")); + JsonLocation minLengthSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(jsonNode.at("/properties/startDate/minLength")); + + assertEquals(5, formatSchemaNodeTokenLocation.getLineNr()); + assertEquals(17, formatSchemaNodeTokenLocation.getColumnNr()); + + assertEquals(6, minLengthSchemaNodeTokenLocation.getLineNr()); + assertEquals(20, minLengthSchemaNodeTokenLocation.getColumnNr()); + } + + @Test + void yamlLocation() throws IOException { + String schemaData = "---\r\n" + + "\"$id\": 'https://schema/myschema'\r\n" + + "properties:\r\n" + + " startDate:\r\n" + + " format: 'date'\r\n" + + " minLength: 6\r\n"; + JsonNode jsonNode = NodeReader.builder().locationAware().build().readTree(schemaData, InputFormat.YAML); + + JsonLocation formatSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(jsonNode.at("/properties/startDate/format")); + JsonLocation minLengthSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(jsonNode.at("/properties/startDate/minLength")); + + assertEquals(5, formatSchemaNodeTokenLocation.getLineNr()); + assertEquals(13, formatSchemaNodeTokenLocation.getColumnNr()); + + assertEquals(6, minLengthSchemaNodeTokenLocation.getLineNr()); + assertEquals(16, minLengthSchemaNodeTokenLocation.getColumnNr()); + } +} diff --git a/src/test/java/com/networknt/schema/suite/TestCase.java b/src/test/java/com/networknt/schema/suite/TestCase.java new file mode 100644 index 000000000..9aa19e433 --- /dev/null +++ b/src/test/java/com/networknt/schema/suite/TestCase.java @@ -0,0 +1,152 @@ +package com.networknt.schema.suite; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +/** + * An individual test case, containing multiple testSpecs of a single schema's + * behavior + */ +@JsonIgnoreProperties(ignoreUnknown = true) // ignore specification in additionalProperties.json +public class TestCase { + + /** + * The test case description (Required) + */ + private final String description; + + /** + * Any additional comments about the test case + */ + private final String comment; + + /** + * A valid JSON Schema (one written for the corresponding version directory that + * the file sits within). (Required) + */ + private final JsonNode schema; + + /** + * A set of related tests all using the same schema (Required) + */ + private final List tests; + + /** + * Indicates whether this test-case should be executed + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final boolean disabled; + + /** + * Describes why this test-case is disabled. + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final String reason; + + private TestSource source; + + /** + * Constructs a new TestCase + * + * @param description The test case description (Required) + * @param comment Any additional comments about the test case + * @param schema A valid JSON Schema (one written for the corresponding version directory that the file sits within) (Required) + * @param tests A set of related tests all using the same schema (Required) + */ + @JsonCreator + public TestCase( + @JsonProperty("description") String description, + @JsonProperty("comment") String comment, + @JsonProperty("schema") JsonNode schema, + @JsonProperty("disabled") Boolean disabled, + @JsonProperty("reason") String reason, + @JsonProperty("tests") List tests + ) { + this.description = description; + this.comment = comment; + this.schema = schema; + this.disabled = Boolean.TRUE.equals(disabled); + this.reason = reason; + + this.tests = tests; + if (null != tests) { + tests.forEach(test -> test.setTestCase(this)); + } + } + + /** + * Identifies the specification file containing this test-case. + * @return the path to the specification + */ + public Path getSpecification() { + return this.source.getPath(); + } + + /** + * The test case description (Required) + */ + public String getDescription() { + return this.description; + } + + public String getDisplayName() { + return String.format("%s (%s)", getDescription(), getSpecification()); + } + + /** + * Any additional comments about the test case + */ + public String getComment() { + return this.comment; + } + + /** + * A valid JSON Schema (one written for the corresponding version directory that + * the file sits within). (Required) + */ + public JsonNode getSchema() { + return this.schema; + } + + /** + * Indicates whether this test-case should be executed + */ + public boolean isDisabled() { + return this.disabled || this.source.isDisabled(); + } + + /** + * Describes why this test is disabled. + */ + public String getReason() { + return this.disabled ? this.reason : this.source.getReason(); + } + + /** + * A set of related tests all using the same schema (Required) + */ + public List getTests() { + return null != this.tests ? this.tests : Collections.emptyList(); + } + + public TestSource getSource() { + return this.source; + } + + void setSource(TestSource source) { + this.source = source; + } + +} diff --git a/src/test/java/com/networknt/schema/suite/TestSource.java b/src/test/java/com/networknt/schema/suite/TestSource.java new file mode 100644 index 000000000..29edd248f --- /dev/null +++ b/src/test/java/com/networknt/schema/suite/TestSource.java @@ -0,0 +1,88 @@ +package com.networknt.schema.suite; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.networknt.schema.serialization.JsonMapperFactory; + +public class TestSource { + protected static final TypeReference> testCaseType = new TypeReference>() { /* intentionally empty */}; + private static final ObjectMapper mapper = JsonMapperFactory.getInstance(); + + /** + * Indicates whether this test-source should be executed + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final boolean disabled; + + /** + * Describes why this test-source is disabled. + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final String reason; + + /** + * The location of the specification file containing these test-cases. + */ + private final Path path; + + private final List testCases; + + TestSource(Path path, List testCases, boolean disabled, String reason) { + this.disabled = disabled; + this.reason = reason; + this.path = path; + this.testCases = testCases; + if (null != testCases) { + testCases.forEach(c -> c.setSource(this)); + } + } + + /** + * Identifies the specification file containing these test-cases. + * @return the path to the specification + */ + public Path getPath() { + return this.path; + } + + public String getReason() { + return this.reason; + } + + public List getTestCases() { + return null != this.testCases ? this.testCases : Collections.emptyList(); + } + + public boolean isDisabled() { + return this.disabled; + } + + public static Optional loadFrom(Path path, boolean disabled, String reason) { + try (InputStream in = Files.newInputStream(path.toFile().toPath())) { + List testCases = mapper.readValue(in, testCaseType); + return Optional.of(new TestSource(path, testCases, disabled, reason)); + } catch (MismatchedInputException e) { + System.err.append("Not a valid test case: ").println(path); + return Optional.empty(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/test/java/com/networknt/schema/suite/TestSpec.java b/src/test/java/com/networknt/schema/suite/TestSpec.java new file mode 100644 index 000000000..e06e27f9f --- /dev/null +++ b/src/test/java/com/networknt/schema/suite/TestSpec.java @@ -0,0 +1,270 @@ +package com.networknt.schema.suite; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.SchemaRegistryConfig; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A single test + */ +public class TestSpec { + + /** + * The test description, briefly explaining which behavior it exercises + * (Required) + */ + private final String description; + + /** + * Any additional comments about the test + */ + private final String comment; + + /** + * The instance which should be validated against the schema in "schema". + * (Required) + */ + private final JsonNode data; + + /** + * Whether the validation process of this instance should consider the instance + * valid or not (Required) + */ + private final boolean valid; + + /** + * A mapping of how strict a keyword's validators should be. Defaults to + * {@literal true}. + *

+ * Each validator has its own understanding of what constitutes strict + * and permissive. + *

+ * This is an extension of the schema used to describe tests in the compliance suite + */ + private final Map strictness = new HashMap<>(0); + + /** + * The set of validation messages expected from testing data against the schema + *

+ * This is an extension of the schema used to describe tests in the compliance suite + *

+ */ + private final Set errors; + + /** + * Indicates whether this test should be executed + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final boolean disabled; + + /** + * Describes why this test is disabled. + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ */ + private final String reason; + + /** + * Indicates whether the test should consider a strict definition of an + * enum. This is related to earlier versions of the OpenAPI specification + * that did not faithfully follow the JSON Schema specification. + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + *

+ * + * @deprecated This property only appears in a single V4 tests that are not + * in the validation suite. It does not appear in the tests + * related to any version of the OpenAPI specification. + */ + private final boolean typeLoose; + + /** + * Identifies the regular expression engine to use for this test-case. + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + */ + private final RegexKind regex; + + /** + * Config information to be provided for {@link SchemaRegistryConfig} with which schema can be validated + *

+ * This is an extension of the schema used to describe tests in the + * compliance suite + */ + private final Map config; + + /** + * The TestCase that contains this TestSpec. + */ + private TestCase testCase; + + /** + * Constructs a new TestSpec + * + * @param description The test description, briefly explaining which behavior it exercises (Required) + * @param comment Any additional comments about the test + * @param data The instance which should be validated against the schema in "schema" (Required) + * @param valid Whether the validation process of this instance should consider the instance valid or not (Required) + * @param strictness A mapping of how strict a keyword's validators should be. + * @param errors A sequence of validation messages expected from testing data against the schema + * @param disabled Indicates whether this test should be executed (Defaults to FALSE) + * @param isTypeLoose Indicates whether the test should consider a strict definition of an enum (Defaults to FALSE) + */ + @JsonCreator + public TestSpec( + @JsonProperty("description") String description, + @JsonProperty("comment") String comment, + @JsonProperty("config") Map config, + @JsonProperty("data") JsonNode data, + @JsonProperty("valid") boolean valid, + @JsonProperty("strictness") Map strictness, + @JsonProperty("errors") Set errors, + @JsonProperty("isTypeLoose") Boolean isTypeLoose, + @JsonProperty("disabled") Boolean disabled, + @JsonProperty("reason") String reason, + @JsonProperty(value = "regex", defaultValue = "unspecified") RegexKind regex + ) { + this.description = description; + this.comment = comment; + this.config = config; + this.data = data; + this.valid = valid; + this.errors = errors; + this.disabled = Boolean.TRUE.equals(disabled); + this.reason = reason; + this.typeLoose = Boolean.TRUE.equals(isTypeLoose); + this.regex = regex; + if (null != strictness) { + this.strictness.putAll(strictness); + } + } + + /** + * The TestCase that contains this TestSpec. + * @return the owning TestCase + */ + public TestCase getTestCase() { + return this.testCase; + } + + /** + * Changes the TestCase that contains this TestSpec. + * @param testCase the owning TestCase + */ + void setTestCase(TestCase testCase) { + this.testCase = testCase; + } + + /** + * The test description, briefly explaining which behavior it exercises + * (Required) + */ + public String getDescription() { + return this.description; + } + + /** + * Any additional comments about the test + */ + public String getComment() { + return this.comment; + } + + + /** + * Config information to be provided for {@link SchemaRegistryConfig} with which schema can be validated + */ + public Map getConfig() { + return config; + } + + /** + * The instance which should be validated against the schema in "schema". + * (Required) + */ + public JsonNode getData() { + return this.data; + } + + /** + * Indicates whether this test should be executed + */ + public boolean isDisabled() { + return this.disabled || this.testCase.isDisabled(); + } + + /** + * Describes why this test is disabled. + */ + public String getReason() { + return this.disabled ? this.reason : this.testCase.getReason(); + } + + /** + * Whether the validation process of this instance should consider the instance + * valid or not (Required) + */ + public boolean isValid() { + return this.valid; + } + + /** + * @return A mapping of how strict a keyword's validators should be (never null). + */ + public Map getStrictness() { + return this.strictness; + } + + /** + * A sequence of validation messages expected from testing data against the schema. + *

+ * This is an extension of the schema used to describe tests in the compliance suite + *

+ * + * @return a non-null list of expected validation messages + */ + public Set getErrors() { + return new HashSet<>(null != this.errors ? this.errors : Collections.emptySet()); + } + + /** + * Indicates whether the test should consider a strict definition of an + * enum. This is related to earlier versions of the OpenAPI specification + * that did not faithfully follow the JSON Schema specification. + *

+ * This is an extension of the schema used to describe tests in the compliance suite + *

+ * + * @deprecated This property only appears in V4 tests that are not in the + * validation suite. It does not appear in the tests related + * to any version of the OpenAPI specification. + */ + @Deprecated + public boolean isTypeLoose() { + return this.typeLoose; + } + + public RegexKind getRegex() { + return this.regex; + } + + public enum RegexKind { + @JsonProperty("unspecified") UNSPECIFIED, + @JsonProperty("ecma-262") JONI, + @JsonProperty("jdk") JDK + } +} diff --git a/src/test/java/com/networknt/schema/utils/AbsoluteIrisTest.java b/src/test/java/com/networknt/schema/utils/AbsoluteIrisTest.java new file mode 100644 index 000000000..088fa87ee --- /dev/null +++ b/src/test/java/com/networknt/schema/utils/AbsoluteIrisTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.AbsoluteIri; + +/** + * Tests for AbsoluteIris. + */ +class AbsoluteIrisTest { + @Test + void uri() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://www.example.org/test")); + assertEquals("https://www.example.org/test", result); + } + + @Test + void uriWithQueryString() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://www.example.org/test/?filter[test]=hello")); + assertEquals("https://www.example.org/test/?filter%5Btest%5D=hello", result); + } + + @Test + void iriDomain() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://Bücher.example")); + assertEquals("https://xn--bcher-kva.example", result); + } + + @Test + void iriDomainWithPath() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://Bücher.example/assets/produktdatenblätter.pdf")); + result = URI.create(result).toASCIIString(); + assertEquals("https://xn--bcher-kva.example/assets/produktdatenbl%C3%A4tter.pdf", result); + } + + @Test + void uriDomainWithPath() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://www.example.org/assets/produktdatenblätter.pdf")); + result = URI.create(result).toASCIIString(); + assertEquals("https://www.example.org/assets/produktdatenbl%C3%A4tter.pdf", result); + } + + @Test + void iriDomainWithPathTrailingSlash() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://Bücher.example/assets/produktdatenblätter/")); + assertEquals("https://xn--bcher-kva.example/assets/produktdatenbl%C3%A4tter/", result); + } + + @Test + void iriDomainWithQueryString() throws MalformedURLException { + String result = AbsoluteIris.toUri(AbsoluteIri.of("https://Bücher.example/assets/produktdatenblätter/?filter[test]=hello")); + assertEquals("https://xn--bcher-kva.example/assets/produktdatenbl%C3%A4tter/?filter%5Btest%5D=hello", result); + URL url = URI.create(result).toURL(); + assertEquals("https", url.getProtocol()); + assertEquals("xn--bcher-kva.example", url.getHost()); + assertEquals("/assets/produktdatenbl%C3%A4tter/", url.getPath()); + assertEquals("filter%5Btest%5D=hello", url.getQuery()); + } + + @Test + void invalid() { + String result = AbsoluteIris.toUri(AbsoluteIri.of("www.example.org/test")); + assertEquals("www.example.org/test", result); + } +} diff --git a/src/test/java/com/networknt/schema/utils/JsonNodesTest.java b/src/test/java/com/networknt/schema/utils/JsonNodesTest.java new file mode 100644 index 000000000..fef8bfc6f --- /dev/null +++ b/src/test/java/com/networknt/schema/utils/JsonNodesTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.Error; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.serialization.NodeReader; +import com.networknt.schema.serialization.node.LocationJsonNodeFactoryFactory; +/** + * Tests for JsonNodes. + */ +class JsonNodesTest { + @Test + void location() throws JsonParseException, IOException { + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/myschema\",\r\n" + + " \"properties\": {\r\n" + + " \"startDate\": {\r\n" + + " \"format\": \"date\",\r\n" + + " \"minLength\": 6\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + JsonNode jsonNode = JsonNodes.readTree(JsonMapperFactory.getInstance(), schemaData, + LocationJsonNodeFactoryFactory.getInstance()); + JsonNode idNode = jsonNode.at("/$id"); + JsonLocation location = JsonNodes.tokenStreamLocationOf(idNode); + assertEquals(2, location.getLineNr()); + assertEquals(10, location.getColumnNr()); + + JsonNode formatNode = jsonNode.at("/properties/startDate/format"); + location = JsonNodes.tokenStreamLocationOf(formatNode); + assertEquals(5, location.getLineNr()); + assertEquals(17, location.getColumnNr()); + + JsonNode minLengthNode = jsonNode.at("/properties/startDate/minLength"); + location = JsonNodes.tokenStreamLocationOf(minLengthNode); + assertEquals(6, location.getLineNr()); + assertEquals(20, location.getColumnNr()); + } + + @Test + void jsonLocation() { + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/myschema\",\r\n" + + " \"properties\": {\r\n" + + " \"startDate\": {\r\n" + + " \"format\": \"date\",\r\n" + + " \"minLength\": 6\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"startDate\": \"1\"\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(nodeReader -> nodeReader.locationAware())); + Schema schema = factory.getSchema(schemaData, InputFormat.JSON); + List messages = schema.validate(inputData, InputFormat.JSON, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + List list = messages.stream().collect(Collectors.toList()); + Error format = list.get(0); + JsonLocation formatInstanceNodeTokenLocation = JsonNodes.tokenStreamLocationOf(format.getInstanceNode()); + JsonLocation formatSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(format.getSchemaNode()); + Error minLength = list.get(1); + JsonLocation minLengthInstanceNodeTokenLocation = JsonNodes.tokenStreamLocationOf(minLength.getInstanceNode()); + JsonLocation minLengthSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(minLength.getSchemaNode()); + + assertEquals("format", format.getKeyword()); + + assertEquals("date", format.getSchemaNode().asText()); + assertEquals(5, formatSchemaNodeTokenLocation.getLineNr()); + assertEquals(17, formatSchemaNodeTokenLocation.getColumnNr()); + + assertEquals("1", format.getInstanceNode().asText()); + assertEquals(2, formatInstanceNodeTokenLocation.getLineNr()); + assertEquals(16, formatInstanceNodeTokenLocation.getColumnNr()); + + assertEquals("minLength", minLength.getKeyword()); + + assertEquals("6", minLength.getSchemaNode().asText()); + assertEquals(6, minLengthSchemaNodeTokenLocation.getLineNr()); + assertEquals(20, minLengthSchemaNodeTokenLocation.getColumnNr()); + + assertEquals("1", minLength.getInstanceNode().asText()); + assertEquals(2, minLengthInstanceNodeTokenLocation.getLineNr()); + assertEquals(16, minLengthInstanceNodeTokenLocation.getColumnNr()); + } + + @Test + void yamlLocation() { + String schemaData = "---\r\n" + + "\"$id\": 'https://schema/myschema'\r\n" + + "properties:\r\n" + + " startDate:\r\n" + + " format: 'date'\r\n" + + " minLength: 6\r\n"; + String inputData = "---\r\n" + + "startDate: '1'\r\n"; + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder().locationAware().build())); + Schema schema = factory.getSchema(schemaData, InputFormat.YAML); + List messages = schema.validate(inputData, InputFormat.YAML, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true)); + }); + List list = messages.stream().collect(Collectors.toList()); + Error format = list.get(0); + JsonLocation formatInstanceNodeTokenLocation = JsonNodes.tokenStreamLocationOf(format.getInstanceNode()); + JsonLocation formatSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(format.getSchemaNode()); + Error minLength = list.get(1); + JsonLocation minLengthInstanceNodeTokenLocation = JsonNodes.tokenStreamLocationOf(minLength.getInstanceNode()); + JsonLocation minLengthSchemaNodeTokenLocation = JsonNodes.tokenStreamLocationOf(minLength.getSchemaNode()); + + assertEquals("format", format.getKeyword()); + + assertEquals("date", format.getSchemaNode().asText()); + assertEquals(5, formatSchemaNodeTokenLocation.getLineNr()); + assertEquals(13, formatSchemaNodeTokenLocation.getColumnNr()); + + assertEquals("1", format.getInstanceNode().asText()); + assertEquals(2, formatInstanceNodeTokenLocation.getLineNr()); + assertEquals(12, formatInstanceNodeTokenLocation.getColumnNr()); + + assertEquals("minLength", minLength.getKeyword()); + + assertEquals("6", minLength.getSchemaNode().asText()); + assertEquals(6, minLengthSchemaNodeTokenLocation.getLineNr()); + assertEquals(16, minLengthSchemaNodeTokenLocation.getColumnNr()); + + assertEquals("1", minLength.getInstanceNode().asText()); + assertEquals(2, minLengthInstanceNodeTokenLocation.getLineNr()); + assertEquals(12, minLengthInstanceNodeTokenLocation.getColumnNr()); + } + + @Test + void missingNode() { + JsonNode missing = JsonNodes.readTree(JsonMapperFactory.getInstance(), + new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)), + LocationJsonNodeFactoryFactory.getInstance()); + assertTrue(missing.isMissingNode()); + } + + @Test + void types() { + String json = "{\r\n" + + " \"properties\": {\r\n" + + " \"number\": 1234.56789,\r\n" + + " \"string\": \"value\",\r\n" + + " \"boolean\": true,\r\n" + + " \"array\": [],\r\n" + + " \"object\": {},\r\n" + + " \"null\": null\r\n" + + " }\r\n" + + "}"; + ObjectMapper objectMapper = JsonMapperFactory.getInstance() + .copy() + .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + JsonNode root = JsonNodes.readTree(objectMapper, json, LocationJsonNodeFactoryFactory.getInstance()); + JsonNode numberNode = root.at("/properties/number"); + assertEquals(3, JsonNodes.tokenStreamLocationOf(numberNode).getLineNr()); + JsonNode stringNode = root.at("/properties/string"); + assertEquals(4, JsonNodes.tokenStreamLocationOf(stringNode).getLineNr()); + JsonNode booleanNode = root.at("/properties/boolean"); + assertEquals(5, JsonNodes.tokenStreamLocationOf(booleanNode).getLineNr()); + JsonNode arrayNode = root.at("/properties/array"); + assertEquals(6, JsonNodes.tokenStreamLocationOf(arrayNode).getLineNr()); + JsonNode objectNode = root.at("/properties/object"); + assertEquals(7, JsonNodes.tokenStreamLocationOf(objectNode).getLineNr()); + JsonNode nullNode = root.at("/properties/null"); + assertEquals(8, JsonNodes.tokenStreamLocationOf(nullNode).getLineNr()); + } +} diff --git a/src/test/java/com/networknt/schema/utils/StringsTest.java b/src/test/java/com/networknt/schema/utils/StringsTest.java new file mode 100755 index 000000000..cac42ceda --- /dev/null +++ b/src/test/java/com/networknt/schema/utils/StringsTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016 Network New Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class StringsTest { + + private static final String[] validNumericValues = { + "1", "-1", "1.1", "-1.1", "0E+1", "0E-1", "0E1", "-0E+1", "-0E-1", "-0E1", "0.1E+1", "0.1E-1", "0.1E1", + "-0.1E+1", "-0.1E-1", "-0.1E1", "10.1", "-10.1", "10E+1", "10E-1", "10E1", "-10E+1", "-10E-1", "-10E1", + "10.1E+1", "10.1E-1", "10.1E1", "-10.1E+1", "-10.1E-1", "-10.1E1", "1E+0", "1E-0", "1E0", + "1E00000000000000000000" + }; + private static final String[] invalidNumericValues = { + "01.1", "1.", ".1", "0.1.1", "E1", "E+1", "E-1", ".E1", ".E+1", ".E-1", ".1E1", ".1E+1", ".1E-1", "1E-", + "1E+", "1E", "+", "-", "1a", "0.1a", "0E1a", "0E-1a", "1.0a", "1.0aE1" + //, "+0", "+1" // for backward compatibility, in violation of JSON spec + }; + + @Test + void testNumericValues() { + for (String validValue : validNumericValues) { + assertTrue(Strings.isNumeric(validValue), validValue); + } + } + + @Test + void testNonNumericValues() { + for (String invalidValue : invalidNumericValues) { + assertFalse(Strings.isNumeric(invalidValue), invalidValue); + } + } +} diff --git a/src/test/java/com/networknt/schema/utils/ThreadSafeCachingSupplierTest.java b/src/test/java/com/networknt/schema/utils/ThreadSafeCachingSupplierTest.java new file mode 100644 index 000000000..3c52f11a0 --- /dev/null +++ b/src/test/java/com/networknt/schema/utils/ThreadSafeCachingSupplierTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +/** + * Test for CachedSupplier. + */ +class ThreadSafeCachingSupplierTest { + @Test + void nullValue() { + ThreadSafeCachingSupplier supplier = new ThreadSafeCachingSupplier<>(null); + assertNull(supplier.get()); + } + + @Test + void concurrency() throws Exception { + AtomicInteger value = new AtomicInteger(0); + + ThreadSafeCachingSupplier supplier = new ThreadSafeCachingSupplier<>(() -> { + return value.addAndGet(1); + }); + Exception[] instance = new Exception[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + int result = supplier.get(); + assertEquals(1, result); + } catch(Exception e) { + synchronized(instance) { + instance[0] = e; + } + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + if (instance[0] != null) { + throw instance[0]; + } + assertEquals(1, value.get()); + } +} diff --git a/src/test/java/com/networknt/schema/vocabulary/VocabularyTest.java b/src/test/java/com/networknt/schema/vocabulary/VocabularyTest.java new file mode 100644 index 000000000..b90e5d205 --- /dev/null +++ b/src/test/java/com/networknt/schema/vocabulary/VocabularyTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.networknt.schema.vocabulary; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.InvalidSchemaException; +import com.networknt.schema.OutputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.BasicDialectRegistry; +import com.networknt.schema.dialect.Dialect; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.keyword.AnnotationKeyword; +import com.networknt.schema.output.OutputUnit; + +/** + * Tests for vocabulary support in meta schemas. + */ +class VocabularyTest { + @Test + void noValidation() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://www.example.com/vocab/validation\": false,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/using/no/validation\",\r\n" + + " \"$schema\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"properties\": {\r\n" + + " \"badProperty\": false,\r\n" + + " \"numberProperty\": {\r\n" + + " \"minimum\": 10\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry + .withDefaultDialectId("https://www.example.com/no-validation-no-format/schema", + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData)))) + .getSchema(schemaData); + + String inputDataNoValidation = "{\r\n" + + " \"numberProperty\": 1\r\n" + + "}"; + + List messages = schema.validate(inputDataNoValidation, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Set validation vocab + schema = SchemaRegistry + .withDefaultDialectId("https://www.example.com/no-validation-no-format/schema", + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData.replace("https://www.example.com/vocab/validation", + Vocabulary.DRAFT_2020_12_VALIDATION.getId()))))) + .getSchema(schemaData); + messages = schema.validate(inputDataNoValidation, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals("minimum", messages.iterator().next().getKeyword()); + assertEquals(SpecificationVersion.DRAFT_2020_12, schema.getSchemaContext().getDialect().getSpecificationVersion()); + } + + @Test + void noFormatValidation() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://www.example.com/vocab/format\": false,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/using/no/format\",\r\n" + + " \"$schema\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"properties\": {\r\n" + + " \"dateProperty\": {\r\n" + + " \"format\": \"date\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Schema schema = SchemaRegistry + .withDefaultDialectId("https://www.example.com/no-validation-no-format/schema", + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData)))) + .getSchema(schemaData); + + String inputDataNoValidation = "{\r\n" + + " \"dateProperty\": \"hello\"\r\n" + + "}"; + + List messages = schema.validate(inputDataNoValidation, InputFormat.JSON, + executionContext -> executionContext.executionConfig(executionConfig -> executionConfig.formatAssertionsEnabled(true))); + assertEquals(0, messages.size()); + + // Set format assertion vocab + schema = SchemaRegistry + .withDefaultDialectId("https://www.example.com/no-validation-no-format/schema", + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData.replace("https://www.example.com/vocab/format", + Vocabulary.DRAFT_2020_12_FORMAT_ASSERTION.getId()))))) + .getSchema(schemaData); + messages = schema.validate(inputDataNoValidation, InputFormat.JSON); + assertEquals(1, messages.size()); + assertEquals("format", messages.iterator().next().getKeyword()); + } + + @Test + void requiredUnknownVocabulary() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://www.example.com/vocab/format\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/using/no/format\",\r\n" + + " \"$schema\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"properties\": {\r\n" + + " \"dateProperty\": {\r\n" + + " \"format\": \"date\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + SchemaRegistry factory = SchemaRegistry + .withDefaultDialectId("https://www.example.com/no-validation-no-format/schema", + builder -> builder.resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData)))); + assertThrows(InvalidSchemaException.class, () -> factory.getSchema(schemaData)); + } + + @Test + void customVocabulary() { + String metaSchemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"$id\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"$vocabulary\": {\r\n" + + " \"https://www.example.com/vocab/format\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/applicator\": true,\r\n" + + " \"https://json-schema.org/draft/2020-12/vocab/core\": true\r\n" + + " },\r\n" + + " \"allOf\": [\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/applicator\" },\r\n" + + " { \"$ref\": \"https://json-schema.org/draft/2020-12/meta/core\" }\r\n" + + " ]\r\n" + + "}"; + String schemaData = "{\r\n" + + " \"$id\": \"https://schema/using/no/format\",\r\n" + + " \"$schema\": \"https://www.example.com/no-validation-no-format/schema\",\r\n" + + " \"hello\": {\r\n" + + " \"dateProperty\": {\r\n" + + " \"format\": \"date\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + VocabularyRegistry vocabularyRegistry = id -> { + if ("https://www.example.com/vocab/format".equals(id)) { + return new Vocabulary("https://www.example.com/vocab/format", new AnnotationKeyword("hello")); + } + return null; + }; + + Dialect dialect = Dialect + .builder("https://www.example.com/no-validation-no-format/schema", Dialects.getDraft202012()) + .vocabularyRegistry(vocabularyRegistry) + .build(); + SchemaRegistry schemaRegistry = SchemaRegistry + .withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.dialectRegistry(new BasicDialectRegistry(dialect)).resourceLoaders(resourceLoaders -> resourceLoaders.resources(Collections + .singletonMap("https://www.example.com/no-validation-no-format/schema", + metaSchemaData)))); + Schema schema = schemaRegistry.getSchema(schemaData); + OutputUnit outputUnit = schema.validate("{}", InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { + executionContext.executionConfig(executionConfig -> executionConfig + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); + assertNotNull(outputUnit.getAnnotations().get("hello")); + } +} diff --git a/src/test/java/com/networknt/schema/walk/WalkListenerTest.java b/src/test/java/com/networknt/schema/walk/WalkListenerTest.java new file mode 100644 index 000000000..dc8a82e69 --- /dev/null +++ b/src/test/java/com/networknt/schema/walk/WalkListenerTest.java @@ -0,0 +1,973 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.walk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SchemaRef; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.DialectId; +import com.networknt.schema.keyword.ItemsLegacyValidator; +import com.networknt.schema.keyword.ItemsValidator; +import com.networknt.schema.keyword.PropertiesValidator; +import com.networknt.schema.path.NodePath; +import com.networknt.schema.keyword.KeywordType; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.utils.JsonNodes; +import com.networknt.schema.utils.SchemaRefs; +import com.networknt.schema.Error; +import com.networknt.schema.Result; + +/** + * JsonSchemaWalkListenerTest. + */ +class WalkListenerTest { + + @Test + void keywordListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List propertyKeywords = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("propertyKeywords", key -> new ArrayList<>()); + propertyKeywords.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + Result result = schema.walk(inputData, InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + @SuppressWarnings("unchecked") + List propertyKeywords = (List) result.getExecutionContext().getCollectorContext().get("propertyKeywords"); + assertEquals(3, propertyKeywords.size()); + assertEquals("properties", propertyKeywords.get(0).getValidator().getKeyword()); + assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); + //assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath() + // .append(propertyKeywords.get(0).getKeyword()).toString()); + assertEquals("/tags/0", propertyKeywords.get(1).getInstanceLocation().toString()); + assertEquals("image", propertyKeywords.get(1).getInstanceNode().get("name").asText()); + //assertEquals("/properties/tags/items/$ref/properties", + // propertyKeywords.get(1).getValidator().getEvaluationPath().toString()); + //assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getEvaluationPath() + // .append(propertyKeywords.get(1).getKeyword()).toString()); + assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString()); + //assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getEvaluationPath() + // .append(propertyKeywords.get(2).getKeyword()).toString()); + assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText()); + } + + @Test + void propertyListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List properties = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("properties", key -> new ArrayList<>()); + properties.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + WalkConfig walkConfig = WalkConfig.builder() + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + Result result = schema.walk(inputData, InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List properties = (List) result.getExecutionContext().getCollectorContext().get("properties"); + assertEquals(5, properties.size()); + assertEquals("properties", properties.get(0).getValidator().getKeyword()); + + assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags", properties.get(0).getEvaluationPath().toString()); + + assertEquals("/tags/0/name", properties.get(1).getInstanceLocation().toString()); + assertEquals("image", properties.get(1).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(1).getEvaluationPath().toString()); + + assertEquals("/tags/0/description", properties.get(2).getInstanceLocation().toString()); + assertEquals("An image", properties.get(2).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(2).getEvaluationPath().toString()); + + assertEquals("/tags/1/name", properties.get(3).getInstanceLocation().toString()); + assertEquals("link", properties.get(3).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(3).getEvaluationPath().toString()); + + assertEquals("/tags/1/description", properties.get(4).getInstanceLocation().toString()); + assertEquals("A link", properties.get(4).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getEvaluationPath().toString()); + } + + @Test + void itemsListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList<>()); + items.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .build(); + + Result result = schema.walk(inputData, InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(2, items.size()); + assertEquals("items", items.get(0).getValidator().getKeyword()); + assertInstanceOf(ItemsLegacyValidator.class, items.get(0).getValidator()); + + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(0).getEvaluationPath().toString()); + + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(1).getEvaluationPath().toString()); + } + + @Test + void items202012Listener() { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList<>()); + items.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .build(); + Result result = schema.walk(inputData, InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(2, items.size()); + assertEquals("items", items.get(0).getValidator().getKeyword()); + assertInstanceOf(ItemsValidator.class, items.get(0).getValidator()); + + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(0).getEvaluationPath().toString()); + + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(1).getEvaluationPath().toString()); + } + + @Test + void draft201909() { + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List propertyKeywords = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("propertyKeywords", key -> new ArrayList<>()); + propertyKeywords.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09) + .getSchema(SchemaLocation.of(DialectId.DRAFT_2019_09)); + + String inputData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"kebab-case\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"snake_case\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"a\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + Result result = schema.walk(inputData, InputFormat.JSON, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertTrue(result.getErrors().isEmpty()); + + List propertyKeywords = result.getExecutionContext().getCollectorContext().get("propertyKeywords"); + + assertEquals(28, propertyKeywords.size()); + + assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); + assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath().append(propertyKeywords.get(0).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchema().getSchemaLocation().append(propertyKeywords.get(0).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(1).getInstanceLocation().toString()); + assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).getEvaluationPath().append(propertyKeywords.get(1).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(1).getSchema().getSchemaLocation().append(propertyKeywords.get(1).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(2).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).getEvaluationPath().append(propertyKeywords.get(2).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(2).getSchema().getSchemaLocation().append(propertyKeywords.get(2).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(3).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).getEvaluationPath().append(propertyKeywords.get(3).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(3).getSchema().getSchemaLocation().append(propertyKeywords.get(3).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(4).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).getEvaluationPath().append(propertyKeywords.get(4).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(4).getSchema().getSchemaLocation().append(propertyKeywords.get(4).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(5).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).getEvaluationPath().append(propertyKeywords.get(5).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(5).getSchema().getSchemaLocation().append(propertyKeywords.get(5).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(6).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).getEvaluationPath().append(propertyKeywords.get(6).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(6).getSchema().getSchemaLocation().append(propertyKeywords.get(6).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(7).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).getEvaluationPath().append(propertyKeywords.get(7).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(7).getSchema().getSchemaLocation().append(propertyKeywords.get(7).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(8).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).getEvaluationPath().append(propertyKeywords.get(8).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(8).getSchema().getSchemaLocation().append(propertyKeywords.get(8).getKeyword()).toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(9).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).getEvaluationPath().append(propertyKeywords.get(9).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(9).getSchema().getSchemaLocation().append(propertyKeywords.get(9).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(10).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).getEvaluationPath().append(propertyKeywords.get(10).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(10).getSchema().getSchemaLocation().append(propertyKeywords.get(10).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(11).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).getEvaluationPath().append(propertyKeywords.get(11).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(11).getSchema().getSchemaLocation().append(propertyKeywords.get(11).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(12).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).getEvaluationPath().append(propertyKeywords.get(12).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(12).getSchema().getSchemaLocation().append(propertyKeywords.get(12).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(13).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).getEvaluationPath().append(propertyKeywords.get(13).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(13).getSchema().getSchemaLocation().append(propertyKeywords.get(13).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(14).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).getEvaluationPath().append(propertyKeywords.get(14).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(14).getSchema().getSchemaLocation().append(propertyKeywords.get(14).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(15).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).getEvaluationPath().append(propertyKeywords.get(15).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(15).getSchema().getSchemaLocation().append(propertyKeywords.get(15).getKeyword()).toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(16).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).getEvaluationPath().append(propertyKeywords.get(16).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(16).getSchema().getSchemaLocation().append(propertyKeywords.get(16).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(17).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).getEvaluationPath().append(propertyKeywords.get(17).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(17).getSchema().getSchemaLocation().append(propertyKeywords.get(17).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(18).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).getEvaluationPath().append(propertyKeywords.get(18).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(18).getSchema().getSchemaLocation().append(propertyKeywords.get(18).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(19).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).getEvaluationPath().append(propertyKeywords.get(19).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(19).getSchema().getSchemaLocation().append(propertyKeywords.get(19).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(20).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).getEvaluationPath().append(propertyKeywords.get(20).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(20).getSchema().getSchemaLocation().append(propertyKeywords.get(20).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(21).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).getEvaluationPath().append(propertyKeywords.get(21).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(21).getSchema().getSchemaLocation().append(propertyKeywords.get(21).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(22).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).getEvaluationPath().append(propertyKeywords.get(22).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(22).getSchema().getSchemaLocation().append(propertyKeywords.get(22).getKeyword()).toString()); + + assertEquals("/properties/a", propertyKeywords.get(23).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).getEvaluationPath().append(propertyKeywords.get(23).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(23).getSchema().getSchemaLocation().append(propertyKeywords.get(23).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(24).getInstanceLocation().toString()); + assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).getEvaluationPath().append(propertyKeywords.get(24).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(24).getSchema().getSchemaLocation().append(propertyKeywords.get(24).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(25).getInstanceLocation().toString()); + assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).getEvaluationPath().append(propertyKeywords.get(25).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(25).getSchema().getSchemaLocation().append(propertyKeywords.get(25).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(26).getInstanceLocation().toString()); + assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).getEvaluationPath().append(propertyKeywords.get(26).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(26).getSchema().getSchemaLocation().append(propertyKeywords.get(26).getKeyword()).toString()); + + assertEquals("", propertyKeywords.get(27).getInstanceLocation().toString()); + assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getEvaluationPath().append(propertyKeywords.get(27).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchema().getSchemaLocation().append(propertyKeywords.get(27).getKeyword()).toString()); + } + + @Test + void applyDefaults() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"S\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + WalkConfig walkConfig = WalkConfig.builder() + .applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + Result result = schema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getErrors().isEmpty()); + } + + @Test + void applyDefaultsWithWalker() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"S\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + Schema schema = walkEvent.getSchema(); + SchemaRef schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + JsonNode defaultNode = schema.getSchemaNode().get("default"); + if (defaultNode != null) { + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + WalkConfig walkConfig = WalkConfig.builder() + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + Result result = schema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getErrors().isEmpty()); + } + + @Test + void applyInvalidDefaultsWithWalker() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": 1\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + Schema schema = walkEvent.getSchema(); + SchemaRef schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + JsonNode defaultNode = schema.getSchemaNode().get("default"); + if (defaultNode != null) { + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + WalkConfig walkConfig = WalkConfig.builder() + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + Result result = schema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString()); + assertFalse(result.getErrors().isEmpty()); + + inputNode = JsonMapperFactory.getInstance().readTree("{}"); + result = schema.walk(inputNode, false, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getErrors().isEmpty()); + } + + @Test + void missingRequired() throws JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"integer\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + Map missingSchemaNode = new LinkedHashMap<>(); + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + JsonNode requiredNode = walkEvent.getSchema().getSchemaNode().get("required"); + List requiredProperties = new ArrayList<>(); + if (requiredNode != null) { + if (requiredNode.isArray()) { + for (JsonNode fieldName : requiredNode) { + requiredProperties.add(fieldName.asText()); + } + } + } + for (String requiredProperty : requiredProperties) { + JsonNode propertyNode = walkEvent.getInstanceNode().get(requiredProperty); + if (propertyNode == null) { + // Get the schema + PropertiesValidator propertiesValidator = walkEvent.getValidator(); + Schema propertySchema = propertiesValidator.getSchemas().get(requiredProperty); + SchemaRef schemaRef = SchemaRefs.from(propertySchema, walkEvent.getExecutionContext()); + if (schemaRef != null) { + propertySchema = schemaRef.getSchema(); + } + missingSchemaNode.put(requiredProperty, propertySchema.getSchemaNode()); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .keywordWalkListenerRunner(keywordWalkListenerRunner) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + Result result = schema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertFalse(result.getErrors().isEmpty()); + assertEquals("{\"type\":\"integer\"}", missingSchemaNode.get("s").toString()); + assertEquals("{\"type\":\"string\"}", missingSchemaNode.get("ref").toString()); + } + + @Test + void generateDataWithWalker() throws JsonProcessingException { + Map> generators = new HashMap<>(); + generators.put("name.findName", () -> "John Doe"); + generators.put("internet.email", () -> "john.doe@gmail.com"); + + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"$ref\": \"#/$defs/Name\"\r\n" + + " },\r\n" + + " \"email\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"faker\": \"internet.email\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"name\",\r\n" + + " \"email\"\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"Name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"faker\": \"name.findName\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + Schema schema = walkEvent.getSchema(); + SchemaRef schemaRef = null; + do { + schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + } while (schemaRef != null); + JsonNode fakerNode = schema.getSchemaNode().get("faker"); + if (fakerNode != null) { + String faker = fakerNode.asText(); + String fakeData = generators.get(faker).get(); + JsonNode fakeDataNode = JsonNodeFactory.instance.textNode(fakeData); + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), fakeDataNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }) + .build(); + WalkConfig walkConfig = WalkConfig.builder() + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + Result result = schema.walk(inputNode, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + assertEquals("{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"}", inputNode.toString()); + assertTrue(result.getErrors().isEmpty()); + } + + /** + * Issue 989 + */ + @Test + void itemListenerDraft201909() { + String schemaData = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"children\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }"; + WalkListener listener = new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(listener).build(); + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder().propertyWalkListener(listener).build(); + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2019_09).getSchema(schemaData); + + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(4, items.size()); + assertEquals("/name", items.get(0).getInstanceLocation().toString()); + assertEquals("properties", items.get(0).getKeyword()); + assertEquals("#/properties/name", items.get(0).getSchema().getSchemaLocation().toString()); + assertEquals("/children/0/name", items.get(1).getInstanceLocation().toString()); + assertEquals("properties", items.get(1).getKeyword()); + assertEquals("#/properties/children/items/properties/name", items.get(1).getSchema().getSchemaLocation().toString()); + assertEquals("/children/0", items.get(2).getInstanceLocation().toString()); + assertEquals("items", items.get(2).getKeyword()); + assertEquals("#/properties/children/items", items.get(2).getSchema().getSchemaLocation().toString()); + assertEquals("/children", items.get(3).getInstanceLocation().toString()); + assertEquals("properties", items.get(3).getKeyword()); + assertEquals("#/properties/children", items.get(3).getSchema().getSchemaLocation().toString()); + } + + /** + * Issue 989 + */ + @Test + void itemListenerDraft202012() { + String schemaData = " {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"children\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }"; + WalkListener listener = new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext() + .getCollectorContext() + .getData() + .computeIfAbsent("items", key -> new ArrayList()); + items.add(walkEvent); + } + }; + ItemWalkListenerRunner itemWalkListenerRunner = ItemWalkListenerRunner.builder().itemWalkListener(listener).build(); + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder().propertyWalkListener(listener).build(); + WalkConfig walkConfig = WalkConfig.builder() + .itemWalkListenerRunner(itemWalkListenerRunner) + .propertyWalkListenerRunner(propertyWalkListenerRunner) + .build(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12).getSchema(schemaData); + + Result result = schema.walk(null, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + List items = result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(4, items.size()); + assertEquals("/name", items.get(0).getInstanceLocation().toString()); + assertEquals("properties", items.get(0).getKeyword()); + assertEquals("#/properties/name", items.get(0).getSchema().getSchemaLocation().toString()); + assertEquals("/children/0/name", items.get(1).getInstanceLocation().toString()); + assertEquals("properties", items.get(1).getKeyword()); + assertEquals("#/properties/children/items/properties/name", items.get(1).getSchema().getSchemaLocation().toString()); + assertEquals("/children/0", items.get(2).getInstanceLocation().toString()); + assertEquals("items", items.get(2).getKeyword()); + assertEquals("#/properties/children/items", items.get(2).getSchema().getSchemaLocation().toString()); + assertEquals("/children", items.get(3).getInstanceLocation().toString()); + assertEquals("properties", items.get(3).getKeyword()); + assertEquals("#/properties/children", items.get(3).getSchema().getSchemaLocation().toString()); + } + +} diff --git a/src/test/resources/benchmark/basic/perftest.json b/src/test/resources/benchmark/basic/perftest.json new file mode 100644 index 000000000..487238ac9 --- /dev/null +++ b/src/test/resources/benchmark/basic/perftest.json @@ -0,0 +1,2009 @@ +{ + "kind": "discovery#restDescription", + "etag": "\"ye6orv2F-1npMW3u9suM3a7C5Bo/q8F4VhSI6PKWBTjcScStS6NHBic\"", + "discoveryVersion": "v1", + "id": "plus:v1", + "name": "plus", + "version": "v1", + "revision": "20151202", + "title": "Google+ API", + "description": "The Google+ API enables developers to build on top of the Google+ platform.", + "ownerDomain": "google.com", + "ownerName": "Google", + "icons": { + "x16": "http://www.google.com/images/icons/product/gplus-16.png", + "x32": "http://www.google.com/images/icons/product/gplus-32.png" + }, + "documentationLink": "https://developers.google.com/+/api/", + "protocol": "rest", + "baseUrl": "https://www.googleapis.com/plus/v1/", + "basePath": "/plus/v1/", + "rootUrl": "https://www.googleapis.com/", + "servicePath": "plus/v1/", + "batchPath": "batch", + "parameters": { + "alt": { + "type": "string", + "description": "Data format for the response.", + "default": "json", + "enum": [ + "json" + ], + "enumDescriptions": [ + "Responses with Content-Type of application/json" + ], + "location": "query" + }, + "fields": { + "type": "string", + "description": "Selector specifying which fields to include in a partial response.", + "location": "query" + }, + "key": { + "type": "string", + "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", + "location": "query" + }, + "oauth_token": { + "type": "string", + "description": "OAuth 2.0 token for the current user.", + "location": "query" + }, + "prettyPrint": { + "type": "boolean", + "description": "Returns response with indentations and line breaks.", + "default": "true", + "location": "query" + }, + "quotaUser": { + "type": "string", + "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", + "location": "query" + }, + "userIp": { + "type": "string", + "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", + "location": "query" + } + }, + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/plus.login": { + "description": "Know your basic profile info and list of people in your circles." + }, + "https://www.googleapis.com/auth/plus.me": { + "description": "Know who you are on Google" + }, + "https://www.googleapis.com/auth/userinfo.email": { + "description": "View your email address" + }, + "https://www.googleapis.com/auth/userinfo.profile": { + "description": "View your basic profile info" + } + } + } + }, + "schemas": { + "Acl": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Description of the access granted, suitable for display." + }, + "items": { + "type": "array", + "description": "The list of access entries.", + "items": { + "$ref": "PlusAclentryResource" + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a collection of access controls. Value: \"plus#acl\".", + "default": "plus#acl" + } + } + }, + "Activity": { + "type": "object", + "properties": { + "access": { + "$ref": "Acl", + "description": "Identifies who has access to see this activity." + }, + "actor": { + "type": "object", + "description": "The person who performed this activity.", + "properties": { + "clientSpecificActorInfo": { + "type": "object", + "description": "Actor info specific to particular clients.", + "properties": { + "youtubeActorInfo": { + "type": "object", + "description": "Actor info specific to YouTube clients.", + "properties": { + "channelId": { + "type": "string", + "description": "ID of the YouTube channel owned by the Actor." + } + } + } + } + }, + "displayName": { + "type": "string", + "description": "The name of the actor, suitable for display." + }, + "id": { + "type": "string", + "description": "The ID of the actor's Person resource." + }, + "image": { + "type": "object", + "description": "The image representation of the actor.", + "properties": { + "url": { + "type": "string", + "description": "The URL of the actor's profile photo. To resize the image and crop it to a square, append the query string ?sz=x, where x is the dimension in pixels of each side." + } + } + }, + "name": { + "type": "object", + "description": "An object representation of the individual components of name.", + "properties": { + "familyName": { + "type": "string", + "description": "The family name (\"last name\") of the actor." + }, + "givenName": { + "type": "string", + "description": "The given name (\"first name\") of the actor." + } + } + }, + "url": { + "type": "string", + "description": "The link to the actor's Google profile." + }, + "verification": { + "type": "object", + "description": "Verification status of actor.", + "properties": { + "adHocVerified": { + "type": "string", + "description": "Verification for one-time or manual processes." + } + } + } + } + }, + "address": { + "type": "string", + "description": "Street address where this activity occurred." + }, + "annotation": { + "type": "string", + "description": "Additional content added by the person who shared this activity, applicable only when resharing an activity." + }, + "crosspostSource": { + "type": "string", + "description": "If this activity is a crosspost from another system, this property specifies the ID of the original activity." + }, + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "geocode": { + "type": "string", + "description": "Latitude and longitude where this activity occurred. Format is latitude followed by longitude, space separated." + }, + "id": { + "type": "string", + "description": "The ID of this activity." + }, + "kind": { + "type": "string", + "description": "Identifies this resource as an activity. Value: \"plus#activity\".", + "default": "plus#activity" + }, + "location": { + "$ref": "Place", + "description": "The location where this activity occurred." + }, + "object": { + "type": "object", + "description": "The object of this activity.", + "properties": { + "actor": { + "type": "object", + "description": "If this activity's object is itself another activity, such as when a person reshares an activity, this property specifies the original activity's actor.", + "properties": { + "clientSpecificActorInfo": { + "type": "object", + "description": "Actor info specific to particular clients.", + "properties": { + "youtubeActorInfo": { + "type": "object", + "description": "Actor info specific to YouTube clients.", + "properties": { + "channelId": { + "type": "string", + "description": "ID of the YouTube channel owned by the Actor." + } + } + } + } + }, + "displayName": { + "type": "string", + "description": "The original actor's name, which is suitable for display." + }, + "id": { + "type": "string", + "description": "ID of the original actor." + }, + "image": { + "type": "object", + "description": "The image representation of the original actor.", + "properties": { + "url": { + "type": "string", + "description": "A URL that points to a thumbnail photo of the original actor." + } + } + }, + "url": { + "type": "string", + "description": "A link to the original actor's Google profile." + }, + "verification": { + "type": "object", + "description": "Verification status of actor.", + "properties": { + "adHocVerified": { + "type": "string", + "description": "Verification for one-time or manual processes." + } + } + } + } + }, + "attachments": { + "type": "array", + "description": "The media objects attached to this activity.", + "items": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "If the attachment is an article, this property contains a snippet of text from the article. It can also include descriptions for other types." + }, + "displayName": { + "type": "string", + "description": "The title of the attachment, such as a photo caption or an article title." + }, + "embed": { + "type": "object", + "description": "If the attachment is a video, the embeddable link.", + "properties": { + "type": { + "type": "string", + "description": "Media type of the link." + }, + "url": { + "type": "string", + "description": "URL of the link." + } + } + }, + "fullImage": { + "type": "object", + "description": "The full image URL for photo attachments.", + "properties": { + "height": { + "type": "integer", + "description": "The height, in pixels, of the linked resource.", + "format": "uint32" + }, + "type": { + "type": "string", + "description": "Media type of the link." + }, + "url": { + "type": "string", + "description": "URL of the image." + }, + "width": { + "type": "integer", + "description": "The width, in pixels, of the linked resource.", + "format": "uint32" + } + } + }, + "id": { + "type": "string", + "description": "The ID of the attachment." + }, + "image": { + "type": "object", + "description": "The preview image for photos or videos.", + "properties": { + "height": { + "type": "integer", + "description": "The height, in pixels, of the linked resource.", + "format": "uint32" + }, + "type": { + "type": "string", + "description": "Media type of the link." + }, + "url": { + "type": "string", + "description": "Image URL." + }, + "width": { + "type": "integer", + "description": "The width, in pixels, of the linked resource.", + "format": "uint32" + } + } + }, + "objectType": { + "type": "string", + "description": "The type of media object. Possible values include, but are not limited to, the following values: \n- \"photo\" - A photo. \n- \"album\" - A photo album. \n- \"video\" - A video. \n- \"article\" - An article, specified by a link." + }, + "thumbnails": { + "type": "array", + "description": "If the attachment is an album, this property is a list of potential additional thumbnails from the album.", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Potential name of the thumbnail." + }, + "image": { + "type": "object", + "description": "Image resource.", + "properties": { + "height": { + "type": "integer", + "description": "The height, in pixels, of the linked resource.", + "format": "uint32" + }, + "type": { + "type": "string", + "description": "Media type of the link." + }, + "url": { + "type": "string", + "description": "Image url." + }, + "width": { + "type": "integer", + "description": "The width, in pixels, of the linked resource.", + "format": "uint32" + } + } + }, + "url": { + "type": "string", + "description": "URL of the webpage containing the image." + } + } + } + }, + "url": { + "type": "string", + "description": "The link to the attachment, which should be of type text/html." + } + } + } + }, + "content": { + "type": "string", + "description": "The HTML-formatted content, which is suitable for display." + }, + "id": { + "type": "string", + "description": "The ID of the object. When resharing an activity, this is the ID of the activity that is being reshared." + }, + "objectType": { + "type": "string", + "description": "The type of the object. Possible values include, but are not limited to, the following values: \n- \"note\" - Textual content. \n- \"activity\" - A Google+ activity." + }, + "originalContent": { + "type": "string", + "description": "The content (text) as provided by the author, which is stored without any HTML formatting. When creating or updating an activity, this value must be supplied as plain text in the request." + }, + "plusoners": { + "type": "object", + "description": "People who +1'd this activity.", + "properties": { + "selfLink": { + "type": "string", + "description": "The URL for the collection of people who +1'd this activity." + }, + "totalItems": { + "type": "integer", + "description": "Total number of people who +1'd this activity.", + "format": "uint32" + } + } + }, + "replies": { + "type": "object", + "description": "Comments in reply to this activity.", + "properties": { + "selfLink": { + "type": "string", + "description": "The URL for the collection of comments in reply to this activity." + }, + "totalItems": { + "type": "integer", + "description": "Total number of comments on this activity.", + "format": "uint32" + } + } + }, + "resharers": { + "type": "object", + "description": "People who reshared this activity.", + "properties": { + "selfLink": { + "type": "string", + "description": "The URL for the collection of resharers." + }, + "totalItems": { + "type": "integer", + "description": "Total number of people who reshared this activity.", + "format": "uint32" + } + } + }, + "url": { + "type": "string", + "description": "The URL that points to the linked resource." + } + } + }, + "placeId": { + "type": "string", + "description": "ID of the place where this activity occurred." + }, + "placeName": { + "type": "string", + "description": "Name of the place where this activity occurred." + }, + "provider": { + "type": "object", + "description": "The service provider that initially published this activity.", + "properties": { + "title": { + "type": "string", + "description": "Name of the service provider." + } + } + }, + "published": { + "type": "string", + "description": "The time at which this activity was initially published. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + }, + "radius": { + "type": "string", + "description": "Radius, in meters, of the region where this activity occurred, centered at the latitude and longitude identified in geocode." + }, + "title": { + "type": "string", + "description": "Title of this activity." + }, + "updated": { + "type": "string", + "description": "The time at which this activity was last updated. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + }, + "url": { + "type": "string", + "description": "The link to this activity." + }, + "verb": { + "type": "string", + "description": "This activity's verb, which indicates the action that was performed. Possible values include, but are not limited to, the following values: \n- \"post\" - Publish content to the stream. \n- \"share\" - Reshare an activity." + } + } + }, + "ActivityFeed": { + "type": "object", + "properties": { + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "id": { + "type": "string", + "description": "The ID of this collection of activities. Deprecated." + }, + "items": { + "type": "array", + "description": "The activities in this page of results.", + "items": { + "$ref": "Activity" + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a collection of activities. Value: \"plus#activityFeed\".", + "default": "plus#activityFeed" + }, + "nextLink": { + "type": "string", + "description": "Link to the next page of activities." + }, + "nextPageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. Provide this value in a subsequent request to return the next page of results." + }, + "selfLink": { + "type": "string", + "description": "Link to this activity resource." + }, + "title": { + "type": "string", + "description": "The title of this collection of activities, which is a truncated portion of the content." + }, + "updated": { + "type": "string", + "description": "The time at which this collection of activities was last updated. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + } + } + }, + "Comment": { + "type": "object", + "properties": { + "actor": { + "type": "object", + "description": "The person who posted this comment.", + "properties": { + "clientSpecificActorInfo": { + "type": "object", + "description": "Actor info specific to particular clients.", + "properties": { + "youtubeActorInfo": { + "type": "object", + "description": "Actor info specific to YouTube clients.", + "properties": { + "channelId": { + "type": "string", + "description": "ID of the YouTube channel owned by the Actor." + } + } + } + } + }, + "displayName": { + "type": "string", + "description": "The name of this actor, suitable for display." + }, + "id": { + "type": "string", + "description": "The ID of the actor." + }, + "image": { + "type": "object", + "description": "The image representation of this actor.", + "properties": { + "url": { + "type": "string", + "description": "The URL of the actor's profile photo. To resize the image and crop it to a square, append the query string ?sz=x, where x is the dimension in pixels of each side." + } + } + }, + "url": { + "type": "string", + "description": "A link to the Person resource for this actor." + }, + "verification": { + "type": "object", + "description": "Verification status of actor.", + "properties": { + "adHocVerified": { + "type": "string", + "description": "Verification for one-time or manual processes." + } + } + } + } + }, + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "id": { + "type": "string", + "description": "The ID of this comment." + }, + "inReplyTo": { + "type": "array", + "description": "The activity this comment replied to.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the activity." + }, + "url": { + "type": "string", + "description": "The URL of the activity." + } + } + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a comment. Value: \"plus#comment\".", + "default": "plus#comment" + }, + "object": { + "type": "object", + "description": "The object of this comment.", + "properties": { + "content": { + "type": "string", + "description": "The HTML-formatted content, suitable for display." + }, + "objectType": { + "type": "string", + "description": "The object type of this comment. Possible values are: \n- \"comment\" - A comment in reply to an activity.", + "default": "comment" + }, + "originalContent": { + "type": "string", + "description": "The content (text) as provided by the author, stored without any HTML formatting. When creating or updating a comment, this value must be supplied as plain text in the request." + } + } + }, + "plusoners": { + "type": "object", + "description": "People who +1'd this comment.", + "properties": { + "totalItems": { + "type": "integer", + "description": "Total number of people who +1'd this comment.", + "format": "uint32" + } + } + }, + "published": { + "type": "string", + "description": "The time at which this comment was initially published. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + }, + "selfLink": { + "type": "string", + "description": "Link to this comment resource." + }, + "updated": { + "type": "string", + "description": "The time at which this comment was last updated. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + }, + "verb": { + "type": "string", + "description": "This comment's verb, indicating what action was performed. Possible values are: \n- \"post\" - Publish content to the stream.", + "default": "post" + } + } + }, + "CommentFeed": { + "type": "object", + "properties": { + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "id": { + "type": "string", + "description": "The ID of this collection of comments." + }, + "items": { + "type": "array", + "description": "The comments in this page of results.", + "items": { + "$ref": "Comment" + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a collection of comments. Value: \"plus#commentFeed\".", + "default": "plus#commentFeed" + }, + "nextLink": { + "type": "string", + "description": "Link to the next page of activities." + }, + "nextPageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. Provide this value in a subsequent request to return the next page of results." + }, + "title": { + "type": "string", + "description": "The title of this collection of comments." + }, + "updated": { + "type": "string", + "description": "The time at which this collection of comments was last updated. Formatted as an RFC 3339 timestamp.", + "format": "date-time" + } + } + }, + "ItemScope": { + "type": "object", + "properties": { + "about": { + "$ref": "ItemScope", + "description": "The subject matter of the content." + }, + "additionalName": { + "type": "array", + "description": "An additional name for a Person, can be used for a middle name.", + "items": { + "type": "string" + } + }, + "address": { + "$ref": "ItemScope", + "description": "Postal address." + }, + "addressCountry": { + "type": "string", + "description": "Address country." + }, + "addressLocality": { + "type": "string", + "description": "Address locality." + }, + "addressRegion": { + "type": "string", + "description": "Address region." + }, + "associated_media": { + "type": "array", + "description": "The encoding.", + "items": { + "$ref": "ItemScope" + } + }, + "attendeeCount": { + "type": "integer", + "description": "Number of attendees.", + "format": "int32" + }, + "attendees": { + "type": "array", + "description": "A person attending the event.", + "items": { + "$ref": "ItemScope" + } + }, + "audio": { + "$ref": "ItemScope", + "description": "From http://schema.org/MusicRecording, the audio file." + }, + "author": { + "type": "array", + "description": "The person or persons who created this result. In the example of restaurant reviews, this might be the reviewer's name.", + "items": { + "$ref": "ItemScope" + } + }, + "bestRating": { + "type": "string", + "description": "Best possible rating value that a result might obtain. This property defines the upper bound for the ratingValue. For example, you might have a 5 star rating scale, you would provide 5 as the value for this property." + }, + "birthDate": { + "type": "string", + "description": "Date of birth." + }, + "byArtist": { + "$ref": "ItemScope", + "description": "From http://schema.org/MusicRecording, the artist that performed this recording." + }, + "caption": { + "type": "string", + "description": "The caption for this object." + }, + "contentSize": { + "type": "string", + "description": "File size in (mega/kilo) bytes." + }, + "contentUrl": { + "type": "string", + "description": "Actual bytes of the media object, for example the image file or video file." + }, + "contributor": { + "type": "array", + "description": "A list of contributors to this result.", + "items": { + "$ref": "ItemScope" + } + }, + "dateCreated": { + "type": "string", + "description": "The date the result was created such as the date that a review was first created." + }, + "dateModified": { + "type": "string", + "description": "The date the result was last modified such as the date that a review was last edited." + }, + "datePublished": { + "type": "string", + "description": "The initial date that the result was published. For example, a user writes a comment on a blog, which has a result.dateCreated of when they submit it. If the blog users comment moderation, the result.datePublished value would match the date when the owner approved the message." + }, + "description": { + "type": "string", + "description": "The string that describes the content of the result." + }, + "duration": { + "type": "string", + "description": "The duration of the item (movie, audio recording, event, etc.) in ISO 8601 date format." + }, + "embedUrl": { + "type": "string", + "description": "A URL pointing to a player for a specific video. In general, this is the information in the src element of an embed tag and should not be the same as the content of the loc tag." + }, + "endDate": { + "type": "string", + "description": "The end date and time of the event (in ISO 8601 date format)." + }, + "familyName": { + "type": "string", + "description": "Family name. This property can be used with givenName instead of the name property." + }, + "gender": { + "type": "string", + "description": "Gender of the person." + }, + "geo": { + "$ref": "ItemScope", + "description": "Geo coordinates." + }, + "givenName": { + "type": "string", + "description": "Given name. This property can be used with familyName instead of the name property." + }, + "height": { + "type": "string", + "description": "The height of the media object." + }, + "id": { + "type": "string", + "description": "An identifier for the object. Your app can choose how to identify objects. The object.id is required if you are writing an action that does not have a corresponding web page or object.url property." + }, + "image": { + "type": "string", + "description": "A URL to the image that represents this result. For example, if a user writes a review of a restaurant and attaches a photo of their meal, you might use that photo as the result.image." + }, + "inAlbum": { + "$ref": "ItemScope", + "description": "From http://schema.org/MusicRecording, which album a song is in." + }, + "kind": { + "type": "string", + "description": "Identifies this resource as an itemScope.", + "default": "plus#itemScope" + }, + "latitude": { + "type": "number", + "description": "Latitude.", + "format": "double" + }, + "location": { + "$ref": "ItemScope", + "description": "The location of the event or organization." + }, + "longitude": { + "type": "number", + "description": "Longitude.", + "format": "double" + }, + "name": { + "type": "string", + "description": "The name of the result. In the example of a restaurant review, this might be the summary the user gave their review such as \"Great ambiance, but overpriced.\"" + }, + "partOfTVSeries": { + "$ref": "ItemScope", + "description": "Property of http://schema.org/TVEpisode indicating which series the episode belongs to." + }, + "performers": { + "type": "array", + "description": "The main performer or performers of the event-for example, a presenter, musician, or actor.", + "items": { + "$ref": "ItemScope" + } + }, + "playerType": { + "type": "string", + "description": "Player type that is required. For example: Flash or Silverlight." + }, + "postOfficeBoxNumber": { + "type": "string", + "description": "Post office box number." + }, + "postalCode": { + "type": "string", + "description": "Postal code." + }, + "ratingValue": { + "type": "string", + "description": "Rating value." + }, + "reviewRating": { + "$ref": "ItemScope", + "description": "Review rating." + }, + "startDate": { + "type": "string", + "description": "The start date and time of the event (in ISO 8601 date format)." + }, + "streetAddress": { + "type": "string", + "description": "Street address." + }, + "text": { + "type": "string", + "description": "The text that is the result of the app activity. For example, if a user leaves a review of a restaurant, this might be the text of the review." + }, + "thumbnail": { + "$ref": "ItemScope", + "description": "Thumbnail image for an image or video." + }, + "thumbnailUrl": { + "type": "string", + "description": "A URL to a thumbnail image that represents this result." + }, + "tickerSymbol": { + "type": "string", + "description": "The exchange traded instrument associated with a Corporation object. The tickerSymbol is expressed as an exchange and an instrument name separated by a space character. For the exchange component of the tickerSymbol attribute, we recommend using the controlled vocabulary of Market Identifier Codes (MIC) specified in ISO15022." + }, + "type": { + "type": "string", + "description": "The schema.org URL that best describes the referenced object and matches the type of moment." + }, + "url": { + "type": "string", + "description": "The URL that points to the result object. For example, a permalink directly to a restaurant reviewer's comment." + }, + "width": { + "type": "string", + "description": "The width of the media object." + }, + "worstRating": { + "type": "string", + "description": "Worst possible rating value that a result might obtain. This property defines the lower bound for the ratingValue." + } + } + }, + "Moment": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The moment ID." + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a moment.", + "default": "plus#moment" + }, + "object": { + "$ref": "ItemScope", + "description": "The object on which the action was performed. Specifying this is equivalent with specifying \"target\". Note that responses from the server will use the \"target\" field instead for backward-compatibility with older clients.", + "annotations": { + "required": [ + "plus.moments.insert" + ] + } + }, + "result": { + "$ref": "ItemScope", + "description": "The object generated by performing the action on the object. For example, a user writes a review of a restaurant, the object is the restaurant and the result is the review." + }, + "startDate": { + "type": "string", + "description": "Time stamp of when the action occurred in RFC3339 format.", + "format": "date-time" + }, + "target": { + "$ref": "ItemScope", + "description": "The object on which the action was performed.", + "annotations": { + "required": [ + "plus.moments.insert" + ] + } + }, + "type": { + "type": "string", + "description": "The schema.org type for the type of moment to write. For example, http://schema.org/AddAction. Note that responses from the server will use the Google schema type instead for backward-compatibility with older clients. For example, http://schemas.google.com/AddActivity.", + "annotations": { + "required": [ + "plus.moments.insert" + ] + } + } + } + }, + "MomentsFeed": { + "type": "object", + "properties": { + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "items": { + "type": "array", + "description": "The moments in this page of results.", + "items": { + "$ref": "Moment" + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a collection of moments. Value: \"plus#momentsFeed\".", + "default": "plus#momentsFeed" + }, + "nextLink": { + "type": "string", + "description": "Link to the next page of moments." + }, + "nextPageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. Provide this value in a subsequent request to return the next page of results." + }, + "selfLink": { + "type": "string", + "description": "Link to this page of moments." + }, + "title": { + "type": "string", + "description": "The title of this collection of moments." + }, + "updated": { + "type": "string", + "description": "The RFC 339 timestamp for when this collection of moments was last updated.", + "format": "date-time" + } + } + }, + "PeopleFeed": { + "type": "object", + "properties": { + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "items": { + "type": "array", + "description": "The people in this page of results. Each item includes the id, displayName, image, and url for the person. To retrieve additional profile data, see the people.get method.", + "items": { + "$ref": "Person" + } + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a collection of people. Value: \"plus#peopleFeed\".", + "default": "plus#peopleFeed" + }, + "nextPageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. Provide this value in a subsequent request to return the next page of results." + }, + "selfLink": { + "type": "string", + "description": "Link to this resource." + }, + "title": { + "type": "string", + "description": "The title of this collection of people." + }, + "totalItems": { + "type": "integer", + "description": "The total number of people available in this list. The number of people in a response might be smaller due to paging. This might not be set for all collections.", + "format": "int32" + } + } + }, + "Person": { + "type": "object", + "properties": { + "aboutMe": { + "type": "string", + "description": "A short biography for this person." + }, + "ageRange": { + "type": "object", + "description": "The age range of the person. Valid ranges are 17 or younger, 18 to 20, and 21 or older. Age is determined from the user's birthday using Western age reckoning.", + "properties": { + "max": { + "type": "integer", + "description": "The age range's upper bound, if any. Possible values include, but are not limited to, the following: \n- \"17\" - for age 17 \n- \"20\" - for age 20", + "format": "int32" + }, + "min": { + "type": "integer", + "description": "The age range's lower bound, if any. Possible values include, but are not limited to, the following: \n- \"21\" - for age 21 \n- \"18\" - for age 18", + "format": "int32" + } + } + }, + "birthday": { + "type": "string", + "description": "The person's date of birth, represented as YYYY-MM-DD." + }, + "braggingRights": { + "type": "string", + "description": "The \"bragging rights\" line of this person." + }, + "circledByCount": { + "type": "integer", + "description": "For followers who are visible, the number of people who have added this person or page to a circle.", + "format": "int32" + }, + "cover": { + "type": "object", + "description": "The cover photo content.", + "properties": { + "coverInfo": { + "type": "object", + "description": "Extra information about the cover photo.", + "properties": { + "leftImageOffset": { + "type": "integer", + "description": "The difference between the left position of the cover image and the actual displayed cover image. Only valid for banner layout.", + "format": "int32" + }, + "topImageOffset": { + "type": "integer", + "description": "The difference between the top position of the cover image and the actual displayed cover image. Only valid for banner layout.", + "format": "int32" + } + } + }, + "coverPhoto": { + "type": "object", + "description": "The person's primary cover image.", + "properties": { + "height": { + "type": "integer", + "description": "The height of the image.", + "format": "int32" + }, + "url": { + "type": "string", + "description": "The URL of the image." + }, + "width": { + "type": "integer", + "description": "The width of the image.", + "format": "int32" + } + } + }, + "layout": { + "type": "string", + "description": "The layout of the cover art. Possible values include, but are not limited to, the following values: \n- \"banner\" - One large image banner." + } + } + }, + "currentLocation": { + "type": "string", + "description": "(this field is not currently used)" + }, + "displayName": { + "type": "string", + "description": "The name of this person, which is suitable for display." + }, + "domain": { + "type": "string", + "description": "The hosted domain name for the user's Google Apps account. For instance, example.com. The plus.profile.emails.read or email scope is needed to get this domain name." + }, + "emails": { + "type": "array", + "description": "A list of email addresses that this person has, including their Google account email address, and the public verified email addresses on their Google+ profile. The plus.profile.emails.read scope is needed to retrieve these email addresses, or the email scope can be used to retrieve just the Google account email address.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of address. Possible values include, but are not limited to, the following values: \n- \"account\" - Google account email address. \n- \"home\" - Home email address. \n- \"work\" - Work email address. \n- \"other\" - Other." + }, + "value": { + "type": "string", + "description": "The email address." + } + } + } + }, + "etag": { + "type": "string", + "description": "ETag of this response for caching purposes." + }, + "gender": { + "type": "string", + "description": "The person's gender. Possible values include, but are not limited to, the following values: \n- \"male\" - Male gender. \n- \"female\" - Female gender. \n- \"other\" - Other." + }, + "id": { + "type": "string", + "description": "The ID of this person." + }, + "image": { + "type": "object", + "description": "The representation of the person's profile photo.", + "properties": { + "isDefault": { + "type": "boolean", + "description": "Whether the person's profile photo is the default one" + }, + "url": { + "type": "string", + "description": "The URL of the person's profile photo. To resize the image and crop it to a square, append the query string ?sz=x, where x is the dimension in pixels of each side." + } + } + }, + "isPlusUser": { + "type": "boolean", + "description": "Whether this user has signed up for Google+." + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a person. Value: \"plus#person\".", + "default": "plus#person" + }, + "language": { + "type": "string", + "description": "The user's preferred language for rendering." + }, + "name": { + "type": "object", + "description": "An object representation of the individual components of a person's name.", + "properties": { + "familyName": { + "type": "string", + "description": "The family name (last name) of this person." + }, + "formatted": { + "type": "string", + "description": "The full name of this person, including middle names, suffixes, etc." + }, + "givenName": { + "type": "string", + "description": "The given name (first name) of this person." + }, + "honorificPrefix": { + "type": "string", + "description": "The honorific prefixes (such as \"Dr.\" or \"Mrs.\") for this person." + }, + "honorificSuffix": { + "type": "string", + "description": "The honorific suffixes (such as \"Jr.\") for this person." + }, + "middleName": { + "type": "string", + "description": "The middle name of this person." + } + } + }, + "nickname": { + "type": "string", + "description": "The nickname of this person." + }, + "objectType": { + "type": "string", + "description": "Type of person within Google+. Possible values include, but are not limited to, the following values: \n- \"person\" - represents an actual person. \n- \"page\" - represents a page." + }, + "occupation": { + "type": "string", + "description": "The occupation of this person." + }, + "organizations": { + "type": "array", + "description": "A list of current or past organizations with which this person is associated.", + "items": { + "type": "object", + "properties": { + "department": { + "type": "string", + "description": "The department within the organization. Deprecated." + }, + "description": { + "type": "string", + "description": "A short description of the person's role in this organization. Deprecated." + }, + "endDate": { + "type": "string", + "description": "The date that the person left this organization." + }, + "location": { + "type": "string", + "description": "The location of this organization. Deprecated." + }, + "name": { + "type": "string", + "description": "The name of the organization." + }, + "primary": { + "type": "boolean", + "description": "If \"true\", indicates this organization is the person's primary one, which is typically interpreted as the current one." + }, + "startDate": { + "type": "string", + "description": "The date that the person joined this organization." + }, + "title": { + "type": "string", + "description": "The person's job title or role within the organization." + }, + "type": { + "type": "string", + "description": "The type of organization. Possible values include, but are not limited to, the following values: \n- \"work\" - Work. \n- \"school\" - School." + } + } + } + }, + "placesLived": { + "type": "array", + "description": "A list of places where this person has lived.", + "items": { + "type": "object", + "properties": { + "primary": { + "type": "boolean", + "description": "If \"true\", this place of residence is this person's primary residence." + }, + "value": { + "type": "string", + "description": "A place where this person has lived. For example: \"Seattle, WA\", \"Near Toronto\"." + } + } + } + }, + "plusOneCount": { + "type": "integer", + "description": "If a Google+ Page, the number of people who have +1'd this page.", + "format": "int32" + }, + "relationshipStatus": { + "type": "string", + "description": "The person's relationship status. Possible values include, but are not limited to, the following values: \n- \"single\" - Person is single. \n- \"in_a_relationship\" - Person is in a relationship. \n- \"engaged\" - Person is engaged. \n- \"married\" - Person is married. \n- \"its_complicated\" - The relationship is complicated. \n- \"open_relationship\" - Person is in an open relationship. \n- \"widowed\" - Person is widowed. \n- \"in_domestic_partnership\" - Person is in a domestic partnership. \n- \"in_civil_union\" - Person is in a civil union." + }, + "skills": { + "type": "string", + "description": "The person's skills." + }, + "tagline": { + "type": "string", + "description": "The brief description (tagline) of this person." + }, + "url": { + "type": "string", + "description": "The URL of this person's profile." + }, + "urls": { + "type": "array", + "description": "A list of URLs for this person.", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "The label of the URL." + }, + "type": { + "type": "string", + "description": "The type of URL. Possible values include, but are not limited to, the following values: \n- \"otherProfile\" - URL for another profile. \n- \"contributor\" - URL to a site for which this person is a contributor. \n- \"website\" - URL for this Google+ Page's primary website. \n- \"other\" - Other URL." + }, + "value": { + "type": "string", + "description": "The URL value." + } + } + } + }, + "verified": { + "type": "boolean", + "description": "Whether the person or Google+ Page has been verified." + } + } + }, + "Place": { + "type": "object", + "properties": { + "address": { + "type": "object", + "description": "The physical address of the place.", + "properties": { + "formatted": { + "type": "string", + "description": "The formatted address for display." + } + } + }, + "displayName": { + "type": "string", + "description": "The display name of the place." + }, + "id": { + "type": "string", + "description": "The id of the place." + }, + "kind": { + "type": "string", + "description": "Identifies this resource as a place. Value: \"plus#place\".", + "default": "plus#place" + }, + "position": { + "type": "object", + "description": "The position of the place.", + "properties": { + "latitude": { + "type": "number", + "description": "The latitude of this position.", + "format": "double" + }, + "longitude": { + "type": "number", + "description": "The longitude of this position.", + "format": "double" + } + } + } + } + }, + "PlusAclentryResource": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "A descriptive name for this entry. Suitable for display." + }, + "id": { + "type": "string", + "description": "The ID of the entry. For entries of type \"person\" or \"circle\", this is the ID of the resource. For other types, this property is not set." + }, + "type": { + "type": "string", + "description": "The type of entry describing to whom access is granted. Possible values are: \n- \"person\" - Access to an individual. \n- \"circle\" - Access to members of a circle. \n- \"myCircles\" - Access to members of all the person's circles. \n- \"extendedCircles\" - Access to members of all the person's circles, plus all of the people in their circles. \n- \"domain\" - Access to members of the person's Google Apps domain. \n- \"public\" - Access to anyone on the web." + } + } + } + }, + "resources": { + "activities": { + "methods": { + "get": { + "id": "plus.activities.get", + "path": "activities/{activityId}", + "httpMethod": "GET", + "description": "Get an activity.", + "parameters": { + "activityId": { + "type": "string", + "description": "The ID of the activity to get.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "activityId" + ], + "response": { + "$ref": "Activity" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "list": { + "id": "plus.activities.list", + "path": "people/{userId}/activities/{collection}", + "httpMethod": "GET", + "description": "List all of the activities in the specified collection for a particular user.", + "parameters": { + "collection": { + "type": "string", + "description": "The collection of activities to list.", + "required": true, + "enum": [ + "public" + ], + "enumDescriptions": [ + "All public activities created by the specified user." + ], + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of activities to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "20", + "format": "uint32", + "minimum": "1", + "maximum": "100", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", + "location": "query" + }, + "userId": { + "type": "string", + "description": "The ID of the user to get activities for. The special value \"me\" can be used to indicate the authenticated user.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "userId", + "collection" + ], + "response": { + "$ref": "ActivityFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "search": { + "id": "plus.activities.search", + "path": "activities", + "httpMethod": "GET", + "description": "Search public activities.", + "parameters": { + "language": { + "type": "string", + "description": "Specify the preferred language to search with. See search language codes for available values.", + "default": "en-US", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of activities to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "10", + "format": "uint32", + "minimum": "1", + "maximum": "20", + "location": "query" + }, + "orderBy": { + "type": "string", + "description": "Specifies how to order search results.", + "default": "recent", + "enum": [ + "best", + "recent" + ], + "enumDescriptions": [ + "Sort activities by relevance to the user, most relevant first.", + "Sort activities by published date, most recent first." + ], + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response. This token can be of any length.", + "location": "query" + }, + "query": { + "type": "string", + "description": "Full-text search query string.", + "required": true, + "location": "query" + } + }, + "parameterOrder": [ + "query" + ], + "response": { + "$ref": "ActivityFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + } + } + }, + "comments": { + "methods": { + "get": { + "id": "plus.comments.get", + "path": "comments/{commentId}", + "httpMethod": "GET", + "description": "Get a comment.", + "parameters": { + "commentId": { + "type": "string", + "description": "The ID of the comment to get.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "commentId" + ], + "response": { + "$ref": "Comment" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "list": { + "id": "plus.comments.list", + "path": "activities/{activityId}/comments", + "httpMethod": "GET", + "description": "List all of the comments for an activity.", + "parameters": { + "activityId": { + "type": "string", + "description": "The ID of the activity to get comments for.", + "required": true, + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of comments to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "20", + "format": "uint32", + "minimum": "0", + "maximum": "500", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", + "location": "query" + }, + "sortOrder": { + "type": "string", + "description": "The order in which to sort the list of comments.", + "default": "ascending", + "enum": [ + "ascending", + "descending" + ], + "enumDescriptions": [ + "Sort oldest comments first.", + "Sort newest comments first." + ], + "location": "query" + } + }, + "parameterOrder": [ + "activityId" + ], + "response": { + "$ref": "CommentFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + } + } + }, + "moments": { + "methods": { + "insert": { + "id": "plus.moments.insert", + "path": "people/{userId}/moments/{collection}", + "httpMethod": "POST", + "description": "Record a moment representing a user's action such as making a purchase or commenting on a blog.", + "parameters": { + "collection": { + "type": "string", + "description": "The collection to which to write moments.", + "required": true, + "enum": [ + "vault" + ], + "enumDescriptions": [ + "The default collection for writing new moments." + ], + "location": "path" + }, + "debug": { + "type": "boolean", + "description": "Return the moment as written. Should be used only for debugging.", + "location": "query" + }, + "userId": { + "type": "string", + "description": "The ID of the user to record actions for. The only valid values are \"me\" and the ID of the authenticated user.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "userId", + "collection" + ], + "request": { + "$ref": "Moment" + }, + "response": { + "$ref": "Moment" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "list": { + "id": "plus.moments.list", + "path": "people/{userId}/moments/{collection}", + "httpMethod": "GET", + "description": "List all of the moments for a particular user.", + "parameters": { + "collection": { + "type": "string", + "description": "The collection of moments to list.", + "required": true, + "enum": [ + "vault" + ], + "enumDescriptions": [ + "All moments created by the requesting application for the authenticated user." + ], + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of moments to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "20", + "format": "uint32", + "minimum": "1", + "maximum": "100", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", + "location": "query" + }, + "targetUrl": { + "type": "string", + "description": "Only moments containing this targetUrl will be returned.", + "location": "query" + }, + "type": { + "type": "string", + "description": "Only moments of this type will be returned.", + "location": "query" + }, + "userId": { + "type": "string", + "description": "The ID of the user to get moments for. The special value \"me\" can be used to indicate the authenticated user.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "userId", + "collection" + ], + "response": { + "$ref": "MomentsFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + } + } + }, + "people": { + "methods": { + "get": { + "id": "plus.people.get", + "path": "people/{userId}", + "httpMethod": "GET", + "description": "Get a person's profile. If your app uses scope https://www.googleapis.com/auth/plus.login, this method is guaranteed to return ageRange and language.", + "parameters": { + "userId": { + "type": "string", + "description": "The ID of the person to get the profile for. The special value \"me\" can be used to indicate the authenticated user.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "userId" + ], + "response": { + "$ref": "Person" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile" + ] + }, + "list": { + "id": "plus.people.list", + "path": "people/{userId}/people/{collection}", + "httpMethod": "GET", + "description": "List all of the people in the specified collection.", + "parameters": { + "collection": { + "type": "string", + "description": "The collection of people to list.", + "required": true, + "enum": [ + "connected", + "visible" + ], + "enumDescriptions": [ + "The list of visible people in the authenticated user's circles who also use the requesting app. This list is limited to users who made their app activities visible to the authenticated user.", + "The list of people who this user has added to one or more circles, limited to the circles visible to the requesting application." + ], + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of people to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "100", + "format": "uint32", + "minimum": "1", + "maximum": "100", + "location": "query" + }, + "orderBy": { + "type": "string", + "description": "The order to return people in.", + "enum": [ + "alphabetical", + "best" + ], + "enumDescriptions": [ + "Order the people by their display name.", + "Order people based on the relevence to the viewer." + ], + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", + "location": "query" + }, + "userId": { + "type": "string", + "description": "Get the collection of people for the person identified. Use \"me\" to indicate the authenticated user.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "userId", + "collection" + ], + "response": { + "$ref": "PeopleFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "listByActivity": { + "id": "plus.people.listByActivity", + "path": "activities/{activityId}/people/{collection}", + "httpMethod": "GET", + "description": "List all of the people in the specified collection for a particular activity.", + "parameters": { + "activityId": { + "type": "string", + "description": "The ID of the activity to get the list of people for.", + "required": true, + "location": "path" + }, + "collection": { + "type": "string", + "description": "The collection of people to list.", + "required": true, + "enum": [ + "plusoners", + "resharers" + ], + "enumDescriptions": [ + "List all people who have +1'd this activity.", + "List all people who have reshared this activity." + ], + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of people to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "20", + "format": "uint32", + "minimum": "1", + "maximum": "100", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response.", + "location": "query" + } + }, + "parameterOrder": [ + "activityId", + "collection" + ], + "response": { + "$ref": "PeopleFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + }, + "search": { + "id": "plus.people.search", + "path": "people", + "httpMethod": "GET", + "description": "Search all public profiles.", + "parameters": { + "language": { + "type": "string", + "description": "Specify the preferred language to search with. See search language codes for available values.", + "default": "en-US", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of people to include in the response, which is used for paging. For any response, the actual number returned might be less than the specified maxResults.", + "default": "25", + "format": "uint32", + "minimum": "1", + "maximum": "50", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "The continuation token, which is used to page through large result sets. To get the next page of results, set this parameter to the value of \"nextPageToken\" from the previous response. This token can be of any length.", + "location": "query" + }, + "query": { + "type": "string", + "description": "Specify a query string for full text search of public text in all profiles.", + "required": true, + "location": "query" + } + }, + "parameterOrder": [ + "query" + ], + "response": { + "$ref": "PeopleFeed" + }, + "scopes": [ + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/plus.me" + ] + } + } + } + } +} diff --git a/src/test/resources/benchmark/basic/schema-draft4.json b/src/test/resources/benchmark/basic/schema-draft4.json new file mode 100644 index 000000000..4e32b57e1 --- /dev/null +++ b/src/test/resources/benchmark/basic/schema-draft4.json @@ -0,0 +1,150 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/src/test/resources/const-messages-override.properties b/src/test/resources/const-messages-override.properties new file mode 100644 index 000000000..896237ecd --- /dev/null +++ b/src/test/resources/const-messages-override.properties @@ -0,0 +1 @@ +const = must be the constant value ''{0}'' but is ''{1}'' \ No newline at end of file diff --git a/src/test/resources/data/AdditionalPropertiesOneOfFailsTest.json b/src/test/resources/data/AdditionalPropertiesOneOfFailsTest.json deleted file mode 100644 index c0a36ee44..000000000 --- a/src/test/resources/data/AdditionalPropertiesOneOfFailsTest.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "locationName": "factoryLocation", - "activities": [ - { - "activityType": "machine", - "age": "(additionalProperty not allowed)", - "height": 10.5 - }, - { - "activityType": "chemical", - "toxic": "(additionalProperty not allowed)", - "chemicalCharacteristic": { - "commonName": "methane", - "chemicalName": "CH4" - } - }, - { - "activityType": "chemical", - "toxic": "(additionalProperty not allowed)", - "chemicalCharacteristic": { - "name": "methane", - "categoryName": "gasses", - "chemicalName": "CH4" - } - } - ] -} \ No newline at end of file diff --git a/src/test/resources/data/OverwritingCustomMessageBug.json b/src/test/resources/data/OverwritingCustomMessageBug.json new file mode 100644 index 000000000..486605361 --- /dev/null +++ b/src/test/resources/data/OverwritingCustomMessageBug.json @@ -0,0 +1,19 @@ +{ + "toplevel": [ + { + "foos": "foo", + "Nope": "a", + "bars": "bar" + }, + { + "foos": "fee", + "Nope": "b", + "bars": "baz" + }, + { + "foos": "foo", + "Nope": "c", + "bars": "bar" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/contains/issue769/max-contains-v7.json b/src/test/resources/data/contains/issue769/max-contains-v7.json new file mode 100644 index 000000000..6baf24efe --- /dev/null +++ b/src/test/resources/data/contains/issue769/max-contains-v7.json @@ -0,0 +1,7 @@ +{ + "$schema": "/schema/contains/issue769/max-contains-v7.json", + "myArray": [ + {"itemType": "type A"}, + {"itemType": "type A"} + ] +} \ No newline at end of file diff --git a/src/test/resources/data/contains/issue769/max-contains.json b/src/test/resources/data/contains/issue769/max-contains.json new file mode 100644 index 000000000..630ddca77 --- /dev/null +++ b/src/test/resources/data/contains/issue769/max-contains.json @@ -0,0 +1,7 @@ +{ + "$schema": "/schema/contains/issue769/max-contains.json", + "myArray": [ + {"itemType": "type A"}, + {"itemType": "type A"} + ] +} \ No newline at end of file diff --git a/src/test/resources/data/contains/issue769/min-contains-v7.json b/src/test/resources/data/contains/issue769/min-contains-v7.json new file mode 100644 index 000000000..b57ce1eab --- /dev/null +++ b/src/test/resources/data/contains/issue769/min-contains-v7.json @@ -0,0 +1,4 @@ +{ + "$schema": "/schema/contains/issue769/min-contains-v7.json", + "myArray": [{"itemType": "type A"}] +} \ No newline at end of file diff --git a/src/test/resources/data/contains/issue769/min-contains.json b/src/test/resources/data/contains/issue769/min-contains.json new file mode 100644 index 000000000..c0b0f3083 --- /dev/null +++ b/src/test/resources/data/contains/issue769/min-contains.json @@ -0,0 +1,4 @@ +{ + "$schema": "/schema/contains/issue769/min-contains.json", + "myArray": [{"itemType": "type A"}] +} \ No newline at end of file diff --git a/src/test/resources/data/issue1091.json b/src/test/resources/data/issue1091.json new file mode 100644 index 000000000..ef0b6ae38 --- /dev/null +++ b/src/test/resources/data/issue1091.json @@ -0,0 +1,753 @@ +{ + "type": "doc", + "version": 1, + "content": [ + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + }, + { + "type": "hardBreak" + }, + { + "type": "text", + "text": "x" + }, + { + "type": "hardBreak" + }, + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "x" + }, + { + "type": "hardBreak" + }, + { + "type": "text", + "text": "x" + } + ] + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] + } + ], + "attrs": { + "order": 1 + } + } + ] +} diff --git a/src/test/resources/data/issue386.json b/src/test/resources/data/issue386.json deleted file mode 100644 index a70a95066..000000000 --- a/src/test/resources/data/issue386.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "valid": [ - { - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "country": "United States of America", - "postal_code": "20500" - } - }, - { - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "20500" - } - }, - { - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "K1M 1M4" - } - }, - { - "data": { - "street_address": "Adriaan Goekooplaan", - "country": "Netherlands", - "postal_code": "2517 JX" - } - } - ], - "invalid": [ - { - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "10000" - }, - "expectedErrors": ["$.postal_code: does not match the regex pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9]"] - }, - { - "description": "invalid through first then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "K1M 1M4" - }, - "expectedErrors": ["$.postal_code: does not match the regex pattern [0-9]{5}(-[0-9]{4})?"] - } - ] -} \ No newline at end of file diff --git a/src/test/resources/data/issue425.json b/src/test/resources/data/issue425.json deleted file mode 100644 index c0e998819..000000000 --- a/src/test/resources/data/issue425.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "description": "Nuallable oneOf validation", - "schema": { - "properties": { - "values": { - "description": "desc", - "nullable": true, - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "string" - } - ] - } - } - }, - "tests": [ - { - "description": "oneOf with single string", - "data": { - "values": "test" - }, - "valid": true - }, - { - "description": "oneOf with invalid type", - "data": { - "values": 3 - }, - "valid": true - }, - { - "description": "oneOf with single string array", - "data": { - "values": [ "test1"] - }, - "valid": true - } - ] - } -] diff --git a/src/test/resources/data/issue467.json b/src/test/resources/data/issue467.json new file mode 100644 index 000000000..7be04dbb5 --- /dev/null +++ b/src/test/resources/data/issue467.json @@ -0,0 +1,19 @@ +{ +"tags": [ + { + "category": "book" + }, + { + "value": "2", + "category": "book" + }, + { + "value": "3", + "category": "book" + }, + { + "value": "4", + "category": "book" + } +] +} diff --git a/src/test/resources/data/issue470-invalid-1.json b/src/test/resources/data/issue470-invalid-1.json deleted file mode 100644 index 0afba7a06..000000000 --- a/src/test/resources/data/issue470-invalid-1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byName": { - "name": 123 - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue470-invalid-2.json b/src/test/resources/data/issue470-invalid-2.json deleted file mode 100644 index 65ea67661..000000000 --- a/src/test/resources/data/issue470-invalid-2.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byName": { - "name": "Too loooooooooong name" - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue470-invalid-3.json b/src/test/resources/data/issue470-invalid-3.json deleted file mode 100644 index 2c81c3a17..000000000 --- a/src/test/resources/data/issue470-invalid-3.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byAge": { - "age": "20" - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue470-invalid-4.json b/src/test/resources/data/issue470-invalid-4.json deleted file mode 100644 index 6a0d1b6f3..000000000 --- a/src/test/resources/data/issue470-invalid-4.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byAge": { - "age": 200 - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue470-valid-1.json b/src/test/resources/data/issue470-valid-1.json deleted file mode 100644 index 04b65d967..000000000 --- a/src/test/resources/data/issue470-valid-1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byName": { - "name": "John" - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue470-valid-2.json b/src/test/resources/data/issue470-valid-2.json deleted file mode 100644 index 1fb9e8477..000000000 --- a/src/test/resources/data/issue470-valid-2.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byAge": { - "age": 35 - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-1.json b/src/test/resources/data/issue491-invalid-1.json deleted file mode 100644 index 80dd10568..000000000 --- a/src/test/resources/data/issue491-invalid-1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "searchAge": { - "age": "Steve" - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-2.json b/src/test/resources/data/issue491-invalid-2.json deleted file mode 100644 index 29f140c72..000000000 --- a/src/test/resources/data/issue491-invalid-2.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "name": 123 - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-3.json b/src/test/resources/data/issue491-invalid-3.json deleted file mode 100644 index 37878f8db..000000000 --- a/src/test/resources/data/issue491-invalid-3.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byAge": { - "age": "Steve" - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-4.json b/src/test/resources/data/issue491-invalid-4.json deleted file mode 100644 index 79c8d4f37..000000000 --- a/src/test/resources/data/issue491-invalid-4.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "age": "Steve" - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-5.json b/src/test/resources/data/issue491-invalid-5.json deleted file mode 100644 index 9528725a7..000000000 --- a/src/test/resources/data/issue491-invalid-5.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "age": 200 - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-invalid-6.json b/src/test/resources/data/issue491-invalid-6.json deleted file mode 100644 index 3fad4f39b..000000000 --- a/src/test/resources/data/issue491-invalid-6.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "name": "TooLoooooooooooooooooooooooooooooooooongName" - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-valid-1.json b/src/test/resources/data/issue491-valid-1.json deleted file mode 100644 index edc86d3a9..000000000 --- a/src/test/resources/data/issue491-valid-1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "searchAge": { - "age": 50 - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-valid-2.json b/src/test/resources/data/issue491-valid-2.json deleted file mode 100644 index d71a75166..000000000 --- a/src/test/resources/data/issue491-valid-2.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "name": "Steve" - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-valid-3.json b/src/test/resources/data/issue491-valid-3.json deleted file mode 100644 index 60d4577d7..000000000 --- a/src/test/resources/data/issue491-valid-3.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "search": { - "byAge": { - "age": 50 - } - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue491-valid-4.json b/src/test/resources/data/issue491-valid-4.json deleted file mode 100644 index ac8d2c8d7..000000000 --- a/src/test/resources/data/issue491-valid-4.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "search": { - "age": 50 - } -} \ No newline at end of file diff --git a/src/test/resources/data/issue500_1.json b/src/test/resources/data/issue500_1.json new file mode 100644 index 000000000..f9ae1d710 --- /dev/null +++ b/src/test/resources/data/issue500_1.json @@ -0,0 +1,5 @@ +{ + "firstName": "John", + "lastName": "Doe", + "age": -21 +} diff --git a/src/test/resources/data/issue500_2.json b/src/test/resources/data/issue500_2.json new file mode 100644 index 000000000..2ff89e6fd --- /dev/null +++ b/src/test/resources/data/issue500_2.json @@ -0,0 +1,5 @@ +{ + "firstName": "John", + "lastName": "Doe", + "age": 15 +} diff --git a/src/test/resources/data/issue606.json b/src/test/resources/data/issue606.json new file mode 100644 index 000000000..f3ee32f77 --- /dev/null +++ b/src/test/resources/data/issue606.json @@ -0,0 +1,8 @@ +{ + "V": [ + { + "A": "foo", + "B": "bar" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/issue627.json b/src/test/resources/data/issue627.json new file mode 100644 index 000000000..e6c526712 --- /dev/null +++ b/src/test/resources/data/issue627.json @@ -0,0 +1,5 @@ +{ + "dateTime": "2022-07-33", + "email": "inValidEmail", + "uuid": "inValidUUID" +} diff --git a/src/test/resources/data/issue664.json b/src/test/resources/data/issue664.json new file mode 100644 index 000000000..386edb396 --- /dev/null +++ b/src/test/resources/data/issue664.json @@ -0,0 +1,10 @@ +[ + { + "country": "United Kingdom", + "postal_code": "UB87LL" + }, + { + "country": "United States of America", + "postal_code": "1234" + } +] \ No newline at end of file diff --git a/src/test/resources/data/issue668.json b/src/test/resources/data/issue668.json new file mode 100644 index 000000000..12f93304c --- /dev/null +++ b/src/test/resources/data/issue668.json @@ -0,0 +1,5 @@ +{ + "sub1": {}, + "sub2": {}, + "sub3": {} +} \ No newline at end of file diff --git a/src/test/resources/data/issue832.json b/src/test/resources/data/issue832.json new file mode 100644 index 000000000..c84fe2332 --- /dev/null +++ b/src/test/resources/data/issue832.json @@ -0,0 +1,4 @@ +{ + "foo": "does not match", + "contact": "not an email address" +} diff --git a/src/test/resources/data/issue898.json b/src/test/resources/data/issue898.json new file mode 100644 index 000000000..1dc70e475 --- /dev/null +++ b/src/test/resources/data/issue898.json @@ -0,0 +1,4 @@ +{ + "foo": "foo3", + "bar": "baz" +} \ No newline at end of file diff --git a/src/test/resources/data/notAllowedValidation/notAllowedJson.json b/src/test/resources/data/notAllowedValidation/notAllowedJson.json new file mode 100644 index 000000000..e71903095 --- /dev/null +++ b/src/test/resources/data/notAllowedValidation/notAllowedJson.json @@ -0,0 +1,6 @@ +{ + "$schema": "/schema/notAllowedValidation/notAllowedJson.json", + "field1": "value1", + "field2": "value2", + "field3": "value3" +} \ No newline at end of file diff --git a/src/test/resources/data/output-format-input.json b/src/test/resources/data/output-format-input.json new file mode 100644 index 000000000..8bdb2103f --- /dev/null +++ b/src/test/resources/data/output-format-input.json @@ -0,0 +1,10 @@ +[ + { + "x": 2.5, + "y": 1.3 + }, + { + "x": 1, + "z": 6.7 + } +] \ No newline at end of file diff --git a/src/test/resources/data/read-only-data.json b/src/test/resources/data/read-only-data.json new file mode 100644 index 000000000..cee56104a --- /dev/null +++ b/src/test/resources/data/read-only-data.json @@ -0,0 +1,4 @@ +{ + "firstName": "George", + "lastName": "Harrison" +} diff --git a/src/test/resources/draft2019-09/additionalItems.json b/src/test/resources/draft2019-09/additionalItems.json deleted file mode 100644 index 5fe4ddb1e..000000000 --- a/src/test/resources/draft2019-09/additionalItems.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "description": "additionalItems as schema", - "schema": { - "items": [ - {} - ], - "additionalItems": { - "type": "integer" - } - }, - "tests": [ - { - "description": "additional items match schema", - "data": [ - null, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "additional items do not match schema", - "data": [ - null, - 2, - 3, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "items is schema, no additionalItems", - "schema": { - "items": {}, - "additionalItems": false - }, - "tests": [ - { - "description": "all items match schema", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - } - ] - }, - { - "description": "array of items with no additionalItems", - "schema": { - "items": [ - {}, - {}, - {} - ], - "additionalItems": false - }, - "tests": [ - { - "description": "fewer number of items present", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "equal number of items present", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "additional items are not permitted", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "additionalItems as false without items", - "schema": { - "additionalItems": false - }, - "tests": [ - { - "description": "items defaults to empty schema so everything is valid", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - } - ] - }, - { - "description": "additionalItems are allowed by default", - "schema": { - "items": [ - { - "type": "integer" - } - ] - }, - "tests": [ - { - "description": "only the first item is validated", - "data": [ - 1, - "foo", - false - ], - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/additionalProperties.json b/src/test/resources/draft2019-09/additionalProperties.json deleted file mode 100644 index 827bb1fba..000000000 --- a/src/test/resources/draft2019-09/additionalProperties.json +++ /dev/null @@ -1,193 +0,0 @@ -[ - { - "description": "additionalProperties being false does not allow other properties", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "patternProperties": { - "^v": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": "boom" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobarbaz", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - }, - { - "description": "patternProperties are not additional properties", - "data": { - "foo": 1, - "vroom": 2 - }, - "valid": true - } - ] - }, - { - "description": "non-ASCII pattern with additionalProperties", - "schema": { - "patternProperties": { - "^á": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "matching the pattern is valid", - "data": { - "ármányos": 2 - }, - "valid": true - }, - { - "description": "not matching the pattern is invalid", - "data": { - "élmény": 2 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties allows a schema which should validate", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional valid property is valid", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": 12 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties can exist by itself", - "schema": { - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "an additional valid property is valid", - "data": { - "foo": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties are allowed by default", - "schema": { - "properties": { - "foo": {}, - "bar": {} - } - }, - "tests": [ - { - "description": "additional properties are allowed", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - } - ] - }, - { - "description": "additionalProperties should not look in applicators", - "schema": { - "allOf": [ - { - "properties": { - "foo": {} - } - } - ], - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "properties defined in allOf are not allowed", - "data": { - "foo": 1, - "bar": true - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/allOf.json b/src/test/resources/draft2019-09/allOf.json deleted file mode 100644 index 703d4c050..000000000 --- a/src/test/resources/draft2019-09/allOf.json +++ /dev/null @@ -1,288 +0,0 @@ -[ - { - "description": "allOf", - "schema": { - "allOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "allOf", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "mismatch second", - "data": { - "foo": "baz" - }, - "valid": false - }, - { - "description": "mismatch first", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type", - "data": { - "foo": "baz", - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "allOf with base schema", - "schema": { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ], - "allOf": [ - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - }, - { - "properties": { - "baz": { - "type": "null" - } - }, - "required": [ - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": "quux", - "bar": 2, - "baz": null - }, - "valid": true - }, - { - "description": "mismatch base schema", - "data": { - "foo": "quux", - "baz": null - }, - "valid": false - }, - { - "description": "mismatch first allOf", - "data": { - "bar": 2, - "baz": null - }, - "valid": false - }, - { - "description": "mismatch second allOf", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "mismatch both", - "data": { - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "allOf simple types", - "schema": { - "allOf": [ - { - "maximum": 30 - }, - { - "minimum": 20 - } - ] - }, - "tests": [ - { - "description": "valid", - "data": 25, - "valid": true - }, - { - "description": "mismatch one", - "data": 35, - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all true", - "schema": { - "allOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "allOf with boolean schemas, some false", - "schema": { - "allOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all false", - "schema": { - "allOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with one empty schema", - "schema": { - "allOf": [ - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with two empty schemas", - "schema": { - "allOf": [ - {}, - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with the first empty schema", - "schema": { - "allOf": [ - {}, - { - "type": "number" - } - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with the last empty schema", - "schema": { - "allOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/anchor.json b/src/test/resources/draft2019-09/anchor.json deleted file mode 100644 index f85e3c33b..000000000 --- a/src/test/resources/draft2019-09/anchor.json +++ /dev/null @@ -1,93 +0,0 @@ -[ - { - "description": "Location-independent identifier", - "schema": { - "allOf": [ - { - "$ref": "#foo" - } - ], - "$defs": { - "A": { - "$anchor": "foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with absolute URI", - "schema": { - "allOf": [ - { - "$ref": "http://localhost:1234/bar#foo" - } - ], - "$defs": { - "A": { - "$id": "http://localhost:1234/bar", - "$anchor": "foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with base URI change in subschema", - "schema": { - "$id": "http://localhost:1234/root", - "allOf": [ - { - "$ref": "http://localhost:1234/nested.json#foo" - } - ], - "$defs": { - "A": { - "$id": "nested.json", - "$defs": { - "B": { - "$anchor": "foo", - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/anyOf.json b/src/test/resources/draft2019-09/anyOf.json deleted file mode 100644 index f35e2194b..000000000 --- a/src/test/resources/draft2019-09/anyOf.json +++ /dev/null @@ -1,224 +0,0 @@ -[ - { - "description": "anyOf", - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first anyOf valid", - "data": 1, - "valid": true - }, - { - "description": "second anyOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both anyOf valid", - "data": 3, - "valid": true - }, - { - "description": "neither anyOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "anyOf with base schema", - "schema": { - "type": "string", - "anyOf": [ - { - "maxLength": 2 - }, - { - "minLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one anyOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both anyOf invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf with boolean schemas, all true", - "schema": { - "anyOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, some true", - "schema": { - "anyOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, all false", - "schema": { - "anyOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf complex types", - "schema": { - "anyOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first anyOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second anyOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both anyOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "neither anyOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "anyOf with one empty schema", - "schema": { - "anyOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is valid", - "data": 123, - "valid": true - } - ] - }, - { - "description": "nested anyOf, to check validation semantics", - "schema": { - "anyOf": [ - { - "anyOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/boolean_schema.json b/src/test/resources/draft2019-09/boolean_schema.json deleted file mode 100644 index ee241ef92..000000000 --- a/src/test/resources/draft2019-09/boolean_schema.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "description": "boolean schema 'true'", - "schema": true, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "boolean true is valid", - "data": true, - "valid": true - }, - { - "description": "boolean false is valid", - "data": false, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - }, - { - "description": "array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "boolean schema 'false'", - "schema": false, - "tests": [ - { - "description": "number is invalid", - "data": 1, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "boolean true is invalid", - "data": true, - "valid": false - }, - { - "description": "boolean false is invalid", - "data": false, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - }, - { - "description": "object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "empty object is invalid", - "data": {}, - "valid": false - }, - { - "description": "array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/const.json b/src/test/resources/draft2019-09/const.json deleted file mode 100644 index 69b5012f2..000000000 --- a/src/test/resources/draft2019-09/const.json +++ /dev/null @@ -1,214 +0,0 @@ -[ - { - "description": "const validation", - "schema": { - "const": 2 - }, - "tests": [ - { - "description": "same value is valid", - "data": 2, - "valid": true - }, - { - "description": "another value is invalid", - "data": 5, - "valid": false - }, - { - "description": "another type is invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "const with object", - "schema": { - "const": { - "foo": "bar", - "baz": "bax" - } - }, - "tests": [ - { - "description": "same object is valid", - "data": { - "foo": "bar", - "baz": "bax" - }, - "valid": true - }, - { - "description": "same object with different property order is valid", - "data": { - "baz": "bax", - "foo": "bar" - }, - "valid": true - }, - { - "description": "another object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "another type is invalid", - "data": [ - 1, - 2 - ], - "valid": false - } - ] - }, - { - "description": "const with array", - "schema": { - "const": [ - { - "foo": "bar" - } - ] - }, - "tests": [ - { - "description": "same array is valid", - "data": [ - { - "foo": "bar" - } - ], - "valid": true - }, - { - "description": "another array item is invalid", - "data": [ - 2 - ], - "valid": false - }, - { - "description": "array with additional items is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - } - ] - }, - { - "description": "const with null", - "schema": { - "const": null - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "not null is invalid", - "data": 0, - "valid": false - } - ] - }, - { - "description": "const with false does not match 0", - "schema": { - "const": false - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "const with true does not match 1", - "schema": { - "const": true - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "const with 0 does not match false", - "schema": { - "const": 0 - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "const with 1 does not match true", - "schema": { - "const": 1 - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/contains.json b/src/test/resources/draft2019-09/contains.json deleted file mode 100644 index 0ea1da807..000000000 --- a/src/test/resources/draft2019-09/contains.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "description": "contains keyword validation", - "schema": { - "contains": { - "minimum": 5 - } - }, - "tests": [ - { - "description": "array with item matching schema (5) is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with item matching schema (6) is valid", - "data": [ - 3, - 4, - 6 - ], - "valid": true - }, - { - "description": "array with two items matching schema (5, 6) is valid", - "data": [ - 3, - 4, - 5, - 6 - ], - "valid": true - }, - { - "description": "array without items matching schema is invalid", - "data": [ - 2, - 3, - 4 - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - }, - { - "description": "not array is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "contains keyword with const keyword", - "schema": { - "contains": { - "const": 5 - } - }, - "tests": [ - { - "description": "array with item 5 is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with two items 5 is valid", - "data": [ - 3, - 4, - 5, - 5 - ], - "valid": true - }, - { - "description": "array without item 5 is invalid", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema true", - "schema": { - "contains": true - }, - "tests": [ - { - "description": "any non-empty array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema false", - "schema": { - "contains": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/default.json b/src/test/resources/draft2019-09/default.json deleted file mode 100644 index 983713399..000000000 --- a/src/test/resources/draft2019-09/default.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "description": "invalid type for default", - "schema": { - "properties": { - "foo": { - "type": "integer", - "default": [] - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "foo": 13 - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - }, - { - "description": "invalid string value for default", - "schema": { - "properties": { - "bar": { - "type": "string", - "minLength": 4, - "default": "bad" - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "bar": "good" - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/defs.json b/src/test/resources/draft2019-09/defs.json deleted file mode 100644 index c3c4cee47..000000000 --- a/src/test/resources/draft2019-09/defs.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "description": "valid definition", - "schema": { - "$ref": "https://json-schema.org/draft/2019-09/schema" - }, - "tests": [ - { - "description": "valid definition schema", - "data": { - "$defs": { - "foo": { - "type": "integer" - } - } - }, - "valid": true - } - ] - }, - { - "description": "invalid definition", - "schema": { - "$ref": "https://json-schema.org/draft/2019-09/schema" - }, - "tests": [ - { - "description": "invalid definition schema", - "data": { - "$defs": { - "foo": { - "type": 1 - } - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/dependentRequired.json b/src/test/resources/draft2019-09/dependentRequired.json deleted file mode 100644 index 1ab6b28d2..000000000 --- a/src/test/resources/draft2019-09/dependentRequired.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "description": "single dependency", - "schema": {"dependentRequired": {"bar": ["foo"]}}, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependant", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "with dependency", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "missing dependency", - "data": {"bar": 2}, - "valid": false - }, - { - "description": "ignores arrays", - "data": ["bar"], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "empty dependents", - "schema": {"dependentRequired": {"bar": []}}, - "tests": [ - { - "description": "empty object", - "data": {}, - "valid": true - }, - { - "description": "object with one property", - "data": {"bar": 2}, - "valid": true - }, - { - "description": "non-object is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "multiple dependents required", - "schema": {"dependentRequired": {"quux": ["foo", "bar"]}}, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependants", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "with dependencies", - "data": {"foo": 1, "bar": 2, "quux": 3}, - "valid": true - }, - { - "description": "missing dependency", - "data": {"foo": 1, "quux": 2}, - "valid": false - }, - { - "description": "missing other dependency", - "data": {"bar": 1, "quux": 2}, - "valid": false - }, - { - "description": "missing both dependencies", - "data": {"quux": 1}, - "valid": false - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "dependentRequired": { - "foo\nbar": ["foo\rbar"], - "foo\"bar": ["foo'bar"] - } - }, - "tests": [ - { - "description": "CRLF", - "data": { - "foo\nbar": 1, - "foo\rbar": 2 - }, - "valid": true - }, - { - "description": "quoted quotes", - "data": { - "foo'bar": 1, - "foo\"bar": 2 - }, - "valid": true - }, - { - "description": "CRLF missing dependent", - "data": { - "foo\nbar": 1, - "foo": 2 - }, - "valid": false - }, - { - "description": "quoted quotes missing dependent", - "data": { - "foo\"bar": 2 - }, - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/src/test/resources/draft2019-09/dependentSchemas.json b/src/test/resources/draft2019-09/dependentSchemas.json deleted file mode 100644 index 0accf03d3..000000000 --- a/src/test/resources/draft2019-09/dependentSchemas.json +++ /dev/null @@ -1,129 +0,0 @@ -[ - { - "description": "single dependency", - "schema": { - "dependentSchemas": { - "bar": { - "properties": { - "foo": {"type": "integer"}, - "bar": {"type": "integer"} - } - } - } - }, - "tests": [ - { - "description": "valid", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "no dependency", - "data": {"foo": "quux"}, - "valid": true - }, - { - "description": "wrong type", - "data": {"foo": "quux", "bar": 2}, - "valid": false - }, - { - "description": "wrong type other", - "data": {"foo": 2, "bar": "quux"}, - "valid": false - }, - { - "description": "wrong type both", - "data": {"foo": "quux", "bar": "quux"}, - "valid": false - }, - { - "description": "ignores arrays", - "data": ["bar"], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "boolean subschemas", - "schema": { - "dependentSchemas": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "object with property having schema true is valid", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "object with property having schema false is invalid", - "data": {"bar": 2}, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": {"foo": 1, "bar": 2}, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "dependentSchemas": { - "foo\tbar": {"minProperties": 4}, - "foo'bar": {"required": ["foo\"bar"]} - } - }, - "tests": [ - { - "description": "quoted tab", - "data": { - "foo\tbar": 1, - "a": 2, - "b": 3, - "c": 4 - }, - "valid": true - }, - { - "description": "quoted quote", - "data": { - "foo'bar": {"foo\"bar": 1} - }, - "valid": false - }, - { - "description": "quoted tab invalid under dependent schema", - "data": { - "foo\tbar": 1, - "a": 2 - }, - "valid": false - }, - { - "description": "quoted quote invalid under dependent schema", - "data": {"foo'bar": 1}, - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/src/test/resources/draft2019-09/enum.json b/src/test/resources/draft2019-09/enum.json deleted file mode 100644 index d0b8aacdf..000000000 --- a/src/test/resources/draft2019-09/enum.json +++ /dev/null @@ -1,233 +0,0 @@ -[ - { - "description": "simple enum validation", - "schema": { - "enum": [ - 1, - 2, - 3 - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": 1, - "valid": true - }, - { - "description": "something else is invalid", - "data": 4, - "valid": false - } - ] - }, - { - "description": "heterogeneous enum validation", - "schema": { - "enum": [ - 6, - "foo", - [], - true, - { - "foo": 12 - } - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": [], - "valid": true - }, - { - "description": "something else is invalid", - "data": null, - "valid": false - }, - { - "description": "objects are deep compared", - "data": { - "foo": false - }, - "valid": false - } - ] - }, - { - "description": "enums in properties", - "schema": { - "type": "object", - "properties": { - "foo": { - "enum": [ - "foo" - ] - }, - "bar": { - "enum": [ - "bar" - ] - } - }, - "required": [ - "bar" - ] - }, - "tests": [ - { - "description": "both properties are valid", - "data": { - "foo": "foo", - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing optional property is valid", - "data": { - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing required property is invalid", - "data": { - "foo": "foo" - }, - "valid": false - }, - { - "description": "missing all properties is invalid", - "data": {}, - "valid": false - } - ] - }, - { - "description": "enum with escaped characters", - "schema": { - "enum": [ - "foo\nbar", - "foo\rbar" - ] - }, - "tests": [ - { - "description": "member 1 is valid", - "data": "foo\nbar", - "valid": true - }, - { - "description": "member 2 is valid", - "data": "foo\rbar", - "valid": true - }, - { - "description": "another string is invalid", - "data": "abc", - "valid": false - } - ] - }, - { - "description": "enum with false does not match 0", - "schema": { - "enum": [ - false - ] - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "enum with true does not match 1", - "schema": { - "enum": [ - true - ] - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "enum with 0 does not match false", - "schema": { - "enum": [ - 0 - ] - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "enum with 1 does not match true", - "schema": { - "enum": [ - 1 - ] - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/exclusiveMaximum.json b/src/test/resources/draft2019-09/exclusiveMaximum.json deleted file mode 100644 index fc8c4bb47..000000000 --- a/src/test/resources/draft2019-09/exclusiveMaximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMaximum validation", - "schema": { - "exclusiveMaximum": 3.0 - }, - "tests": [ - { - "description": "below the exclusiveMaximum is valid", - "data": 2.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 3.0, - "valid": false - }, - { - "description": "above the exclusiveMaximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/exclusiveMinimum.json b/src/test/resources/draft2019-09/exclusiveMinimum.json deleted file mode 100644 index 815d8fbe1..000000000 --- a/src/test/resources/draft2019-09/exclusiveMinimum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMinimum validation", - "schema": { - "exclusiveMinimum": 1.1 - }, - "tests": [ - { - "description": "above the exclusiveMinimum is valid", - "data": 1.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "below the exclusiveMinimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/format.json b/src/test/resources/draft2019-09/format.json deleted file mode 100644 index 6e6cd01a7..000000000 --- a/src/test/resources/draft2019-09/format.json +++ /dev/null @@ -1,648 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IDN e-mail addresses", - "schema": { - "format": "idn-email" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of regexes", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IDN hostnames", - "schema": { - "format": "idn-hostname" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of hostnames", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of date strings", - "schema": { - "format": "date" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of time strings", - "schema": { - "format": "time" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of JSON pointers", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of relative JSON pointers", - "schema": { - "format": "relative-json-pointer" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IRIs", - "schema": { - "format": "iri" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IRI references", - "schema": { - "format": "iri-reference" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI references", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI templates", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/if-then-else.json b/src/test/resources/draft2019-09/if-then-else.json deleted file mode 100644 index 7b014aeac..000000000 --- a/src/test/resources/draft2019-09/if-then-else.json +++ /dev/null @@ -1,366 +0,0 @@ -[ - { - "description": "ignore if without then or else", - "schema": { - "if": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone if", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone if", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "ignore then without if", - "schema": { - "then": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone then", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone then", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "ignore else without if", - "schema": { - "else": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone else", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone else", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "if and then without else", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - } - }, - "tests": [ - { - "description": "valid through then", - "data": -1, - "valid": true - }, - { - "description": "invalid through then", - "data": -100, - "valid": false - }, - { - "description": "valid when if test fails", - "data": 3, - "valid": true - } - ] - }, - { - "description": "if and else without then", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "else": { - "multipleOf": 2 - } - }, - "tests": [ - { - "description": "valid when if test passes", - "data": -1, - "valid": true - }, - { - "description": "valid through else", - "data": 4, - "valid": true - }, - { - "description": "invalid through else", - "data": 3, - "valid": false - } - ] - }, - { - "description": "validate against correct branch, then vs else", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - }, - "else": { - "multipleOf": 2 - } - }, - "tests": [ - { - "description": "valid through then", - "data": -1, - "valid": true - }, - { - "description": "invalid through then", - "data": -100, - "valid": false - }, - { - "description": "valid through else", - "data": 4, - "valid": true - }, - { - "description": "invalid through else", - "data": 3, - "valid": false - } - ] - }, - { - "description": "non-interference across combined schemas", - "schema": { - "allOf": [ - { - "if": { - "exclusiveMaximum": 0 - } - }, - { - "then": { - "minimum": -10 - } - }, - { - "else": { - "multipleOf": 2 - } - } - ] - }, - "tests": [ - { - "description": "valid, but would have been invalid through then", - "data": -100, - "valid": true - }, - { - "description": "valid, but would have been invalid through else", - "data": 3, - "valid": true - } - ] - }, - { - "description": "conditions by properties", - "schema": { - "type": "object", - "properties": { - "street_address": { - "type": "string" - }, - "country": { - "enum": ["United States of America", "Canada"] - } - }, - "if": { - "properties": { - "country": { - "const": "United States of America" - } - } - }, - "then": { - "properties": { - "postal_code": { - "pattern": "[0-9]{5}(-[0-9]{4})?" - } - } - }, - "else": { - "properties": { - "postal_code": { - "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" - } - } - } - }, - "tests": [ - { - "description": "valid through then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "country": "United States of America", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid through then, alternative match", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid through else", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "K1M 1M4" - }, - "valid": true - }, - { - "description": "invalid through else", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "10000" - }, - "valid": false - } - , - { - "description": "invalid through then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "K1M 1M4" - }, - "valid": false - } - ] - }, - { - "description": "conditions by allOf properties", - "schema": { - "type": "object", - "properties": { - "street_address": { - "type": "string" - }, - "country": { - "default": "United States of America", - "enum": ["United States of America", "Canada", "Netherlands"] - } - }, - "allOf": [ - { - "if": { - "properties": { "country": { "const": "United States of America" } } - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Canada" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Netherlands" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } - } - } - ] - }, - "tests": [ - { - "description": "valid first if", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "country": "United States of America", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid first if, alternative match", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid second if", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "K1M 1M4" - }, - "valid": true - }, - { - "description": "valid third if", - "data": { - "street_address": "Adriaan Goekooplaan", - "country": "Netherlands", - "postal_code": "2517 JX" - }, - "valid": true - }, - { - "description": "invalid through second then", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "10000" - }, - "valid": false - }, - { - "description": "invalid through first then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "K1M 1M4" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/invalid-min-max-contains.json b/src/test/resources/draft2019-09/invalid-min-max-contains.json new file mode 100644 index 000000000..8acbb8664 --- /dev/null +++ b/src/test/resources/draft2019-09/invalid-min-max-contains.json @@ -0,0 +1,123 @@ +[ + { + "description": "minContains is not a number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minContains": "1" + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"minContains\":\"1\"}" + ] + } + ] + }, + { + "description": "minContains is not an integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minContains": 0.5 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"minContains\":0.5}" + ] + } + ] + }, + { + "description": "minContains is a negative number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minContains": -1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"minContains\":-1}" + ] + } + ] + }, + { + "description": "maxContains is not a number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxContains": "1" + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"maxContains\":\"1\"}" + ] + } + ] + }, + { + "description": "maxContains is not an integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxContains": 0.5 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"maxContains\":0.5}" + ] + } + ] + }, + { + "description": "maxContains is a negative number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxContains": -1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"maxContains\":-1}" + ] + } + ] + }, + { + "description": "maxContains is less than minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxContains": 0, + "minContains": 1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": minContains must less than or equal to maxContains in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"maxContains\":0,\"minContains\":1}", + ": minContains must less than or equal to maxContains in {\"$schema\":\"https://json-schema.org/draft/2019-09/schema\",\"maxContains\":0,\"minContains\":1}" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft2019-09/issue375.json b/src/test/resources/draft2019-09/issue375.json index fe6206fd2..66979c0dd 100644 --- a/src/test/resources/draft2019-09/issue375.json +++ b/src/test/resources/draft2019-09/issue375.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2019-09/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "test", "description": "asdasdint", "type": "object", diff --git a/src/test/resources/draft2019-09/items.json b/src/test/resources/draft2019-09/items.json deleted file mode 100644 index b84adac58..000000000 --- a/src/test/resources/draft2019-09/items.json +++ /dev/null @@ -1,506 +0,0 @@ -[ - { - "description": "a schema given for items", - "schema": { - "items": { - "type": "integer" - } - }, - "tests": [ - { - "description": "valid items", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "wrong type of items", - "data": [ - 1, - "x" - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "length": 1 - }, - "valid": true - } - ] - }, - { - "description": "an array of schemas for items", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "tests": [ - { - "description": "correct types", - "data": [ - 1, - "foo" - ], - "valid": true - }, - { - "description": "wrong types", - "data": [ - "foo", - 1 - ], - "valid": false - }, - { - "description": "incomplete array of items", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with additional items", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array", - "data": [], - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "1": "valid", - "length": 2 - }, - "valid": true - } - ] - }, - { - "description": "items with boolean schema (true)", - "schema": { - "items": true - }, - "tests": [ - { - "description": "any array is valid", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schema (false)", - "schema": { - "items": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - 1, - "foo", - true - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schemas", - "schema": { - "items": [ - true, - false - ] - }, - "tests": [ - { - "description": "array with one item is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with two items is invalid", - "data": [ - 1, - "foo" - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items and subitems", - "schema": { - "$defs": { - "item": { - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/$defs/sub-item" - }, - { - "$ref": "#/$defs/sub-item" - } - ] - }, - "sub-item": { - "type": "object", - "required": [ - "foo" - ] - } - }, - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/$defs/item" - }, - { - "$ref": "#/$defs/item" - }, - { - "$ref": "#/$defs/item" - } - ] - }, - "tests": [ - { - "description": "valid items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": true - }, - { - "description": "too many items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "too many sub-items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong item", - "data": [ - { - "foo": null - }, - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong sub-item", - "data": [ - [ - {}, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "fewer items is valid", - "data": [ - [ - { - "foo": null - } - ], - [ - { - "foo": null - } - ] - ], - "valid": true - } - ] - }, - { - "description": "nested items", - "schema": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - } - } - } - } - }, - "tests": [ - { - "description": "valid nested array", - "data": [ - [ - [ - [ - 1 - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": true - }, - { - "description": "nested array with invalid type", - "data": [ - [ - [ - [ - "1" - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": false - }, - { - "description": "not deep enough", - "data": [ - [ - [ - 1 - ], - [ - 2 - ], - [ - 3 - ] - ], - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/maxItems.json b/src/test/resources/draft2019-09/maxItems.json deleted file mode 100644 index 52f0f2a81..000000000 --- a/src/test/resources/draft2019-09/maxItems.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "description": "maxItems validation", - "schema": { - "maxItems": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "too long is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "foobar", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/maxLength.json b/src/test/resources/draft2019-09/maxLength.json deleted file mode 100644 index f297da029..000000000 --- a/src/test/resources/draft2019-09/maxLength.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "maxLength validation", - "schema": { - "maxLength": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": "f", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too long is invalid", - "data": "foo", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - }, - { - "description": "two supplementary Unicode code points is long enough", - "data": "\uD83D\uDCA9\uD83D\uDCA9", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/maxProperties.json b/src/test/resources/draft2019-09/maxProperties.json deleted file mode 100644 index cffe1ba3d..000000000 --- a/src/test/resources/draft2019-09/maxProperties.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "description": "maxProperties validation", - "schema": { - "maxProperties": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "too long is invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/maximum.json b/src/test/resources/draft2019-09/maximum.json deleted file mode 100644 index 194ee32ee..000000000 --- a/src/test/resources/draft2019-09/maximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "maximum validation", - "schema": { - "maximum": 3.0 - }, - "tests": [ - { - "description": "below the maximum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 3.0, - "valid": true - }, - { - "description": "above the maximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/minItems.json b/src/test/resources/draft2019-09/minItems.json deleted file mode 100644 index 8a89e7aed..000000000 --- a/src/test/resources/draft2019-09/minItems.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "minItems validation", - "schema": { - "minItems": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "too short is invalid", - "data": [], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/minLength.json b/src/test/resources/draft2019-09/minLength.json deleted file mode 100644 index ee310f821..000000000 --- a/src/test/resources/draft2019-09/minLength.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "minLength validation", - "schema": { - "minLength": 2 - }, - "tests": [ - { - "description": "longer is valid", - "data": "foo", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too short is invalid", - "data": "f", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1, - "valid": true - }, - { - "description": "one supplementary Unicode code point is not long enough", - "data": "\uD83D\uDCA9", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/minProperties.json b/src/test/resources/draft2019-09/minProperties.json deleted file mode 100644 index c700fe42e..000000000 --- a/src/test/resources/draft2019-09/minProperties.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "minProperties validation", - "schema": { - "minProperties": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "too short is invalid", - "data": {}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/minimum.json b/src/test/resources/draft2019-09/minimum.json deleted file mode 100644 index 47070907a..000000000 --- a/src/test/resources/draft2019-09/minimum.json +++ /dev/null @@ -1,63 +0,0 @@ -[ - { - "description": "minimum validation", - "schema": { - "minimum": 1.1 - }, - "tests": [ - { - "description": "above the minimum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 1.1, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - }, - { - "description": "minimum validation with signed integer", - "schema": { - "minimum": -2 - }, - "tests": [ - { - "description": "negative above the minimum is valid", - "data": -1, - "valid": true - }, - { - "description": "positive above the minimum is valid", - "data": 0, - "valid": true - }, - { - "description": "boundary point is valid", - "data": -2, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": -3, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/multipleOf.json b/src/test/resources/draft2019-09/multipleOf.json deleted file mode 100644 index 886ea837e..000000000 --- a/src/test/resources/draft2019-09/multipleOf.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "description": "by int", - "schema": { - "multipleOf": 2 - }, - "tests": [ - { - "description": "int by int", - "data": 10, - "valid": true - }, - { - "description": "int by int fail", - "data": 7, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "by number", - "schema": { - "multipleOf": 1.5 - }, - "tests": [ - { - "description": "zero is multiple of anything", - "data": 0, - "valid": true - }, - { - "description": "4.5 is multiple of 1.5", - "data": 4.5, - "valid": true - }, - { - "description": "35 is not multiple of 1.5", - "data": 35, - "valid": false - } - ] - }, - { - "description": "by small number", - "schema": { - "multipleOf": 0.0001 - }, - "tests": [ - { - "description": "0.0075 is multiple of 0.0001", - "data": 0.0075, - "valid": true - }, - { - "description": "0.00751 is not multiple of 0.0001", - "data": 0.00751, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/not.json b/src/test/resources/draft2019-09/not.json deleted file mode 100644 index c2d2e411e..000000000 --- a/src/test/resources/draft2019-09/not.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "description": "not", - "schema": { - "not": { - "type": "integer" - } - }, - "tests": [ - { - "description": "allowed", - "data": "foo", - "valid": true - }, - { - "description": "disallowed", - "data": 1, - "valid": false - } - ] - }, - { - "description": "not multiple types", - "schema": { - "not": { - "type": [ - "integer", - "boolean" - ] - } - }, - "tests": [ - { - "description": "valid", - "data": "foo", - "valid": true - }, - { - "description": "mismatch", - "data": 1, - "valid": false - }, - { - "description": "other mismatch", - "data": true, - "valid": false - } - ] - }, - { - "description": "not more complex schema", - "schema": { - "not": { - "type": "object", - "properties": { - "foo": { - "type": "string" - } - } - } - }, - "tests": [ - { - "description": "match", - "data": 1, - "valid": true - }, - { - "description": "other match", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "foo": "bar" - }, - "valid": false - } - ] - }, - { - "description": "forbidden property", - "schema": { - "properties": { - "foo": { - "not": {} - } - } - }, - "tests": [ - { - "description": "property present", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "property absent", - "data": { - "bar": 1, - "baz": 2 - }, - "valid": true - } - ] - }, - { - "description": "not with boolean schema true", - "schema": { - "not": true - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "not with boolean schema false", - "schema": { - "not": false - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/oneOf.json b/src/test/resources/draft2019-09/oneOf.json deleted file mode 100644 index 65ab47d0a..000000000 --- a/src/test/resources/draft2019-09/oneOf.json +++ /dev/null @@ -1,327 +0,0 @@ -[ - { - "description": "oneOf", - "schema": { - "oneOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": 1, - "valid": true - }, - { - "description": "second oneOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both oneOf valid", - "data": 3, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "oneOf with base schema", - "schema": { - "type": "string", - "oneOf": [ - { - "minLength": 2 - }, - { - "maxLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one oneOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both oneOf valid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all true", - "schema": { - "oneOf": [ - true, - true, - true - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, one true", - "schema": { - "oneOf": [ - true, - false, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "oneOf with boolean schemas, more than one true", - "schema": { - "oneOf": [ - true, - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all false", - "schema": { - "oneOf": [ - false, - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf complex types", - "schema": { - "oneOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second oneOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both oneOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": false - }, - { - "description": "neither oneOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "oneOf with empty schema", - "schema": { - "oneOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "one valid - valid", - "data": "foo", - "valid": true - }, - { - "description": "both valid - invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "oneOf with required", - "schema": { - "type": "object", - "oneOf": [ - { - "required": [ - "foo", - "bar" - ] - }, - { - "required": [ - "foo", - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "both invalid - invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "first valid - valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "second valid - valid", - "data": { - "foo": 1, - "baz": 3 - }, - "valid": true - }, - { - "description": "both valid - invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - } - ] - }, - { - "description": "oneOf with missing optional property", - "schema": { - "oneOf": [ - { - "properties": { - "bar": true, - "baz": true - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": true - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": { - "bar": 8 - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "foo": "foo" - }, - "valid": true - }, - { - "description": "both oneOf valid", - "data": { - "foo": "foo", - "bar": 8 - }, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": { - "baz": "quux" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/bignum.json b/src/test/resources/draft2019-09/optional/bignum.json deleted file mode 100644 index 4e5bafdfa..000000000 --- a/src/test/resources/draft2019-09/optional/bignum.json +++ /dev/null @@ -1,119 +0,0 @@ -[ - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a bignum is an integer", - "data": 12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a bignum is a number", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a negative bignum is an integer", - "data": -12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a negative bignum is a number", - "data": -98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "string", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "a bignum is not a string", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "maximum": 18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision", - "schema": { - "exclusiveMaximum": 972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 972783798187987123879878123.188781371, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "minimum": -18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision on negative numbers", - "schema": { - "exclusiveMinimum": -972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -972783798187987123879878123.188781371, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/content.json b/src/test/resources/draft2019-09/optional/content.json deleted file mode 100644 index 0efc737a2..000000000 --- a/src/test/resources/draft2019-09/optional/content.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "description": "validation of string-encoded content based on media type", - "schema": { - "contentMediaType": "application/json" - }, - "tests": [ - { - "description": "a valid JSON document", - "data": "{\"foo\": \"bar\"}", - "valid": true - }, - { - "description": "an invalid JSON document", - "data": "{:}", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - }, - { - "description": "validation of binary string-encoding", - "schema": { - "contentEncoding": "base64" - }, - "tests": [ - { - "description": "a valid base64 string", - "data": "eyJmb28iOiAiYmFyIn0K", - "valid": true - }, - { - "description": "an invalid base64 string (% is not a valid character)", - "data": "eyJmb28iOi%iYmFyIn0K", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - }, - { - "description": "validation of binary-encoded media type documents", - "schema": { - "contentMediaType": "application/json", - "contentEncoding": "base64" - }, - "tests": [ - { - "description": "a valid base64-encoded JSON document", - "data": "eyJmb28iOiAiYmFyIn0K", - "valid": true - }, - { - "description": "a validly-encoded invalid JSON document", - "data": "ezp9Cg==", - "valid": false - }, - { - "description": "an invalid base64 string that is valid JSON", - "data": "{}", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/ecmascript-regex.json b/src/test/resources/draft2019-09/optional/ecmascript-regex.json deleted file mode 100644 index a27652188..000000000 --- a/src/test/resources/draft2019-09/optional/ecmascript-regex.json +++ /dev/null @@ -1,215 +0,0 @@ -[ - { - "description": "ECMA 262 regex non-compliance", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "ECMA 262 has no support for \\Z anchor from .NET", - "data": "^\\S(|(.|\\n)*\\S)\\Z", - "valid": false - } - ] - }, - { - "description": "ECMA 262 regex $ does not match trailing newline", - "schema": { - "type": "string", - "pattern": "^abc$" - }, - "tests": [ - { - "description": "matches in Python, but should not in jsonschema", - "data": "abc\n", - "valid": false - }, - { - "description": "should match", - "data": "abc", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex converts \\a to ascii BEL", - "schema": { - "type": "string", - "pattern": "^\\a$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\a", - "valid": false - }, - { - "description": "matches", - "data": "\u0007", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and upper letter", - "schema": { - "type": "string", - "pattern": "^\\cC$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cC", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and lower letter", - "schema": { - "type": "string", - "pattern": "^\\cc$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cc", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\d matches ascii digits only", - "schema": { - "type": "string", - "pattern": "^\\d$" - }, - "tests": [ - { - "description": "ASCII zero matches", - "data": "0", - "valid": true - }, - { - "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", - "data": "߀", - "valid": false - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) does not match", - "data": "\u07c0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\D matches everything but ascii digits", - "schema": { - "type": "string", - "pattern": "^\\D$" - }, - "tests": [ - { - "description": "ASCII zero does not match", - "data": "0", - "valid": false - }, - { - "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", - "data": "߀", - "valid": true - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) matches", - "data": "\u07c0", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\w matches ascii letters only", - "schema": { - "type": "string", - "pattern": "^\\w$" - }, - "tests": [ - { - "description": "ASCII 'a' matches", - "data": "a", - "valid": true - }, - { - "description": "latin-1 e-acute does not match (unlike e.g. Python)", - "data": "é", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\w matches everything but ascii letters", - "schema": { - "type": "string", - "pattern": "^\\W$" - }, - "tests": [ - { - "description": "ASCII 'a' does not match", - "data": "a", - "valid": false - }, - { - "description": "latin-1 e-acute matches (unlike e.g. Python)", - "data": "é", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\s matches ascii whitespace only", - "schema": { - "type": "string", - "pattern": "^\\s$" - }, - "tests": [ - { - "description": "ASCII space matches", - "data": " ", - "valid": true - }, - { - "description": "latin-1 non-breaking-space does not match (unlike e.g. Python)", - "data": "\u00a0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\S matches everything but ascii whitespace", - "schema": { - "type": "string", - "pattern": "^\\S$" - }, - "tests": [ - { - "description": "ASCII space does not match", - "data": " ", - "valid": false - }, - { - "description": "latin-1 non-breaking-space matches (unlike e.g. Python)", - "data": "\u00a0", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/date-time.json b/src/test/resources/draft2019-09/optional/format/date-time.json deleted file mode 100644 index 6baa1a564..000000000 --- a/src/test/resources/draft2019-09/optional/format/date-time.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "a valid date-time string", - "data": "1963-06-19T08:30:06.283185Z", - "valid": true - }, - { - "description": "a valid date-time string without second fraction", - "data": "1963-06-19T08:30:06Z", - "valid": true - }, - { - "description": "a valid date-time string with plus offset", - "data": "1937-01-01T12:00:27.87+00:20", - "valid": true - }, - { - "description": "a valid date-time string with minus offset", - "data": "1990-12-31T15:59:50.123-08:00", - "valid": true - }, - { - "description": "a invalid day in date-time string", - "data": "1990-02-31T15:59:60.123-08:00", - "valid": false - }, - { - "description": "an invalid offset in date-time string", - "data": "1990-12-31T15:59:60-24:00", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963 08:30:06 PST", - "valid": false - }, - { - "description": "case-insensitive T and Z", - "data": "1963-06-19t08:30:06.283185z", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350T01:01:01", - "valid": false - }, - { - "description": "an invalid date-time string without offset", - "data": "1963-06-19T08:30:06", - "valid": false - }, - { - "description": "an invalid date-time string with only hours in offset", - "data": "1963-06-19T08:30:06+02", - "valid": false - }, - { - "description": "an invalid date-time string without colon in offset", - "data": "1963-06-19T08:30:06+0200", - "valid": false - }, - { - "description": "a valid date-time string with leap second", - "data": "1998-12-31T23:59:60Z", - "valid": true - }, - { - "description": "an invalid date-time string with leap second", - "data": "1997-12-31T23:59:60Z", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/date.json b/src/test/resources/draft2019-09/optional/format/date.json deleted file mode 100644 index 39c66593f..000000000 --- a/src/test/resources/draft2019-09/optional/format/date.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "description": "validation of date strings", - "schema": { - "format": "date" - }, - "tests": [ - { - "description": "a valid date string", - "data": "1963-06-19", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963", - "valid": false - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/email.json b/src/test/resources/draft2019-09/optional/format/email.json deleted file mode 100644 index 9873d6c10..000000000 --- a/src/test/resources/draft2019-09/optional/format/email.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "a valid e-mail address", - "data": "joe.bloggs@example.com", - "valid": true - }, - { - "description": "an invalid e-mail address", - "data": "2962", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/hostname.json b/src/test/resources/draft2019-09/optional/format/hostname.json deleted file mode 100644 index e2ec7960d..000000000 --- a/src/test/resources/draft2019-09/optional/format/hostname.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of host names", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "a valid host name", - "data": "www.example.com", - "valid": true - }, - { - "description": "a valid punycoded IDN hostname", - "data": "xn--4gbwdl.xn--wgbh1c", - "valid": true - }, - { - "description": "a host name starting with an illegal character", - "data": "-a-host-name-that-starts-with--", - "valid": false - }, - { - "description": "a host name containing illegal characters", - "data": "not_a_valid_host_name", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/idn-email.json b/src/test/resources/draft2019-09/optional/format/idn-email.json deleted file mode 100644 index 7619a1870..000000000 --- a/src/test/resources/draft2019-09/optional/format/idn-email.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of an internationalized e-mail addresses", - "schema": { - "format": "idn-email" - }, - "tests": [ - { - "description": "a valid idn e-mail (example@example.test in Hangul)", - "data": "실례@실례.테스트", - "valid": true - }, - { - "description": "an invalid idn e-mail address", - "data": "2962", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/idn-hostname.json b/src/test/resources/draft2019-09/optional/format/idn-hostname.json deleted file mode 100644 index c17b6572d..000000000 --- a/src/test/resources/draft2019-09/optional/format/idn-hostname.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "validation of internationalized host names", - "schema": { - "format": "idn-hostname" - }, - "tests": [ - { - "description": "a valid host name (example.test in Hangul)", - "data": "실례.테스트", - "valid": true - }, - { - "description": "illegal first char U+302E Hangul single dot tone mark", - "data": "〮실례.테스트", - "valid": false - }, - { - "description": "contains illegal char U+302E Hangul single dot tone mark", - "data": "실〮례.테스트", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/ipv4.json b/src/test/resources/draft2019-09/optional/format/ipv4.json deleted file mode 100644 index f8d8df0cc..000000000 --- a/src/test/resources/draft2019-09/optional/format/ipv4.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/ipv6.json b/src/test/resources/draft2019-09/optional/format/ipv6.json deleted file mode 100644 index ee728012d..000000000 --- a/src/test/resources/draft2019-09/optional/format/ipv6.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "a valid IPv6 address", - "data": "::1", - "valid": true - }, - { - "description": "an IPv6 address with out-of-range values", - "data": "12345::", - "valid": false - }, - { - "description": "an IPv6 address with too many components", - "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", - "valid": false - }, - { - "description": "an IPv6 address containing illegal characters", - "data": "::laptop", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/iri-reference.json b/src/test/resources/draft2019-09/optional/format/iri-reference.json deleted file mode 100644 index a82de6389..000000000 --- a/src/test/resources/draft2019-09/optional/format/iri-reference.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "validation of IRI References", - "schema": { - "format": "iri-reference" - }, - "tests": [ - { - "description": "a valid IRI", - "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid protocol-relative IRI Reference", - "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid relative IRI Reference", - "data": "/âππ", - "valid": true - }, - { - "description": "an invalid IRI Reference", - "data": "\\\\WINDOWS\\filëßåré", - "valid": false - }, - { - "description": "a valid IRI Reference", - "data": "âππ", - "valid": true - }, - { - "description": "a valid IRI fragment", - "data": "#ƒrägmênt", - "valid": true - }, - { - "description": "an invalid IRI fragment", - "data": "#ƒräg\\mênt", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/iri.json b/src/test/resources/draft2019-09/optional/format/iri.json deleted file mode 100644 index e0e182ade..000000000 --- a/src/test/resources/draft2019-09/optional/format/iri.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "description": "validation of IRIs", - "schema": { - "format": "iri" - }, - "tests": [ - { - "description": "a valid IRI with anchor tag", - "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid IRI with anchor tag and parantheses", - "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", - "valid": true - }, - { - "description": "a valid IRI with URL-encoded stuff", - "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid IRI with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid IRI based on IPv6", - "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", - "valid": true - }, - { - "description": "an invalid IRI based on IPv6", - "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", - "valid": false - }, - { - "description": "an invalid relative IRI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid IRI", - "data": "\\\\WINDOWS\\filëßåré", - "valid": false - }, - { - "description": "an invalid IRI though valid IRI reference", - "data": "âππ", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/json-pointer.json b/src/test/resources/draft2019-09/optional/format/json-pointer.json deleted file mode 100644 index da3c341e6..000000000 --- a/src/test/resources/draft2019-09/optional/format/json-pointer.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "description": "validation of JSON-pointers (JSON String Representation)", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "a valid JSON-pointer", - "data": "/foo/bar~0/baz~1/%a", - "valid": true - }, - { - "description": "not a valid JSON-pointer (~ not escaped)", - "data": "/foo/bar~", - "valid": false - }, - { - "description": "valid JSON-pointer with empty segment", - "data": "/foo//bar", - "valid": true - }, - { - "description": "valid JSON-pointer with the last empty segment", - "data": "/foo/bar/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #1", - "data": "", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #2", - "data": "/foo", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #3", - "data": "/foo/0", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #4", - "data": "/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #5", - "data": "/a~1b", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #6", - "data": "/c%d", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #7", - "data": "/e^f", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #8", - "data": "/g|h", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #9", - "data": "/i\\j", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #10", - "data": "/k\"l", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #11", - "data": "/ ", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #12", - "data": "/m~0n", - "valid": true - }, - { - "description": "valid JSON-pointer used adding to the last array position", - "data": "/foo/-", - "valid": true - }, - { - "description": "valid JSON-pointer (- used as object member name)", - "data": "/foo/-/bar", - "valid": true - }, - { - "description": "valid JSON-pointer (multiple escaped characters)", - "data": "/~1~0~0~1~1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #1", - "data": "/~1.1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #2", - "data": "/~0.1", - "valid": true - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", - "data": "#", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", - "data": "#/", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", - "data": "#a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #1", - "data": "/~0~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #2", - "data": "/~0/~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #1", - "data": "/~2", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #2", - "data": "/~-1", - "valid": false - }, - { - "description": "not a valid JSON-pointer (multiple characters not escaped)", - "data": "/~~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", - "data": "a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", - "data": "0", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", - "data": "a/a", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/regex.json b/src/test/resources/draft2019-09/optional/format/regex.json deleted file mode 100644 index 7907f6a96..000000000 --- a/src/test/resources/draft2019-09/optional/format/regex.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of regular expressions", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "a valid regular expression", - "data": "([abc])+\\s+$", - "valid": true - }, - { - "description": "a regular expression with unclosed parens is invalid", - "data": "^(abc]", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/relative-json-pointer.json b/src/test/resources/draft2019-09/optional/format/relative-json-pointer.json deleted file mode 100644 index cd30dfa3c..000000000 --- a/src/test/resources/draft2019-09/optional/format/relative-json-pointer.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of Relative JSON Pointers (RJP)", - "schema": { - "format": "relative-json-pointer" - }, - "tests": [ - { - "description": "a valid upwards RJP", - "data": "1", - "valid": true - }, - { - "description": "a valid downwards RJP", - "data": "0/foo/bar", - "valid": true - }, - { - "description": "a valid up and then down RJP, with array index", - "data": "2/0/baz/1/zip", - "valid": true - }, - { - "description": "a valid RJP taking the member or index name", - "data": "0#", - "valid": true - }, - { - "description": "an invalid RJP that is a valid JSON Pointer", - "data": "/foo/bar", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/time.json b/src/test/resources/draft2019-09/optional/format/time.json deleted file mode 100644 index 87ad9097a..000000000 --- a/src/test/resources/draft2019-09/optional/format/time.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "description": "validation of time strings", - "schema": { - "format": "time" - }, - "tests": [ - { - "description": "a valid time string", - "data": "08:30:06.283185Z", - "valid": true - }, - { - "description": "an invalid time string", - "data": "08:30:06 PST", - "valid": false - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "01:01:01,1111", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/uri-reference.json b/src/test/resources/draft2019-09/optional/format/uri-reference.json deleted file mode 100644 index 89756d60b..000000000 --- a/src/test/resources/draft2019-09/optional/format/uri-reference.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "validation of URI References", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "a valid URI", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid relative URI Reference", - "data": "/abc", - "valid": true - }, - { - "description": "an invalid URI Reference", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "a valid URI Reference", - "data": "abc", - "valid": true - }, - { - "description": "a valid URI fragment", - "data": "#fragment", - "valid": true - }, - { - "description": "an invalid URI fragment", - "data": "#frag\\ment", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/uri-template.json b/src/test/resources/draft2019-09/optional/format/uri-template.json deleted file mode 100644 index 58e20b422..000000000 --- a/src/test/resources/draft2019-09/optional/format/uri-template.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "format: uri-template", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "a valid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term}", - "valid": true - }, - { - "description": "an invalid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term", - "valid": false - }, - { - "description": "a valid uri-template without variables", - "data": "http://example.com/dictionary", - "valid": true - }, - { - "description": "a valid relative uri-template", - "data": "dictionary/{term:1}/{term}", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/format/uri.json b/src/test/resources/draft2019-09/optional/format/uri.json deleted file mode 100644 index 1937a0dac..000000000 --- a/src/test/resources/draft2019-09/optional/format/uri.json +++ /dev/null @@ -1,105 +0,0 @@ -[ - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "a valid URL with anchor tag", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid URL with anchor tag and parantheses", - "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", - "valid": true - }, - { - "description": "a valid URL with URL-encoded stuff", - "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid puny-coded URL ", - "data": "http://xn--nw2a.xn--j6w193g/", - "valid": true - }, - { - "description": "a valid URL with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid URL based on IPv4", - "data": "http://223.255.255.254", - "valid": true - }, - { - "description": "a valid URL with ftp scheme", - "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", - "valid": true - }, - { - "description": "a valid URL for a simple text file", - "data": "http://www.ietf.org/rfc/rfc2396.txt", - "valid": true - }, - { - "description": "a valid URL ", - "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", - "valid": true - }, - { - "description": "a valid mailto URI", - "data": "mailto:John.Doe@example.com", - "valid": true - }, - { - "description": "a valid newsgroup URI", - "data": "news:comp.infosystems.www.servers.unix", - "valid": true - }, - { - "description": "a valid tel URI", - "data": "tel:+1-816-555-1212", - "valid": true - }, - { - "description": "a valid URN", - "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - "valid": true - }, - { - "description": "an invalid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": false - }, - { - "description": "an invalid relative URI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid URI", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "an invalid URI though valid URI reference", - "data": "abc", - "valid": false - }, - { - "description": "an invalid URI with spaces", - "data": "http:// shouldfail.com", - "valid": false - }, - { - "description": "an invalid URI with spaces and missing scheme", - "data": ":// should fail", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/optional/zeroTerminatedFloats.json b/src/test/resources/draft2019-09/optional/zeroTerminatedFloats.json deleted file mode 100644 index 046c7a5e3..000000000 --- a/src/test/resources/draft2019-09/optional/zeroTerminatedFloats.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "description": "some languages do not distinguish between different types of numeric value", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a float without fractional part is an integer", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/pattern.json b/src/test/resources/draft2019-09/pattern.json deleted file mode 100644 index 273559706..000000000 --- a/src/test/resources/draft2019-09/pattern.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "description": "pattern validation", - "schema": { - "pattern": "^a*$" - }, - "tests": [ - { - "description": "a matching pattern is valid", - "data": "aaa", - "valid": true - }, - { - "description": "a non-matching pattern is invalid", - "data": "abc", - "valid": false - }, - { - "description": "ignores non-strings", - "data": true, - "valid": true - } - ] - }, - { - "description": "pattern is not anchored", - "schema": { - "pattern": "a+" - }, - "tests": [ - { - "description": "matches a substring", - "data": "xxaayy", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/patternProperties.json b/src/test/resources/draft2019-09/patternProperties.json deleted file mode 100644 index 8823ed971..000000000 --- a/src/test/resources/draft2019-09/patternProperties.json +++ /dev/null @@ -1,202 +0,0 @@ -[ - { - "description": "patternProperties validates properties matching a regex", - "schema": { - "patternProperties": { - "f.*o": { - "type": "integer" - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "multiple valid matches is valid", - "data": { - "foo": 1, - "foooooo": 2 - }, - "valid": true - }, - { - "description": "a single invalid match is invalid", - "data": { - "foo": "bar", - "fooooo": 2 - }, - "valid": false - }, - { - "description": "multiple invalid matches is invalid", - "data": { - "foo": "bar", - "foooooo": "baz" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foo", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "multiple simultaneous patternProperties are validated", - "schema": { - "patternProperties": { - "a*": { - "type": "integer" - }, - "aaa*": { - "maximum": 20 - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "a": 21 - }, - "valid": true - }, - { - "description": "a simultaneous match is valid", - "data": { - "aaaa": 18 - }, - "valid": true - }, - { - "description": "multiple matches is valid", - "data": { - "a": 21, - "aaaa": 18 - }, - "valid": true - }, - { - "description": "an invalid due to one is invalid", - "data": { - "a": "bar" - }, - "valid": false - }, - { - "description": "an invalid due to the other is invalid", - "data": { - "aaaa": 31 - }, - "valid": false - }, - { - "description": "an invalid due to both is invalid", - "data": { - "aaa": "foo", - "aaaa": 31 - }, - "valid": false - } - ] - }, - { - "description": "regexes are not anchored by default and are case sensitive", - "schema": { - "patternProperties": { - "[0-9]{2,}": { - "type": "boolean" - }, - "X_": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "non recognized members are ignored", - "data": { - "answer 1": "42" - }, - "valid": true - }, - { - "description": "recognized members are accounted for", - "data": { - "a31b": null - }, - "valid": false - }, - { - "description": "regexes are case sensitive", - "data": { - "a_x_3": 3 - }, - "valid": true - }, - { - "description": "regexes are case sensitive, 2", - "data": { - "a_X_3": 3 - }, - "valid": false - } - ] - }, - { - "description": "patternProperties with boolean schemas", - "schema": { - "patternProperties": { - "f.*": true, - "b.*": false - } - }, - "tests": [ - { - "description": "object with property matching schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property matching schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/permissive-duration.json b/src/test/resources/draft2019-09/permissive-duration.json new file mode 100644 index 000000000..d148830e0 --- /dev/null +++ b/src/test/resources/draft2019-09/permissive-duration.json @@ -0,0 +1,19 @@ +[ + { + "description": "validation of duration strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "duration" + }, + "tests": [ + { + "description": "weeks can be combined with other units", + "data": "P1Y2W", + "valid": true, + "strictness": { + "duration": false + } + } + ] + } +] diff --git a/src/test/resources/draft2019-09/properties.json b/src/test/resources/draft2019-09/properties.json index daf6708f3..6c96d8922 100644 --- a/src/test/resources/draft2019-09/properties.json +++ b/src/test/resources/draft2019-09/properties.json @@ -1,238 +1,32 @@ [ - { - "description": "object properties validation", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "both properties present and valid is valid", - "data": { - "foo": 1, - "bar": "baz" - }, - "valid": true - }, - { - "description": "one property invalid is invalid", - "data": { - "foo": 1, - "bar": {} - }, - "valid": false - }, - { - "description": "both properties invalid is invalid", - "data": { - "foo": [], - "bar": {} - }, - "valid": false - }, - { - "description": "doesn't invalidate other properties", - "data": { - "quux": [] - }, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "properties, patternProperties, additionalProperties interaction", - "schema": { - "properties": { - "foo": { - "type": "array", - "maxItems": 3 - }, - "bar": { - "type": "array" - } - }, - "patternProperties": { - "f.o": { - "minItems": 2 - } - }, - "additionalProperties": { - "type": "integer" - } - }, - "tests": [ - { - "description": "property validates property", - "data": { - "foo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "property invalidates property", - "data": { - "foo": [ - 1, - 2, - 3, - 4 - ] - }, - "valid": false - }, - { - "description": "patternProperty invalidates property", - "data": { - "foo": [] - }, - "valid": false - }, - { - "description": "patternProperty validates nonproperty", - "data": { - "fxo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "patternProperty invalidates nonproperty", - "data": { - "fxo": [] - }, - "valid": false - }, - { - "description": "additionalProperty ignores property", - "data": { - "bar": [] - }, - "valid": true - }, - { - "description": "additionalProperty validates others", - "data": { - "quux": 3 - }, - "valid": true - }, - { - "description": "additionalProperty invalidates others", - "data": { - "quux": "foo" - }, - "valid": false - } - ] - }, - { - "description": "properties with boolean schema", - "schema": { - "properties": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "no property present is valid", - "data": {}, - "valid": true - }, - { - "description": "only 'true' property present is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "only 'false' property present is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "both properties present is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "properties with escaped characters", - "schema": { - "properties": { - "foo\nbar": { - "type": "number" - }, - "foo\"bar": { - "type": "number" - }, - "foo\\bar": { - "type": "number" - }, - "foo\rbar": { - "type": "number" - }, - "foo\tbar": { - "type": "number" - }, - "foo\fbar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with all numbers is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1", - "foo\\bar": "1", - "foo\rbar": "1", - "foo\tbar": "1", - "foo\fbar": "1" - }, - "valid": false - } - ] - } + { + "description": "object properties validation with required", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"}, + "hello": {"type": "string"}, + "world": {"type": "string"} + }, + "required": [ "bar", "hello", "world" ] + }, + "tests": [ + { + "description": "required hello and world is not present", + "data": {"foo": 1, "bar": "baz"}, + "valid": false + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}, "hello": "v", "world": "b"}, + "valid": false + }, + { + "description": "all valid", + "data": {"foo": 1, "bar": "b", "hello": "c", "world": "d"}, + "valid": true + } + ] + } ] diff --git a/src/test/resources/draft2019-09/propertyNames.json b/src/test/resources/draft2019-09/propertyNames.json deleted file mode 100644 index 54cd1f2ab..000000000 --- a/src/test/resources/draft2019-09/propertyNames.json +++ /dev/null @@ -1,122 +0,0 @@ -[ - { - "description": "propertyNames validation", - "schema": { - "propertyNames": { - "maxLength": 3 - } - }, - "tests": [ - { - "description": "all property names valid", - "data": { - "f": {}, - "foo": {} - }, - "valid": true - }, - { - "description": "some property names invalid", - "data": { - "foo": {}, - "foobar": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "propertyNames validation with pattern", - "schema": { - "propertyNames": { "pattern": "^a+$" } - }, - "tests": [ - { - "description": "matching property names are valid", - "data": { - "a": {}, - "aa": {}, - "aaa": {} - }, - "valid": true - }, - { - "description": "non-matching property name is invalid", - "data": { - "aaA": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema true", - "schema": { - "propertyNames": true - }, - "tests": [ - { - "description": "object with any properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema false", - "schema": { - "propertyNames": false - }, - "tests": [ - { - "description": "object with any properties is invalid", - "data": { - "foo": 1 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft2019-09/ref.json b/src/test/resources/draft2019-09/ref.json deleted file mode 100644 index a17c2dffe..000000000 --- a/src/test/resources/draft2019-09/ref.json +++ /dev/null @@ -1,476 +0,0 @@ -[ - { - "description": "root pointer ref", - "schema": { - "properties": { - "foo": { - "$ref": "#" - } - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "match", - "data": { - "foo": false - }, - "valid": true - }, - { - "description": "recursive match", - "data": { - "foo": { - "foo": false - } - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": false - }, - "valid": false - }, - { - "description": "recursive mismatch", - "data": { - "foo": { - "bar": false - } - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to object", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "$ref": "#/properties/foo" - } - } - }, - "tests": [ - { - "description": "match", - "data": { - "bar": 3 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": true - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to array", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "$ref": "#/items/0" - } - ] - }, - "tests": [ - { - "description": "match array", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "mismatch array", - "data": [ - 1, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "escaped pointer ref", - "schema": { - "tilda~field": { - "type": "integer" - }, - "slash/field": { - "type": "integer" - }, - "percent%field": { - "type": "integer" - }, - "properties": { - "tilda": { - "$ref": "#/tilda~0field" - }, - "slash": { - "$ref": "#/slash~1field" - }, - "percent": { - "$ref": "#/percent%25field" - } - } - }, - "tests": [ - { - "description": "slash invalid", - "data": { - "slash": "aoeu" - }, - "valid": false - }, - { - "description": "tilda invalid", - "data": { - "tilda": "aoeu" - }, - "valid": false - }, - { - "description": "percent invalid", - "data": { - "percent": "aoeu" - }, - "valid": false - }, - { - "description": "slash valid", - "data": { - "slash": 123 - }, - "valid": true - }, - { - "description": "tilda valid", - "data": { - "tilda": 123 - }, - "valid": true - }, - { - "description": "percent valid", - "data": { - "percent": 123 - }, - "valid": true - } - ] - }, - { - "description": "nested refs", - "schema": { - "$defs": { - "a": { - "type": "integer" - }, - "b": { - "$ref": "#/$defs/a" - }, - "c": { - "$ref": "#/$defs/b" - } - }, - "$ref": "#/$defs/c" - }, - "tests": [ - { - "description": "nested ref valid", - "data": 5, - "valid": true - }, - { - "description": "nested ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref overrides any sibling keywords", - "schema": { - "$defs": { - "reffed": { - "type": "array" - } - }, - "properties": { - "foo": { - "$ref": "#/$defs/reffed", - "maxItems": 2 - } - } - }, - "tests": [ - { - "description": "ref valid", - "data": { - "foo": [] - }, - "valid": true - }, - { - "description": "ref valid, maxItems ignored", - "data": { - "foo": [ - 1, - 2, - 3 - ] - }, - "valid": true - }, - { - "description": "ref invalid", - "data": { - "foo": "string" - }, - "valid": false - } - ] - }, - { - "description": "remote ref, containing refs itself", - "schema": { - "$ref": "https://json-schema.org/draft/2019-09/schema" - }, - "tests": [ - { - "description": "remote ref valid", - "data": { - "minLength": 1 - }, - "valid": true - }, - { - "description": "remote ref invalid", - "data": { - "minLength": -1 - }, - "valid": false - } - ] - }, - { - "description": "property named $ref that is not a reference", - "schema": { - "properties": { - "$ref": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "property named $ref valid", - "data": { - "$ref": "a" - }, - "valid": true - }, - { - "description": "property named $ref invalid", - "data": { - "$ref": 2 - }, - "valid": false - } - ] - }, - { - "description": "$ref to boolean schema true", - "schema": { - "$ref": "#/$defs/bool", - "$defs": { - "bool": true - } - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "$ref to boolean schema false", - "schema": { - "$ref": "#/$defs/bool", - "$defs": { - "bool": false - } - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "Recursive references between schemas", - "schema": { - "$id": "http://localhost:1234/tree", - "description": "tree of nodes", - "type": "object", - "properties": { - "meta": { - "type": "string" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "node" - } - } - }, - "required": [ - "meta", - "nodes" - ], - "$defs": { - "node": { - "$id": "http://localhost:1234/node", - "description": "node", - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "subtree": { - "$ref": "tree" - } - }, - "required": [ - "value" - ] - } - } - }, - "tests": [ - { - "description": "valid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 1.1 - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": true - }, - { - "description": "invalid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": "string is invalid" - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": false - } - ] - }, - { - "description": "refs with quote", - "schema": { - "properties": { - "foo\"bar": { - "$ref": "#/$defs/foo%22bar" - } - }, - "$defs": { - "foo\"bar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with numbers is valid", - "data": { - "foo\"bar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\"bar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/refRemote.json b/src/test/resources/draft2019-09/refRemote.json deleted file mode 100644 index a9f8dbd77..000000000 --- a/src/test/resources/draft2019-09/refRemote.json +++ /dev/null @@ -1,166 +0,0 @@ -[ - { - "description": "remote ref", - "schema": { - "$ref": "http://localhost:1234/integer.json" - }, - "tests": [ - { - "description": "remote ref valid", - "data": 1, - "valid": true - }, - { - "description": "remote ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "fragment within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/integer" - }, - "tests": [ - { - "description": "remote fragment valid", - "data": 1, - "valid": true - }, - { - "description": "remote fragment invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" - }, - "tests": [ - { - "description": "ref within ref valid", - "data": 1, - "valid": true - }, - { - "description": "ref within ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "base URI change", - "schema": { - "$id": "http://localhost:1234/", - "items": { - "$id": "folder/", - "items": { - "$ref": "folderInteger.json" - } - } - }, - "tests": [ - { - "description": "base URI change ref valid", - "data": [ - [ - 1 - ] - ], - "valid": true - }, - { - "description": "base URI change ref invalid", - "data": [ - [ - "a" - ] - ], - "valid": false - } - ] - }, - { - "description": "base URI change - change folder", - "schema": { - "$id": "http://localhost:1234/scope_change_defs1.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/$defs/baz" - } - }, - "$defs": { - "baz": { - "$id": "folder/", - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - }, - { - "description": "root ref in remote ref", - "schema": { - "$id": "http://localhost:1234/object", - "type": "object", - "properties": { - "name": { - "$ref": "name-defs.json#/$defs/orNull" - } - } - }, - "tests": [ - { - "description": "string is valid", - "data": { - "name": "foo" - }, - "valid": true - }, - { - "description": "null is valid", - "data": { - "name": null - }, - "valid": true - }, - { - "description": "object is invalid", - "data": { - "name": { - "name": null - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/refRemote_ignored.json b/src/test/resources/draft2019-09/refRemote_ignored.json deleted file mode 100644 index 983556cf5..000000000 --- a/src/test/resources/draft2019-09/refRemote_ignored.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "description": "base URI change - change folder in subschema", - "schema": { - "$id": "http://localhost:1234/scope_change_defs2.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/$defs/baz/$defs/bar" - } - }, - "$defs": { - "baz": { - "$id": "folder/", - "$defs": { - "bar": { - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/src/test/resources/draft2019-09/required.json b/src/test/resources/draft2019-09/required.json deleted file mode 100644 index 6358d4e1f..000000000 --- a/src/test/resources/draft2019-09/required.json +++ /dev/null @@ -1,111 +0,0 @@ -[ - { - "description": "required validation", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "required": [ - "foo" - ] - }, - "tests": [ - { - "description": "present required property is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "non-present required property is invalid", - "data": { - "bar": 1 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "required default validation", - "schema": { - "properties": { - "foo": {} - } - }, - "tests": [ - { - "description": "not required by default", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with empty array", - "schema": { - "properties": { - "foo": {} - }, - "required": [] - }, - "tests": [ - { - "description": "property not required", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with escaped characters", - "schema": { - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ] - }, - "tests": [ - { - "description": "object with all properties present is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with some properties missing is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/type.json b/src/test/resources/draft2019-09/type.json deleted file mode 100644 index 83348a468..000000000 --- a/src/test/resources/draft2019-09/type.json +++ /dev/null @@ -1,504 +0,0 @@ -[ - { - "description": "integer type matches integers", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "an integer is an integer", - "data": 1, - "valid": true - }, - { - "description": "a float is not an integer", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an integer", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not an integer, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not an integer", - "data": {}, - "valid": false - }, - { - "description": "an array is not an integer", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an integer", - "data": true, - "valid": false - }, - { - "description": "null is not an integer", - "data": null, - "valid": false - } - ] - }, - { - "description": "number type matches numbers", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "an integer is a number", - "data": 1, - "valid": true - }, - { - "description": "a float is a number", - "data": 1.1, - "valid": true - }, - { - "description": "a string is not a number", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not a number, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not a number", - "data": {}, - "valid": false - }, - { - "description": "an array is not a number", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a number", - "data": true, - "valid": false - }, - { - "description": "null is not a number", - "data": null, - "valid": false - } - ] - }, - { - "description": "string type matches strings", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "1 is not a string", - "data": 1, - "valid": false - }, - { - "description": "a float is not a string", - "data": 1.1, - "valid": false - }, - { - "description": "a string is a string", - "data": "foo", - "valid": true - }, - { - "description": "a string is still a string, even if it looks like a number", - "data": "1", - "valid": true - }, - { - "description": "an empty string is still a string", - "data": "", - "valid": true - }, - { - "description": "an object is not a string", - "data": {}, - "valid": false - }, - { - "description": "an array is not a string", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a string", - "data": true, - "valid": false - }, - { - "description": "null is not a string", - "data": null, - "valid": false - } - ] - }, - { - "description": "object type matches objects", - "schema": { - "type": "object" - }, - "tests": [ - { - "description": "an integer is not an object", - "data": 1, - "valid": false - }, - { - "description": "a float is not an object", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an object", - "data": "foo", - "valid": false - }, - { - "description": "an object is an object", - "data": {}, - "valid": true - }, - { - "description": "an array is not an object", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an object", - "data": true, - "valid": false - }, - { - "description": "null is not an object", - "data": null, - "valid": false - } - ] - }, - { - "description": "array type matches arrays", - "schema": { - "type": "array" - }, - "tests": [ - { - "description": "an integer is not an array", - "data": 1, - "valid": false - }, - { - "description": "a float is not an array", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an array", - "data": "foo", - "valid": false - }, - { - "description": "an object is not an array", - "data": {}, - "valid": false - }, - { - "description": "an array is an array", - "data": [], - "valid": true - }, - { - "description": "a boolean is not an array", - "data": true, - "valid": false - }, - { - "description": "null is not an array", - "data": null, - "valid": false - } - ] - }, - { - "description": "boolean type matches booleans", - "schema": { - "type": "boolean" - }, - "tests": [ - { - "description": "an integer is not a boolean", - "data": 1, - "valid": false - }, - { - "description": "zero is not a boolean", - "data": 0, - "valid": false - }, - { - "description": "a float is not a boolean", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not a boolean", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not a boolean", - "data": "", - "valid": false - }, - { - "description": "an object is not a boolean", - "data": {}, - "valid": false - }, - { - "description": "an array is not a boolean", - "data": [], - "valid": false - }, - { - "description": "true is a boolean", - "data": true, - "valid": true - }, - { - "description": "false is a boolean", - "data": false, - "valid": true - }, - { - "description": "null is not a boolean", - "data": null, - "valid": false - } - ] - }, - { - "description": "null type matches only the null object", - "schema": { - "type": "null" - }, - "tests": [ - { - "description": "an integer is not null", - "data": 1, - "valid": false - }, - { - "description": "a float is not null", - "data": 1.1, - "valid": false - }, - { - "description": "zero is not null", - "data": 0, - "valid": false - }, - { - "description": "a string is not null", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not null", - "data": "", - "valid": false - }, - { - "description": "an object is not null", - "data": {}, - "valid": false - }, - { - "description": "an array is not null", - "data": [], - "valid": false - }, - { - "description": "true is not null", - "data": true, - "valid": false - }, - { - "description": "false is not null", - "data": false, - "valid": false - }, - { - "description": "null is null", - "data": null, - "valid": true - } - ] - }, - { - "description": "multiple types can be specified in an array", - "schema": { - "type": [ - "integer", - "string" - ] - }, - "tests": [ - { - "description": "an integer is valid", - "data": 1, - "valid": true - }, - { - "description": "a string is valid", - "data": "foo", - "valid": true - }, - { - "description": "a float is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "an object is invalid", - "data": {}, - "valid": false - }, - { - "description": "an array is invalid", - "data": [], - "valid": false - }, - { - "description": "a boolean is invalid", - "data": true, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type as array with one item", - "schema": { - "type": [ - "string" - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "type: array or object", - "schema": { - "type": [ - "array", - "object" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type: array, object or null", - "schema": { - "type": [ - "array", - "object", - "null" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2019-09/uniqueItems.json b/src/test/resources/draft2019-09/uniqueItems.json deleted file mode 100644 index c64cdbbda..000000000 --- a/src/test/resources/draft2019-09/uniqueItems.json +++ /dev/null @@ -1,328 +0,0 @@ -[ - { - "description": "uniqueItems validation", - "schema": { - "uniqueItems": true - }, - "tests": [ - { - "description": "unique array of integers is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "non-unique array of integers is invalid", - "data": [ - 1, - 1 - ], - "valid": false - }, - { - "description": "numbers are unique if mathematically unequal", - "data": [ - 1.0, - 1.00, - 1 - ], - "valid": false - }, - { - "description": "false is not equal to zero", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "true is not equal to one", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "unique array of objects is valid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "baz" - } - ], - "valid": true - }, - { - "description": "non-unique array of objects is invalid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "bar" - } - ], - "valid": false - }, - { - "description": "unique array of nested objects is valid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": false - } - } - } - ], - "valid": true - }, - { - "description": "non-unique array of nested objects is invalid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": true - } - } - } - ], - "valid": false - }, - { - "description": "unique array of arrays is valid", - "data": [ - [ - "foo" - ], - [ - "bar" - ] - ], - "valid": true - }, - { - "description": "non-unique array of arrays is invalid", - "data": [ - [ - "foo" - ], - [ - "foo" - ] - ], - "valid": false - }, - { - "description": "1 and true are unique", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "0 and false are unique", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "unique heterogeneous types are valid", - "data": [ - {}, - [ - 1 - ], - true, - null, - 1 - ], - "valid": true - }, - { - "description": "non-unique heterogeneous types are invalid", - "data": [ - {}, - [ - 1 - ], - true, - null, - {}, - 1 - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "unique array extended from [false, true] is valid", - "data": [ - false, - true, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "unique array extended from [true, false] is valid", - "data": [ - true, - false, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "non-unique array extended from [false, true] is not valid", - "data": [ - false, - true, - "foo", - "foo" - ], - "valid": false - }, - { - "description": "non-unique array extended from [true, false] is not valid", - "data": [ - true, - false, - "foo", - "foo" - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items and additionalItems=false", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true, - "additionalItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "extra items are invalid even if unique", - "data": [ - false, - true, - null - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft2020-12/invalid-min-max-contains.json b/src/test/resources/draft2020-12/invalid-min-max-contains.json new file mode 100644 index 000000000..8b35ce883 --- /dev/null +++ b/src/test/resources/draft2020-12/invalid-min-max-contains.json @@ -0,0 +1,123 @@ +[ + { + "description": "minContains is not a number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": "1" + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"minContains\":\"1\"}" + ] + } + ] + }, + { + "description": "minContains is not an integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": 0.5 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"minContains\":0.5}" + ] + } + ] + }, + { + "description": "minContains is a negative number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": -1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"minContains\":-1}" + ] + } + ] + }, + { + "description": "maxContains is not a number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": "1" + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"maxContains\":\"1\"}" + ] + } + ] + }, + { + "description": "maxContains is not an integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": 0.5 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"maxContains\":0.5}" + ] + } + ] + }, + { + "description": "maxContains is a negative number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": -1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": must be a non-negative integer in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"maxContains\":-1}" + ] + } + ] + }, + { + "description": "maxContains is less than minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": 0, + "minContains": 1 + }, + "tests": [ + { + "description": "should fail", + "data": [], + "valid": false, + "errors": [ + ": minContains must less than or equal to maxContains in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"maxContains\":0,\"minContains\":1}", + ": minContains must less than or equal to maxContains in {\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"maxContains\":0,\"minContains\":1}" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft2020-12/issue495.json b/src/test/resources/draft2020-12/issue495.json new file mode 100644 index 000000000..a16a37216 --- /dev/null +++ b/src/test/resources/draft2020-12/issue495.json @@ -0,0 +1,108 @@ +[ + { + "description": "issue495 using ECMA-262", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^[a-z]{1,10}$": true, + "(^1$)": true + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "an expected property name", + "regex": "ecma-262", + "data": { "aaa": 3 }, + "valid": true + }, + { + "description": "another expected property name", + "regex": "jdk", + "data": { "1": 3 }, + "valid": true + }, + { + "description": "trailing newline", + "regex": "ecma-262", + "data": { "aaa\n": 3 }, + "valid": false, + "disabled": true, + "comment": "Test fails" + }, + { + "description": "another trailing newline", + "regex": "jdk", + "data": { "1\n": 3 }, + "valid": false, + "disabled": true, + "comment": "Test fails" + }, + { + "description": "embedded newline", + "regex": "ecma-262", + "data": { "aaa\nbbb": 3 }, + "valid": false + }, + { + "description": "leading newline", + "regex": "ecma-262", + "data": { "\nbbb": 3 }, + "valid": false + } + ] + }, + { + "description": "issue495 using Java Pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^[a-z]{1,10}$": true, + "(^1$)": true + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "an expected property name", + "regex": "jdk", + "data": { "aaa": 3 }, + "valid": true + }, + { + "description": "another expected property name", + "regex": "jdk", + "data": { "1": 3 }, + "valid": true + }, + { + "description": "trailing newline", + "regex": "jdk", + "data": { "aaa\n": 3 }, + "valid": false, + "disabled": true, + "comment": "Test fails" + }, + { + "description": "another trailing newline", + "regex": "jdk", + "data": { "1\n": 3 }, + "valid": false, + "disabled": true, + "comment": "Test fails" + }, + { + "description": "embedded newline", + "regex": "jdk", + "data": { "aaa\nbbb": 3 }, + "valid": false + }, + { + "description": "leading newline", + "regex": "jdk", + "data": { "\nbbb": 3 }, + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft2020-12/issue656.json b/src/test/resources/draft2020-12/issue656.json new file mode 100644 index 000000000..fc012ce0f --- /dev/null +++ b/src/test/resources/draft2020-12/issue656.json @@ -0,0 +1,163 @@ +[ + { + "description": "issue656 - Should be valid to one and only one of schema, but more than one are valid error", + "schema": { + "$id": "someid", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "JSON Schema for treatments", + "oneOf": [ + { + "$ref": "#/$defs/drug-treatment" + }, + { + "$ref": "#/$defs/surgery-treatment" + } + ], + "$defs": { + "base": { + "type": "object", + "properties": { + "type-tag": { + "enum": [ + "SURGERY", + "DRUGTREATMENT", + "RADIOLOGY", + "PHYSIOTHERAPY" + ] + }, + "id": { + "type": "string", + "format": "uuid" + }, + "patient-id": { + "type": "string", + "format": "uuid" + }, + "patient-name": { + "type": "string" + }, + "provider-id": { + "type": "string", + "format": "uuid" + }, + "provider-name": { + "type": "string" + }, + "diagnosis": { + "type": "string" + }, + "followup-treatments": { + "type": "array", + "items": { + "$ref": "#" + } + } + }, + "required": [ + "id", + "type-tag", + "patient-id", + "patient-name", + "provider-id", + "provider-name", + "diagnosis", + "followup-treatments" + ] + }, + "drug-treatment": { + "allOf": [ + { + "$ref": "#/$defs/base" + } + ], + "properties": { + "drug": { + "type": "string" + }, + "dosage": { + "type": "number" + }, + "start-date": { + "type": "string", + "format": "date" + }, + "end-date": { + "type": "string", + "format": "date" + }, + "frequency": "integer" + }, + "required": [ + "drug", + "dosage", + "start-date", + "end-date", + "frequency" + ], + "unevaluatedProperties": false + }, + "surgery-treatment": { + "allOf": [ + { + "$ref": "#/$defs/base" + } + ], + "properties": { + "surgery-date": { + "type": "string", + "format": "date" + }, + "discharge-instructions": { + "type": "string" + }, + "required": [ + "surgery-date", + "discharge-instructions" + ], + "unevaluatedProperties": false + } + } + } + }, + "tests": [ + { + "description": "Sample 1", + "data": { + "type-tag": "SURGERY", + "surgery-date": "2222-02-12", + "discharge-instructions": "dsfdsfdsfds", + "id": "12d2e565-8966-4029-840b-1959277b37f6", + "patient-id": "ab62420e-0bd8-4e39-8e0b-36e464b7abb2", + "patient-name": "Tom", + "provider-id": "154523b2-7598-4ed4-aab1-b2ef1692109c", + "provider-name": "gdsfdsd", + "diagnosis": "fdsfds", + "followup-treatments": [] + }, + "valid": true + }, + { + "description": "Sample 2", + "data": { + "type-tag": "DRUGTREATMENT", + "drug": "fdsds", + "dosage": 2.0, + "start-date": "2222-02-12", + "end-date": "2222-02-12", + "frequency": 2, + "id": "aa7da984-0252-45b1-b0cd-f1dbe98662e2", + "patient-id": "ab62420e-0bd8-4e39-8e0b-36e464b7abb2", + "patient-name": "Tom", + "provider-id": "154523b2-7598-4ed4-aab1-b2ef1692109c", + "provider-name": "gdsfdsd", + "diagnosis": "sfdsfds", + "followup-treatments": [] + }, + "valid": false, + "errors": [ + ": must be valid to one and only one schema, but 2 are valid with indexes '0, 1'" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft2020-12/issue782.json b/src/test/resources/draft2020-12/issue782.json new file mode 100644 index 000000000..1d93f33f9 --- /dev/null +++ b/src/test/resources/draft2020-12/issue782.json @@ -0,0 +1,102 @@ +[ + { + "description": "issue782 using ECMA-262", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^x-": true, + "y-$": true, + "^z-$": true + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "regexes may be anchored to the start of the property name, 1", + "regex": "ecma-262", + "data": { "x-api-id": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to the start of the property name, 2", + "regex": "ecma-262", + "data": { "ax-api-id": 3 }, + "valid": false + }, + { + "description": "regexes may be anchored to the end of the property name, 1", + "regex": "ecma-262", + "data": { "api-id-y-": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to the end of the property name, 2", + "regex": "ecma-262", + "data": { "y-api-id": 3 }, + "valid": false + }, + { + "description": "regexes may be anchored to both ends of the property name, 1", + "regex": "ecma-262", + "data": { "z-": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to both ends of the property name, 2", + "regex": "ecma-262", + "data": { "az-api-id": 3 }, + "valid": false + } + ] + }, + { + "description": "issue782 using Java Pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^x-": true, + "y-$": true, + "^z-$": true + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "regexes may be anchored to the start of the property name, 1", + "regex": "jdk", + "data": { "x-api-id": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to the start of the property name, 2", + "regex": "jdk", + "data": { "ax-api-id": 3 }, + "valid": false + }, + { + "description": "regexes may be anchored to the end of the property name, 1", + "regex": "jdk", + "data": { "api-id-y-": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to the end of the property name, 2", + "regex": "jdk", + "data": { "y-api-id": 3 }, + "valid": false + }, + { + "description": "regexes may be anchored to both ends of the property name, 1", + "regex": "jdk", + "data": { "z-": 3 }, + "valid": true + }, + { + "description": "regexes may be anchored to both ends of the property name, 2", + "regex": "jdk", + "data": { "az-api-id": 3 }, + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft2020-12/issue798.json b/src/test/resources/draft2020-12/issue798.json new file mode 100644 index 000000000..b5875ff29 --- /dev/null +++ b/src/test/resources/draft2020-12/issue798.json @@ -0,0 +1,46 @@ +[ + { + "description": "issue798", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "a": { "type": "string" }, + "b": { "type": "string", "readOnly": true }, + "c": { "type": "string", "writeOnly": true }, + "d": { "type": "string", "readOnly": true, "writeOnly": true } + } + }, + "tests": [ + { + "description": "default behavior", + "data": { "a": "a string" }, + "valid": true, + "config": { "readOnly": true, "writeOnly": true } + }, + { + "description": "readonly behavior", + "data": { "a": "a string", "b": "a string" }, + "valid": false, + "config": { "readOnly": true, "writeOnly": true }, + "errors": [ "/b: is a readonly field, it cannot be changed" ] + }, + { + "description": "write-only behavior", + "data": { "a": "a string", "c": "a string" }, + "valid": false, + "config": { "readOnly": true, "writeOnly": true }, + "errors": [ "/c: is a write-only field, it cannot appear in the data" ] + }, + { + "description": "both behavior", + "data": { "a": "a string", "d": "a string" }, + "valid": false, + "config": { "readOnly": true, "writeOnly": true }, + "errors": [ + "/d: is a readonly field, it cannot be changed", + "/d: is a write-only field, it cannot appear in the data" + ] + } + ] + } +] diff --git a/src/test/resources/draft2020-12/schemaTag.json b/src/test/resources/draft2020-12/schemaTag.json new file mode 100644 index 000000000..d22b883a8 --- /dev/null +++ b/src/test/resources/draft2020-12/schemaTag.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema" +} \ No newline at end of file diff --git a/src/test/resources/draft4/additionalItems.json b/src/test/resources/draft4/additionalItems.json deleted file mode 100644 index 784bc8461..000000000 --- a/src/test/resources/draft4/additionalItems.json +++ /dev/null @@ -1,149 +0,0 @@ -[ - { - "description": "additionalItems as schema", - "schema": { - "items": [{}], - "additionalItems": {"type": "integer"} - }, - "tests": [ - { - "description": "additional items match schema", - "data": [ null, 2, 3, 4 ], - "valid": true - }, - { - "description": "additional items do not match schema", - "data": [ null, 2, 3, "foo" ], - "valid": false - } - ] - }, - { - "description": "when items is schema, additionalItems does nothing", - "schema": { - "items": {}, - "additionalItems": false - }, - "tests": [ - { - "description": "all items match schema", - "data": [ 1, 2, 3, 4, 5 ], - "valid": true - } - ] - }, - { - "description": "array of items with no additionalItems permitted", - "schema": { - "items": [{}, {}, {}], - "additionalItems": false - }, - "tests": [ - { - "description": "empty array", - "data": [ ], - "valid": true - }, - { - "description": "fewer number of items present (1)", - "data": [ 1 ], - "valid": true - }, - { - "description": "fewer number of items present (2)", - "data": [ 1, 2 ], - "valid": true - }, - { - "description": "equal number of items present", - "data": [ 1, 2, 3 ], - "valid": true - }, - { - "description": "additional items are not permitted", - "data": [ 1, 2, 3, 4 ], - "valid": false - } - ] - }, - { - "description": "additionalItems as false without items", - "schema": {"additionalItems": false}, - "tests": [ - { - "description": - "items defaults to empty schema so everything is valid", - "data": [ 1, 2, 3, 4, 5 ], - "valid": true - }, - { - "description": "ignores non-arrays", - "data": {"foo" : "bar"}, - "valid": true - } - ] - }, - { - "description": "additionalItems are allowed by default", - "schema": {"items": [{"type": "integer"}]}, - "tests": [ - { - "description": "only the first item is validated", - "data": [1, "foo", false], - "valid": true - } - ] - }, - { - "description": "additionalItems should not look in applicators, valid case", - "schema": { - "allOf": [ - { "items": [ { "type": "integer" } ] } - ], - "additionalItems": { "type": "boolean" } - }, - "tests": [ - { - "description": "items defined in allOf are not examined", - "data": [ 1, null ], - "valid": true - } - ] - }, - { - "description": "additionalItems should not look in applicators, invalid case", - "schema": { - "allOf": [ - { "items": [ { "type": "integer" }, { "type": "string" } ] } - ], - "items": [ {"type": "integer" } ], - "additionalItems": { "type": "boolean" } - }, - "tests": [ - { - "description": "items defined in allOf are not examined", - "data": [ 1, "hello" ], - "valid": false - } - ] - }, - { - "description": "items validation adjusts the starting index for additionalItems", - "schema": { - "items": [ { "type": "string" } ], - "additionalItems": { "type": "integer" } - }, - "tests": [ - { - "description": "valid items", - "data": [ "x", 2, 3 ], - "valid": true - }, - { - "description": "wrong type of second item", - "data": [ "x", "y" ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/additionalProperties.json b/src/test/resources/draft4/additionalProperties.json deleted file mode 100644 index 381275a59..000000000 --- a/src/test/resources/draft4/additionalProperties.json +++ /dev/null @@ -1,133 +0,0 @@ -[ - { - "description": - "additionalProperties being false does not allow other properties", - "schema": { - "properties": {"foo": {}, "bar": {}}, - "patternProperties": { "^v": {} }, - "additionalProperties": false - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "an additional property is invalid", - "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [1, 2, 3], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobarbaz", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - }, - { - "description": "patternProperties are not additional properties", - "data": {"foo":1, "vroom": 2}, - "valid": true - } - ] - }, - { - "description": "non-ASCII pattern with additionalProperties", - "schema": { - "patternProperties": {"^á": {}}, - "additionalProperties": false - }, - "tests": [ - { - "description": "matching the pattern is valid", - "data": {"ármányos": 2}, - "valid": true - }, - { - "description": "not matching the pattern is invalid", - "data": {"élmény": 2}, - "valid": false - } - ] - }, - { - "description": - "additionalProperties allows a schema which should validate", - "schema": { - "properties": {"foo": {}, "bar": {}}, - "additionalProperties": {"type": "boolean"} - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "an additional valid property is valid", - "data": {"foo" : 1, "bar" : 2, "quux" : true}, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": {"foo" : 1, "bar" : 2, "quux" : 12}, - "valid": false - } - ] - }, - { - "description": - "additionalProperties can exist by itself", - "schema": { - "additionalProperties": {"type": "boolean"} - }, - "tests": [ - { - "description": "an additional valid property is valid", - "data": {"foo" : true}, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": {"foo" : 1}, - "valid": false - } - ] - }, - { - "description": "additionalProperties are allowed by default", - "schema": {"properties": {"foo": {}, "bar": {}}}, - "tests": [ - { - "description": "additional properties are allowed", - "data": {"foo": 1, "bar": 2, "quux": true}, - "valid": true - } - ] - }, - { - "description": "additionalProperties should not look in applicators", - "schema": { - "allOf": [ - {"properties": {"foo": {}}} - ], - "additionalProperties": {"type": "boolean"} - }, - "tests": [ - { - "description": "properties defined in allOf are not examined", - "data": {"foo": 1, "bar": true}, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/anyOf.json b/src/test/resources/draft4/anyOf.json deleted file mode 100644 index f8d82e87c..000000000 --- a/src/test/resources/draft4/anyOf.json +++ /dev/null @@ -1,182 +0,0 @@ -[ - { - "description": "anyOf", - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first anyOf valid", - "data": 1, - "valid": true - }, - { - "description": "second anyOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both anyOf valid", - "data": 3, - "valid": true - }, - { - "description": "neither anyOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "anyOf with base schema", - "schema": { - "type": "string", - "anyOf" : [ - { - "maxLength": 2 - }, - { - "minLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one anyOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both anyOf invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf complex types", - "schema": { - "anyOf": [ - { - "properties": { - "bar": {"type": "integer"} - }, - "required": ["bar"] - }, - { - "properties": { - "foo": {"type": "string"} - }, - "required": ["foo"] - } - ] - }, - "tests": [ - { - "description": "first anyOf valid (complex)", - "data": {"bar": 2}, - "valid": true - }, - { - "description": "second anyOf valid (complex)", - "data": {"foo": "baz"}, - "valid": true - }, - { - "description": "both anyOf valid (complex)", - "data": {"foo": "baz", "bar": 2}, - "valid": true - }, - { - "description": "neither anyOf valid (complex)", - "data": {"foo": 2, "bar": "quux"}, - "valid": false - } - ] - }, - { - "description": "anyOf with one empty schema", - "schema": { - "anyOf": [ - { "type": "number" }, - {} - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is valid", - "data": 123, - "valid": true - } - ] - }, - { - "description": "nested anyOf, to check validation semantics", - "schema": { - "anyOf": [ - { - "anyOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "nested anyOf, to check validation semantics", - "schema": { - "anyOf": [ - { - "anyOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/complex.json b/src/test/resources/draft4/complex.json similarity index 100% rename from src/test/resources/draft4/optional/complex.json rename to src/test/resources/draft4/complex.json diff --git a/src/test/resources/draft4/dependencies.json b/src/test/resources/draft4/dependencies.json deleted file mode 100644 index 51eeddf32..000000000 --- a/src/test/resources/draft4/dependencies.json +++ /dev/null @@ -1,194 +0,0 @@ -[ - { - "description": "dependencies", - "schema": { - "dependencies": {"bar": ["foo"]} - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependant", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "with dependency", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "missing dependency", - "data": {"bar": 2}, - "valid": false - }, - { - "description": "ignores arrays", - "data": ["bar"], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "multiple dependencies", - "schema": { - "dependencies": {"quux": ["foo", "bar"]} - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependants", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "with dependencies", - "data": {"foo": 1, "bar": 2, "quux": 3}, - "valid": true - }, - { - "description": "missing dependency", - "data": {"foo": 1, "quux": 2}, - "valid": false - }, - { - "description": "missing other dependency", - "data": {"bar": 1, "quux": 2}, - "valid": false - }, - { - "description": "missing both dependencies", - "data": {"quux": 1}, - "valid": false - } - ] - }, - { - "description": "multiple dependencies subschema", - "schema": { - "dependencies": { - "bar": { - "properties": { - "foo": {"type": "integer"}, - "bar": {"type": "integer"} - } - } - } - }, - "tests": [ - { - "description": "valid", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "no dependency", - "data": {"foo": "quux"}, - "valid": true - }, - { - "description": "wrong type", - "data": {"foo": "quux", "bar": 2}, - "valid": false - }, - { - "description": "wrong type other", - "data": {"foo": 2, "bar": "quux"}, - "valid": false - }, - { - "description": "wrong type both", - "data": {"foo": "quux", "bar": "quux"}, - "valid": false - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "dependencies": { - "foo\nbar": ["foo\rbar"], - "foo\tbar": { - "minProperties": 4 - }, - "foo'bar": {"required": ["foo\"bar"]}, - "foo\"bar": ["foo'bar"] - } - }, - "tests": [ - { - "description": "valid object 1", - "data": { - "foo\nbar": 1, - "foo\rbar": 2 - }, - "valid": true - }, - { - "description": "valid object 2", - "data": { - "foo\tbar": 1, - "a": 2, - "b": 3, - "c": 4 - }, - "valid": true - }, - { - "description": "valid object 3", - "data": { - "foo'bar": 1, - "foo\"bar": 2 - }, - "valid": true - }, - { - "description": "invalid object 1", - "data": { - "foo\nbar": 1, - "foo": 2 - }, - "valid": false - }, - { - "description": "invalid object 2", - "data": { - "foo\tbar": 1, - "a": 2 - }, - "valid": false - }, - { - "description": "invalid object 3", - "data": { - "foo'bar": 1 - }, - "valid": false - }, - { - "description": "invalid object 4", - "data": { - "foo\"bar": 2 - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/enum.json b/src/test/resources/draft4/enum.json deleted file mode 100644 index f085097be..000000000 --- a/src/test/resources/draft4/enum.json +++ /dev/null @@ -1,236 +0,0 @@ -[ - { - "description": "simple enum validation", - "schema": {"enum": [1, 2, 3]}, - "tests": [ - { - "description": "one of the enum is valid", - "data": 1, - "valid": true - }, - { - "description": "something else is invalid", - "data": 4, - "valid": false - } - ] - }, - { - "description": "heterogeneous enum validation", - "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, - "tests": [ - { - "description": "one of the enum is valid", - "data": [], - "valid": true - }, - { - "description": "something else is invalid", - "data": null, - "valid": false - }, - { - "description": "objects are deep compared", - "data": {"foo": false}, - "valid": false - }, - { - "description": "valid object matches", - "data": {"foo": 12}, - "valid": true - }, - { - "description": "extra properties in object is invalid", - "data": {"foo": 12, "boo": 42}, - "valid": false - } - ] - }, - { - "description": "heterogeneous enum-with-null validation", - "schema": { "enum": [6, null] }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "number is valid", - "data": 6, - "valid": true - }, - { - "description": "something else is invalid", - "data": "test", - "valid": false - } - ] - }, - { - "description": "enums in properties", - "schema": { - "type":"object", - "properties": { - "foo": {"enum":["foo"]}, - "bar": {"enum":["bar"]} - }, - "required": ["bar"] - }, - "tests": [ - { - "description": "both properties are valid", - "data": {"foo":"foo", "bar":"bar"}, - "valid": true - }, - { - "description": "wrong foo value", - "data": {"foo":"foot", "bar":"bar"}, - "valid": false - }, - { - "description": "wrong bar value", - "data": {"foo":"foo", "bar":"bart"}, - "valid": false - }, - { - "description": "missing optional property is valid", - "data": {"bar":"bar"}, - "valid": true - }, - { - "description": "missing required property is invalid", - "data": {"foo":"foo"}, - "valid": false - }, - { - "description": "missing all properties is invalid", - "data": {}, - "valid": false - } - ] - }, - { - "description": "enum with escaped characters", - "schema": { - "enum": ["foo\nbar", "foo\rbar"] - }, - "tests": [ - { - "description": "member 1 is valid", - "data": "foo\nbar", - "valid": true - }, - { - "description": "member 2 is valid", - "data": "foo\rbar", - "valid": true - }, - { - "description": "another string is invalid", - "data": "abc", - "valid": false - } - ] - }, - { - "description": "enum with false does not match 0", - "schema": {"enum": [false]}, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "enum with true does not match 1", - "schema": {"enum": [true]}, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "enum with 0 does not match false", - "schema": {"enum": [0]}, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "enum with 1 does not match true", - "schema": {"enum": [1]}, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - }, - { - "description": "nul characters in strings", - "schema": { "enum": [ "hello\u0000there" ] }, - "tests": [ - { - "description": "match string with nul", - "data": "hello\u0000there", - "valid": true - }, - { - "description": "do not match string lacking nul", - "data": "hellothere", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/extra/classpath/schema.json b/src/test/resources/draft4/extra/classpath/schema.json index bb46c7e80..6b783b038 100644 --- a/src/test/resources/draft4/extra/classpath/schema.json +++ b/src/test/resources/draft4/extra/classpath/schema.json @@ -13,12 +13,14 @@ }, "tests": [ { + "description": "an integer is an integer", "data": { "features": 4 }, "valid": true }, { + "description": "a number is not an integer", "data": { "features": 4.0 }, diff --git a/src/test/resources/draft4/extra/property.json b/src/test/resources/draft4/extra/property.json index a1069a6b8..c1a51275a 100644 --- a/src/test/resources/draft4/extra/property.json +++ b/src/test/resources/draft4/extra/property.json @@ -17,6 +17,7 @@ }, "tests": [ { + "description": "an integer is an integer", "data": { "featuresInteger": 4, "featuresBoolean": true @@ -24,6 +25,7 @@ "valid": true }, { + "description": "a number is not an integer", "data": { "featuresInteger": 4.0, "featuresBoolean": true diff --git a/src/test/resources/draft4/extra/uri_mapping/invalid-schema-uri.json b/src/test/resources/draft4/extra/uri_mapping/invalid-schema-uri.json deleted file mode 100644 index 34e8dfefd..000000000 --- a/src/test/resources/draft4/extra/uri_mapping/invalid-schema-uri.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "publicURL": "http://json-schema.org/draft-04/schema#", - "localURL": "resource:/draftv4.schema.json" - }, - { - "publicURL": "http://example.com/invalid/schema/url", - "localURL": "resource:/draft4/extra/uri_mapping/example-schema.json" - } -] diff --git a/src/test/resources/draft4/extra/uri_mapping/schema-with-ref-mapping.json b/src/test/resources/draft4/extra/uri_mapping/schema-with-ref-mapping.json deleted file mode 100644 index 34e8dfefd..000000000 --- a/src/test/resources/draft4/extra/uri_mapping/schema-with-ref-mapping.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "publicURL": "http://json-schema.org/draft-04/schema#", - "localURL": "resource:/draftv4.schema.json" - }, - { - "publicURL": "http://example.com/invalid/schema/url", - "localURL": "resource:/draft4/extra/uri_mapping/example-schema.json" - } -] diff --git a/src/test/resources/draft4/issue258/Element.json b/src/test/resources/draft4/issue258/Element.json new file mode 100644 index 000000000..0361c5fd2 --- /dev/null +++ b/src/test/resources/draft4/issue258/Element.json @@ -0,0 +1,19 @@ +{ + "id": "Element.json", + "type": "object", + "datatype": true, + "category": "fhir", + "properties": { + "id": { + "type": "string", + "pattern": "[A-Za-z0-9\\-\\.]{1,64}" + }, + "extension": { + "type": "array", + "items": { + "$ref": "Extension.json" + } + } + }, + "additionalProperties": false +} diff --git a/src/test/resources/draft4/issue258/Extension.json b/src/test/resources/draft4/issue258/Extension.json new file mode 100644 index 000000000..c1ffb6b04 --- /dev/null +++ b/src/test/resources/draft4/issue258/Extension.json @@ -0,0 +1,19 @@ +{ + "id": "Extension.json", + "type": "object", + "datatype": true, + "category": "fhir", + "properties": { + "url": { + "type": "string", + "format": "url" + }, + "valueElement": { + "$ref": "Element.json" + } + }, + "additionalProperties": false, + "required": [ + "url" + ] +} diff --git a/src/test/resources/draft4/issue258/Master.json b/src/test/resources/draft4/issue258/Master.json new file mode 100644 index 000000000..01bc4ad9b --- /dev/null +++ b/src/test/resources/draft4/issue258/Master.json @@ -0,0 +1,18 @@ +{ + "id": "Master.json", + "type": "object", + "datatype": true, + "category": "fhir", + "properties": { + "element": { + "$ref": "Element.json" + }, + "extension": { + "type": "array", + "items": { + "$ref": "Extension.json" + } + } + }, + "additionalProperties": false +} diff --git a/src/test/resources/draft4/issue425.json b/src/test/resources/draft4/issue425.json new file mode 100644 index 000000000..20e1d6e91 --- /dev/null +++ b/src/test/resources/draft4/issue425.json @@ -0,0 +1,52 @@ +[ + { + "description": "Nullable oneOf validation", + "schema": { + "properties": { + "values": { + "description": "desc", + "nullable": true, + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + } + } + }, + "tests": [ + { + "description": "oneOf with single string", + "data": { + "values": "test" + }, + "valid": true + }, + { + "description": "oneOf with invalid type", + "data": { + "values": 3 + }, + "valid": false, + "errors": [ + "/values: must be valid to one and only one schema, but 0 are valid", + "/values: integer found, array expected", + "/values: integer found, string expected" + ] + }, + { + "description": "oneOf with single string array", + "data": { + "values": [ "test1"] + }, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/items.json b/src/test/resources/draft4/items.json deleted file mode 100644 index 7bf9f02ba..000000000 --- a/src/test/resources/draft4/items.json +++ /dev/null @@ -1,195 +0,0 @@ -[ - { - "description": "a schema given for items", - "schema": { - "items": {"type": "integer"} - }, - "tests": [ - { - "description": "valid items", - "data": [ 1, 2, 3 ], - "valid": true - }, - { - "description": "wrong type of items", - "data": [1, "x"], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": {"foo" : "bar"}, - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "length": 1 - }, - "valid": true - } - ] - }, - { - "description": "an array of schemas for items", - "schema": { - "items": [ - {"type": "integer"}, - {"type": "string"} - ] - }, - "tests": [ - { - "description": "correct types", - "data": [ 1, "foo" ], - "valid": true - }, - { - "description": "wrong types", - "data": [ "foo", 1 ], - "valid": false - }, - { - "description": "incomplete array of items", - "data": [ 1 ], - "valid": true - }, - { - "description": "array with additional items", - "data": [ 1, "foo", true ], - "valid": true - }, - { - "description": "empty array", - "data": [ ], - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "1": "valid", - "length": 2 - }, - "valid": true - } - ] - }, - { - "description": "items and subitems", - "schema": { - "definitions": { - "item": { - "type": "array", - "additionalItems": false, - "items": [ - { "$ref": "#/definitions/sub-item" }, - { "$ref": "#/definitions/sub-item" } - ] - }, - "sub-item": { - "type": "object", - "required": ["foo"] - } - }, - "type": "array", - "additionalItems": false, - "items": [ - { "$ref": "#/definitions/item" }, - { "$ref": "#/definitions/item" }, - { "$ref": "#/definitions/item" } - ] - }, - "tests": [ - { - "description": "valid items", - "data": [ - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ] - ], - "valid": true - }, - { - "description": "too many items", - "data": [ - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ] - ], - "valid": false - }, - { - "description": "too many sub-items", - "data": [ - [ {"foo": null}, {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ] - ], - "valid": false - }, - { - "description": "wrong item", - "data": [ - {"foo": null}, - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ] - ], - "valid": false - }, - { - "description": "wrong sub-item", - "data": [ - [ {}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ], - [ {"foo": null}, {"foo": null} ] - ], - "valid": false - }, - { - "description": "fewer items is valid", - "data": [ - [ {"foo": null} ], - [ {"foo": null} ] - ], - "valid": true - } - ] - }, - { - "description": "nested items", - "schema": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - } - } - } - } - }, - "tests": [ - { - "description": "valid nested array", - "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], - "valid": true - }, - { - "description": "nested array with invalid type", - "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], - "valid": false - }, - { - "description": "not deep enough", - "data": [[[1], [2],[3]], [[4], [5], [6]]], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/maxLength.json b/src/test/resources/draft4/maxLength.json deleted file mode 100644 index 811d35b25..000000000 --- a/src/test/resources/draft4/maxLength.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "description": "maxLength validation", - "schema": {"maxLength": 2}, - "tests": [ - { - "description": "shorter is valid", - "data": "f", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too long is invalid", - "data": "foo", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - }, - { - "description": "two supplementary Unicode code points is long enough", - "data": "\uD83D\uDCA9\uD83D\uDCA9", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft4/minLength.json b/src/test/resources/draft4/minLength.json deleted file mode 100644 index 3f09158de..000000000 --- a/src/test/resources/draft4/minLength.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "description": "minLength validation", - "schema": {"minLength": 2}, - "tests": [ - { - "description": "longer is valid", - "data": "foo", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too short is invalid", - "data": "f", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1, - "valid": true - }, - { - "description": "one supplementary Unicode code point is not long enough", - "data": "\uD83D\uDCA9", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/multipleOf.json b/src/test/resources/draft4/multipleOf.json deleted file mode 100644 index faa87cff5..000000000 --- a/src/test/resources/draft4/multipleOf.json +++ /dev/null @@ -1,71 +0,0 @@ -[ - { - "description": "by int", - "schema": {"multipleOf": 2}, - "tests": [ - { - "description": "int by int", - "data": 10, - "valid": true - }, - { - "description": "int by int fail", - "data": 7, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "by number", - "schema": {"multipleOf": 1.5}, - "tests": [ - { - "description": "zero is multiple of anything", - "data": 0, - "valid": true - }, - { - "description": "4.5 is multiple of 1.5", - "data": 4.5, - "valid": true - }, - { - "description": "35 is not multiple of 1.5", - "data": 35, - "valid": false - } - ] - }, - { - "description": "by small number", - "schema": {"multipleOf": 0.0001}, - "tests": [ - { - "description": "0.0075 is multiple of 0.0001", - "data": 0.0075, - "valid": true - }, - { - "description": "0.00751 is not multiple of 0.0001", - "data": 0.00751, - "valid": false - } - ] - }, - { - "description": "invalid instance should not raise error when float division = inf", - "schema": {"type": "integer", "multipleOf": 0.123456789}, - "tests": [ - { - "description": "always invalid, but naive implementations may raise an overflow error", - "data": 1e308, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/not.json b/src/test/resources/draft4/not.json deleted file mode 100644 index cbb7f46bf..000000000 --- a/src/test/resources/draft4/not.json +++ /dev/null @@ -1,96 +0,0 @@ -[ - { - "description": "not", - "schema": { - "not": {"type": "integer"} - }, - "tests": [ - { - "description": "allowed", - "data": "foo", - "valid": true - }, - { - "description": "disallowed", - "data": 1, - "valid": false - } - ] - }, - { - "description": "not multiple types", - "schema": { - "not": {"type": ["integer", "boolean"]} - }, - "tests": [ - { - "description": "valid", - "data": "foo", - "valid": true - }, - { - "description": "mismatch", - "data": 1, - "valid": false - }, - { - "description": "other mismatch", - "data": true, - "valid": false - } - ] - }, - { - "description": "not more complex schema", - "schema": { - "not": { - "type": "object", - "properties": { - "foo": { - "type": "string" - } - } - } - }, - "tests": [ - { - "description": "match", - "data": 1, - "valid": true - }, - { - "description": "other match", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "mismatch", - "data": {"foo": "bar"}, - "valid": false - } - ] - }, - { - "description": "forbidden property", - "schema": { - "properties": { - "foo": { - "not": {} - } - } - }, - "tests": [ - { - "description": "property present", - "data": {"foo": 1, "bar": 2}, - "valid": false - }, - { - "description": "property absent", - "data": {"bar": 1, "baz": 2}, - "valid": true - } - ] - } - -] diff --git a/src/test/resources/draft4/oneOf.json b/src/test/resources/draft4/oneOf.json deleted file mode 100644 index fb63b0898..000000000 --- a/src/test/resources/draft4/oneOf.json +++ /dev/null @@ -1,230 +0,0 @@ -[ - { - "description": "oneOf", - "schema": { - "oneOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": 1, - "valid": true - }, - { - "description": "second oneOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both oneOf valid", - "data": 3, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "oneOf with base schema", - "schema": { - "type": "string", - "oneOf" : [ - { - "minLength": 2 - }, - { - "maxLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one oneOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both oneOf valid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf complex types", - "schema": { - "oneOf": [ - { - "properties": { - "bar": {"type": "integer"} - }, - "required": ["bar"] - }, - { - "properties": { - "foo": {"type": "string"} - }, - "required": ["foo"] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid (complex)", - "data": {"bar": 2}, - "valid": true - }, - { - "description": "second oneOf valid (complex)", - "data": {"foo": "baz"}, - "valid": true - }, - { - "description": "both oneOf valid (complex)", - "data": {"foo": "baz", "bar": 2}, - "valid": false - }, - { - "description": "neither oneOf valid (complex)", - "data": {"foo": 2, "bar": "quux"}, - "valid": false - } - ] - }, - { - "description": "oneOf with empty schema", - "schema": { - "oneOf": [ - { "type": "number" }, - {} - ] - }, - "tests": [ - { - "description": "one valid - valid", - "data": "foo", - "valid": true - }, - { - "description": "both valid - invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "oneOf with required", - "schema": { - "type": "object", - "oneOf": [ - { "required": ["foo", "bar"] }, - { "required": ["foo", "baz"] } - ] - }, - "tests": [ - { - "description": "both invalid - invalid", - "data": {"bar": 2}, - "valid": false - }, - { - "description": "first valid - valid", - "data": {"foo": 1, "bar": 2}, - "valid": true - }, - { - "description": "second valid - valid", - "data": {"foo": 1, "baz": 3}, - "valid": true - }, - { - "description": "both valid - invalid", - "data": {"foo": 1, "bar": 2, "baz" : 3}, - "valid": false - } - ] - }, - { - "description": "oneOf with missing optional property", - "schema": { - "oneOf": [ - { - "properties": { - "bar": {}, - "baz": {} - }, - "required": ["bar"] - }, - { - "properties": { - "foo": {} - }, - "required": ["foo"] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": {"bar": 8}, - "valid": true - }, - { - "description": "second oneOf valid", - "data": {"foo": "foo"}, - "valid": true - }, - { - "description": "both oneOf valid", - "data": {"foo": "foo", "bar": 8}, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": {"baz": "quux"}, - "valid": false - } - ] - }, - { - "description": "nested oneOf, to check validation semantics", - "schema": { - "oneOf": [ - { - "oneOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/bignum.json b/src/test/resources/draft4/optional/bignum.json deleted file mode 100644 index 7a622de8e..000000000 --- a/src/test/resources/draft4/optional/bignum.json +++ /dev/null @@ -1,95 +0,0 @@ -[ - { - "description": "integer", - "schema": { "type": "integer" }, - "tests": [ - { - "description": "a bignum is an integer", - "data": 12345678910111213141516171819202122232425262728293031, - "valid": true - }, - { - "description": "a negative bignum is an integer", - "data": -12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { "type": "number" }, - "tests": [ - { - "description": "a bignum is a number", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": true - }, - { - "description": "a negative bignum is a number", - "data": -98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "string", - "schema": { "type": "string" }, - "tests": [ - { - "description": "a bignum is not a string", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { "maximum": 18446744073709551615 }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision", - "schema": { - "maximum": 972783798187987123879878123.18878137, - "exclusiveMaximum": true - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 972783798187987123879878123.188781371, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { "minimum": -18446744073709551615 }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision on negative numbers", - "schema": { - "minimum": -972783798187987123879878123.18878137, - "exclusiveMinimum": true - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -972783798187987123879878123.188781371, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/ecmascript-regex.json b/src/test/resources/draft4/optional/ecmascript-regex.json deleted file mode 100644 index 77624cffe..000000000 --- a/src/test/resources/draft4/optional/ecmascript-regex.json +++ /dev/null @@ -1,552 +0,0 @@ -[ - { - "description": "ECMA 262 regex $ does not match trailing newline", - "schema": { - "type": "string", - "pattern": "^abc$" - }, - "tests": [ - { - "description": "matches in Python, but should not in jsonschema", - "data": "abc\\n", - "valid": false - }, - { - "description": "should match", - "data": "abc", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex converts \\t to horizontal tab", - "schema": { - "type": "string", - "pattern": "^\\t$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\t", - "valid": false - }, - { - "description": "matches", - "data": "\u0009", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and upper letter", - "schema": { - "type": "string", - "pattern": "^\\cC$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cC", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and lower letter", - "schema": { - "type": "string", - "pattern": "^\\cc$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cc", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\d matches ascii digits only", - "schema": { - "type": "string", - "pattern": "^\\d$" - }, - "tests": [ - { - "description": "ASCII zero matches", - "data": "0", - "valid": true - }, - { - "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", - "data": "߀", - "valid": false - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) does not match", - "data": "\u07c0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\D matches everything but ascii digits", - "schema": { - "type": "string", - "pattern": "^\\D$" - }, - "tests": [ - { - "description": "ASCII zero does not match", - "data": "0", - "valid": false - }, - { - "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", - "data": "߀", - "valid": true - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) matches", - "data": "\u07c0", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\w matches ascii letters only", - "schema": { - "type": "string", - "pattern": "^\\w$" - }, - "tests": [ - { - "description": "ASCII 'a' matches", - "data": "a", - "valid": true - }, - { - "description": "latin-1 e-acute does not match (unlike e.g. Python)", - "data": "é", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\W matches everything but ascii letters", - "schema": { - "type": "string", - "pattern": "^\\W$" - }, - "tests": [ - { - "description": "ASCII 'a' does not match", - "data": "a", - "valid": false - }, - { - "description": "latin-1 e-acute matches (unlike e.g. Python)", - "data": "é", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\s matches whitespace", - "schema": { - "type": "string", - "pattern": "^\\s$" - }, - "tests": [ - { - "description": "ASCII space matches", - "data": " ", - "valid": true - }, - { - "description": "Character tabulation matches", - "data": "\t", - "valid": true - }, - { - "description": "Line tabulation matches", - "data": "\u000b", - "valid": true - }, - { - "description": "Form feed matches", - "data": "\u000c", - "valid": true - }, - { - "description": "latin-1 non-breaking-space matches", - "data": "\u00a0", - "valid": true - }, - { - "description": "zero-width whitespace matches", - "data": "\ufeff", - "valid": true - }, - { - "description": "line feed matches (line terminator)", - "data": "\u000a", - "valid": true - }, - { - "description": "paragraph separator matches (line terminator)", - "data": "\u2029", - "valid": true - }, - { - "description": "EM SPACE matches (Space_Separator)", - "data": "\u2003", - "valid": true - }, - { - "description": "Non-whitespace control does not match", - "data": "\u0001", - "valid": false - }, - { - "description": "Non-whitespace does not match", - "data": "\u2013", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\S matches everything but whitespace", - "schema": { - "type": "string", - "pattern": "^\\S$" - }, - "tests": [ - { - "description": "ASCII space does not match", - "data": " ", - "valid": false - }, - { - "description": "Character tabulation does not match", - "data": "\t", - "valid": false - }, - { - "description": "Line tabulation does not match", - "data": "\u000b", - "valid": false - }, - { - "description": "Form feed does not match", - "data": "\u000c", - "valid": false - }, - { - "description": "latin-1 non-breaking-space does not match", - "data": "\u00a0", - "valid": false - }, - { - "description": "zero-width whitespace does not match", - "data": "\ufeff", - "valid": false - }, - { - "description": "line feed does not match (line terminator)", - "data": "\u000a", - "valid": false - }, - { - "description": "paragraph separator does not match (line terminator)", - "data": "\u2029", - "valid": false - }, - { - "description": "EM SPACE does not match (Space_Separator)", - "data": "\u2003", - "valid": false - }, - { - "description": "Non-whitespace control matches", - "data": "\u0001", - "valid": true - }, - { - "description": "Non-whitespace matches", - "data": "\u2013", - "valid": true - } - ] - }, - { - "description": "unicode semantics should be used for all pattern matching", - "schema": { "pattern": "\\p{Letter}cole" }, - "tests": [ - { - "description": "ascii character in json string", - "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", - "valid": true - }, - { - "description": "literal unicode character in json string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": true - }, - { - "description": "unicode character in hex format in string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": true - }, - { - "description": "unicode matching is case-sensitive", - "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", - "valid": false - } - ] - }, - { - "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", - "schema": { "pattern": "\\wcole" }, - "tests": [ - { - "description": "ascii character in json string", - "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", - "valid": true - }, - { - "description": "literal unicode character in json string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": false - }, - { - "description": "unicode character in hex format in string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": false - }, - { - "description": "unicode matching is case-sensitive", - "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", - "valid": false - } - ] - }, - { - "description": "unicode characters do not match ascii ranges", - "schema": { "pattern": "[a-z]cole" }, - "tests": [ - { - "description": "literal unicode character in json string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": false - }, - { - "description": "unicode character in hex format in string", - "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", - "valid": false - }, - { - "description": "ascii characters match", - "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", - "valid": true - } - ] - }, - { - "description": "\\d in pattern matches [0-9], not unicode digits", - "schema": { "pattern": "^\\d+$" }, - "tests": [ - { - "description": "ascii digits", - "data": "42", - "valid": true - }, - { - "description": "ascii non-digits", - "data": "-%#", - "valid": false - }, - { - "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", - "data": "৪২", - "valid": false - } - ] - }, - { - "description": "unicode digits are more than 0 through 9", - "schema": { "pattern": "^\\p{digit}+$" }, - "tests": [ - { - "description": "ascii digits", - "data": "42", - "valid": true - }, - { - "description": "ascii non-digits", - "data": "-%#", - "valid": false - }, - { - "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", - "data": "৪২", - "valid": true - } - ] - }, - { - "description": "unicode semantics should be used for all patternProperties matching", - "schema": { - "type": "object", - "patternProperties": { - "\\p{Letter}cole": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "ascii character in json string", - "data": { "l'ecole": "pas de vraie vie" }, - "valid": true - }, - { - "description": "literal unicode character in json string", - "data": { "l'école": "pas de vraie vie" }, - "valid": true - }, - { - "description": "unicode character in hex format in string", - "data": { "l'\u00e9cole": "pas de vraie vie" }, - "valid": true - }, - { - "description": "unicode matching is case-sensitive", - "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, - "valid": false - } - ] - }, - { - "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", - "schema": { - "type": "object", - "patternProperties": { - "\\wcole": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "ascii character in json string", - "data": { "l'ecole": "pas de vraie vie" }, - "valid": true - }, - { - "description": "literal unicode character in json string", - "data": { "l'école": "pas de vraie vie" }, - "valid": false - }, - { - "description": "unicode character in hex format in string", - "data": { "l'\u00e9cole": "pas de vraie vie" }, - "valid": false - }, - { - "description": "unicode matching is case-sensitive", - "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, - "valid": false - } - ] - }, - { - "description": "unicode characters do not match ascii ranges", - "schema": { - "type": "object", - "patternProperties": { - "[a-z]cole": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "literal unicode character in json string", - "data": { "l'école": "pas de vraie vie" }, - "valid": false - }, - { - "description": "unicode character in hex format in string", - "data": { "l'\u00e9cole": "pas de vraie vie" }, - "valid": false - }, - { - "description": "ascii characters match", - "data": { "l'ecole": "pas de vraie vie" }, - "valid": true - } - ] - }, - { - "description": "\\d in patternProperties matches [0-9], not unicode digits", - "schema": { - "type": "object", - "patternProperties": { - "^\\d+$": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "ascii digits", - "data": { "42": "life, the universe, and everything" }, - "valid": true - }, - { - "description": "ascii non-digits", - "data": { "-%#": "spending the year dead for tax reasons" }, - "valid": false - }, - { - "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", - "data": { "৪২": "khajit has wares if you have coin" }, - "valid": false - } - ] - }, - { - "description": "unicode digits are more than 0 through 9", - "schema": { - "type": "object", - "patternProperties": { - "^\\p{digit}+$": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "ascii digits", - "data": { "42": "life, the universe, and everything" }, - "valid": true - }, - { - "description": "ascii non-digits", - "data": { "-%#": "spending the year dead for tax reasons" }, - "valid": false - }, - { - "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", - "data": { "৪২": "khajit has wares if you have coin" }, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format.json b/src/test/resources/draft4/optional/format.json deleted file mode 100644 index 6b9ab7ded..000000000 --- a/src/test/resources/draft4/optional/format.json +++ /dev/null @@ -1,378 +0,0 @@ -[ - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "a valid date-time string", - "data": "1963-06-19T08:30:06.283185Z", - "valid": true - }, - { - "description": "an invalid date-time string without colon", - "data": "2018-07-27T00:00:01.000-0300", - "valid": false - }, - { - "description": "a valid date-time string", - "data": "2019-07-24T00:00:01-04:00", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "2019-07-24T24:00:01-04:00", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "2019-07-24T25/:00:01-04:00", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963 08:30:06 PST", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "1963-13-25T08:30:06.283185Z", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "1963-12-35T08:30:06.283185Z", - "valid": false - }, - { - "description": "an valid date-time string", - "data": "1963-12-31T08:30:06.283185Z", - "valid": true - }, - { - "description": "an valid date-time string", - "data": "1963-12-30T08:30:06.283Z", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "1963-12-30T08:30:06.283", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "1963-12-30T08:30:06", - "valid": false - }, - { - "description": "an valid date-time string", - "data": "1963-12-30T08:30:06Z", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350T01:01:01", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "2019-02-29T08:30:06Z", - "valid": false - }, - { - "description": "a valid date-time string", - "data": "2019-02-28T13:30:06Z", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "2019-02-28T08:60:06Z", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "2019-02-28T08:30:60Z", - "valid": false - }, - { - "description": "a valid date-time string", - "data": "2019-02-28T08:30:59.12345+08:00", - "valid": true - }, - { - "description": "a valid date-time string", - "data": "2019-02-28T08:30:59.12345-12:00", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "2019-02-28T08:30:59.12345-60:00", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "2019-02-28T08:30:59.12345-12:60", - "valid": false - }, - { - "description": "a valid date-time string", - "data": "2019-02-28T08:30:59.99999z", - "valid": true - }, - { - "description": "an incomplete date-time string", - "data": "2019-02-28", - "valid": false - }, - { - "description": "an incomplete date-time string", - "data": "2019-02-28-08:00", - "valid": false - }, - { - "description": "an incomplete date-time string", - "data": "2019-02-28Z", - "valid": false - } - ] - }, - { - "description": "validation of date strings", - "schema": { - "format": "date" - }, - "tests": [ - { - "description": "an invalid date string", - "data": "1963-13-25", - "valid": false - }, - { - "description": "an invalid date string", - "data": "1963-12-35", - "valid": false - }, - { - "description": "an invalid date string", - "data": "2019-02-28T08:30:59.99999z", - "valid": false - }, - { - "description": "a valid date string", - "data": "1963-12-31", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-35", - "valid": false - }, - { - "description": "an invalid date string", - "data": "2019-02-29", - "valid": false - }, - { - "description": "an invalid date string", - "data": "2019-06-31", - "valid": false - }, - { - "description": "a valid date string", - "data": "2048-02-29", - "valid": true - } - ] - }, - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "a valid URI", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "empty fragment", - "data": "http://json-schema.org/draft-07/schema#", - "valid": true - }, - { - "description": "empty query string", - "data": "http://json-schema.org/?", - "valid": true - }, - { - "description": "empty query string and fragment", - "data": "http://json-schema.org/?#", - "valid": true - }, - { - "description": "One letter scheme", - "data": "a://foo", - "valid": true - }, - { - "description": "very long valid URI", - "data": "http://foo.bar/?baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a#&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a&baz=a%20a", - "valid": true - }, - { - "description": "authority with user", - "data": "https://foo@bar.org", - "valid": true - }, - { - "description": "authority with port", - "data": "https://foo.org:12345", - "valid": true - }, - { - "description": "authority with user and port", - "data": "https://foo@bar.org:12345", - "valid": true - }, - { - "description": "a scheme is mandatory in URI", - "data": "//foo.bar/?baz=qux#quux", - "valid": false - }, - { - "description": "an invalid URI", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "an invalid URI though valid URI reference", - "data": "abc", - "valid": false - }, - { - "description": "an invalid query string", - "data": "http://foo.bar/?baz=q|ux#quux", - "valid": false - } - ] - }, - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "a valid e-mail address", - "data": "joe.bloggs@example.com", - "valid": true - }, - { - "description": "an invalid e-mail address", - "data": "2962", - "valid": false - }, - { - "description": "an invalid e-mail address with wrong domain", - "data": "steve@gmail.nn", - "valid": false - }, - { - "description": "an invalid emmail with two @", - "data": "gmail@@gmail.com", - "valid": false - } - ] - }, - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - } - ] - }, - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "a valid IPv6 address", - "data": "::1", - "valid": true - }, - { - "description": "an IPv6 address with out-of-range values", - "data": "12345::", - "valid": false - }, - { - "description": "an IPv6 address with too many components", - "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", - "valid": false - }, - { - "description": "an IPv6 address containing illegal characters", - "data": "::laptop", - "valid": false - } - ] - }, - { - "description": "validation of host names", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "a valid host name", - "data": "www.example.com", - "valid": true - }, - { - "description": "a host name starting with an illegal character", - "data": "-a-host-name-that-starts-with--", - "valid": false - }, - { - "description": "a host name containing illegal characters", - "data": "not_a_valid_host_name", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/date-time.json b/src/test/resources/draft4/optional/format/date-time.json deleted file mode 100644 index f4f993355..000000000 --- a/src/test/resources/draft4/optional/format/date-time.json +++ /dev/null @@ -1,133 +0,0 @@ -[ - { - "description": "validation of date-time strings", - "schema": { "format": "date-time" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid date-time string", - "data": "1963-06-19T08:30:06.283185Z", - "valid": true - }, - { - "description": "a valid date-time string without second fraction", - "data": "1963-06-19T08:30:06Z", - "valid": true - }, - { - "description": "a valid date-time string with plus offset", - "data": "1937-01-01T12:00:27.87+00:20", - "valid": true - }, - { - "description": "a valid date-time string with minus offset", - "data": "1990-12-31T15:59:50.123-08:00", - "valid": true - }, - { - "description": "a valid date-time with a leap second, UTC", - "data": "1998-12-31T23:59:60Z", - "valid": true - }, - { - "description": "a valid date-time with a leap second, with minus offset", - "data": "1998-12-31T15:59:60.123-08:00", - "valid": true - }, - { - "description": "an invalid date-time past leap second, UTC", - "data": "1998-12-31T23:59:61Z", - "valid": false - }, - { - "description": "an invalid date-time with leap second on a wrong minute, UTC", - "data": "1998-12-31T23:58:60Z", - "valid": false - }, - { - "description": "an invalid date-time with leap second on a wrong hour, UTC", - "data": "1998-12-31T22:59:60Z", - "valid": false - }, - { - "description": "an invalid day in date-time string", - "data": "1990-02-31T15:59:59.123-08:00", - "valid": false - }, - { - "description": "an invalid offset in date-time string", - "data": "1990-12-31T15:59:59-24:00", - "valid": false - }, - { - "description": "an invalid closing Z after time-zone offset", - "data": "1963-06-19T08:30:06.28123+01:00Z", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963 08:30:06 PST", - "valid": false - }, - { - "description": "case-insensitive T and Z", - "data": "1963-06-19t08:30:06.283185z", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350T01:01:01", - "valid": false - }, - { - "description": "invalid non-padded month dates", - "data": "1963-6-19T08:30:06.283185Z", - "valid": false - }, - { - "description": "invalid non-padded day dates", - "data": "1963-06-1T08:30:06.283185Z", - "valid": false - }, - { - "description": "non-ascii digits should be rejected in the date portion", - "data": "1963-06-1৪T00:00:00Z", - "valid": false - }, - { - "description": "non-ascii digits should be rejected in the time portion", - "data": "1963-06-11T0৪:00:00Z", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/email.json b/src/test/resources/draft4/optional/format/email.json deleted file mode 100644 index d6761a46b..000000000 --- a/src/test/resources/draft4/optional/format/email.json +++ /dev/null @@ -1,83 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { "format": "email" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid e-mail address", - "data": "joe.bloggs@example.com", - "valid": true - }, - { - "description": "an invalid e-mail address", - "data": "2962", - "valid": false - }, - { - "description": "tilde in local part is valid", - "data": "te~st@example.com", - "valid": true - }, - { - "description": "tilde before local part is valid", - "data": "~test@example.com", - "valid": true - }, - { - "description": "tilde after local part is valid", - "data": "test~@example.com", - "valid": true - }, - { - "description": "dot before local part is not valid", - "data": ".test@example.com", - "valid": false - }, - { - "description": "dot after local part is not valid", - "data": "test.@example.com", - "valid": false - }, - { - "description": "two separated dots inside local part are valid", - "data": "te.s.t@example.com", - "valid": true - }, - { - "description": "two subsequent dots inside local part are not valid", - "data": "te..st@example.com", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/hostname.json b/src/test/resources/draft4/optional/format/hostname.json deleted file mode 100644 index 8a67fda88..000000000 --- a/src/test/resources/draft4/optional/format/hostname.json +++ /dev/null @@ -1,98 +0,0 @@ -[ - { - "description": "validation of host names", - "schema": { "format": "hostname" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid host name", - "data": "www.example.com", - "valid": true - }, - { - "description": "a valid punycoded IDN hostname", - "data": "xn--4gbwdl.xn--wgbh1c", - "valid": true - }, - { - "description": "a host name starting with an illegal character", - "data": "-a-host-name-that-starts-with--", - "valid": false - }, - { - "description": "a host name containing illegal characters", - "data": "not_a_valid_host_name", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", - "valid": false - }, - { - "description": "starts with hyphen", - "data": "-hostname", - "valid": false - }, - { - "description": "ends with hyphen", - "data": "hostname-", - "valid": false - }, - { - "description": "starts with underscore", - "data": "_hostname", - "valid": false - }, - { - "description": "ends with underscore", - "data": "hostname_", - "valid": false - }, - { - "description": "contains underscore", - "data": "host_name", - "valid": false - }, - { - "description": "maximum label length", - "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", - "valid": true - }, - { - "description": "exceeds maximum label length", - "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/ipv4.json b/src/test/resources/draft4/optional/format/ipv4.json deleted file mode 100644 index 6b166c70c..000000000 --- a/src/test/resources/draft4/optional/format/ipv4.json +++ /dev/null @@ -1,84 +0,0 @@ -[ - { - "description": "validation of IP addresses", - "schema": { "format": "ipv4" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - }, - { - "description": "an IP address as an integer (decimal)", - "data": "2130706433", - "valid": false - }, - { - "description": "leading zeroes should be rejected, as they are treated as octals", - "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", - "data": "087.10.0.1", - "valid": false - }, - { - "description": "value without leading zero is valid", - "data": "87.10.0.1", - "valid": true - }, - { - "description": "non-ascii digits should be rejected", - "data": "1২7.0.0.1", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/ipv6.json b/src/test/resources/draft4/optional/format/ipv6.json deleted file mode 100644 index 6379927b3..000000000 --- a/src/test/resources/draft4/optional/format/ipv6.json +++ /dev/null @@ -1,208 +0,0 @@ -[ - { - "description": "validation of IPv6 addresses", - "schema": { "format": "ipv6" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid IPv6 address", - "data": "::1", - "valid": true - }, - { - "description": "an IPv6 address with out-of-range values", - "data": "12345::", - "valid": false - }, - { - "description": "trailing 4 hex symbols is valid", - "data": "::abef", - "valid": true - }, - { - "description": "trailing 5 hex symbols is invalid", - "data": "::abcef", - "valid": false - }, - { - "description": "an IPv6 address with too many components", - "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", - "valid": false - }, - { - "description": "an IPv6 address containing illegal characters", - "data": "::laptop", - "valid": false - }, - { - "description": "no digits is valid", - "data": "::", - "valid": true - }, - { - "description": "leading colons is valid", - "data": "::42:ff:1", - "valid": true - }, - { - "description": "trailing colons is valid", - "data": "d6::", - "valid": true - }, - { - "description": "missing leading octet is invalid", - "data": ":2:3:4:5:6:7:8", - "valid": false - }, - { - "description": "missing trailing octet is invalid", - "data": "1:2:3:4:5:6:7:", - "valid": false - }, - { - "description": "missing leading octet with omitted octets later", - "data": ":2:3:4::8", - "valid": false - }, - { - "description": "single set of double colons in the middle is valid", - "data": "1:d6::42", - "valid": true - }, - { - "description": "two sets of double colons is invalid", - "data": "1::d6::42", - "valid": false - }, - { - "description": "mixed format with the ipv4 section as decimal octets", - "data": "1::d6:192.168.0.1", - "valid": true - }, - { - "description": "mixed format with double colons between the sections", - "data": "1:2::192.168.0.1", - "valid": true - }, - { - "description": "mixed format with ipv4 section with octet out of range", - "data": "1::2:192.168.256.1", - "valid": false - }, - { - "description": "mixed format with ipv4 section with a hex octet", - "data": "1::2:192.168.ff.1", - "valid": false - }, - { - "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", - "data": "::ffff:192.168.0.1", - "valid": true - }, - { - "description": "triple colons is invalid", - "data": "1:2:3:4:5:::8", - "valid": false - }, - { - "description": "8 octets", - "data": "1:2:3:4:5:6:7:8", - "valid": true - }, - { - "description": "insufficient octets without double colons", - "data": "1:2:3:4:5:6:7", - "valid": false - }, - { - "description": "no colons is invalid", - "data": "1", - "valid": false - }, - { - "description": "ipv4 is not ipv6", - "data": "127.0.0.1", - "valid": false - }, - { - "description": "ipv4 segment must have 4 octets", - "data": "1:2:3:4:1.2.3", - "valid": false - }, - { - "description": "leading whitespace is invalid", - "data": " ::1", - "valid": false - }, - { - "description": "trailing whitespace is invalid", - "data": "::1 ", - "valid": false - }, - { - "description": "netmask is not a part of ipv6 address", - "data": "fe80::/64", - "valid": false - }, - { - "description": "zone id is not a part of ipv6 address", - "data": "fe80::a%eth1", - "valid": false - }, - { - "description": "a long valid ipv6", - "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", - "valid": true - }, - { - "description": "a long invalid ipv6, below length limit, first", - "data": "100:100:100:100:100:100:255.255.255.255.255", - "valid": false - }, - { - "description": "a long invalid ipv6, below length limit, second", - "data": "100:100:100:100:100:100:100:255.255.255.255", - "valid": false - }, - { - "description": "non-ascii digits should be rejected", - "data": "1:2:3:4:5:6:7:৪", - "valid": false - }, - { - "description": "non-ascii digits should be rejected in the ipv4 portion also", - "data": "1:2::192.16৪.0.1", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/optional/format/uri.json b/src/test/resources/draft4/optional/format/uri.json deleted file mode 100644 index 792d71a05..000000000 --- a/src/test/resources/draft4/optional/format/uri.json +++ /dev/null @@ -1,108 +0,0 @@ -[ - { - "description": "validation of URIs", - "schema": { "format": "uri" }, - "tests": [ - { - "description": "a valid URL with anchor tag", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid URL with anchor tag and parentheses", - "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", - "valid": true - }, - { - "description": "a valid URL with URL-encoded stuff", - "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid puny-coded URL ", - "data": "http://xn--nw2a.xn--j6w193g/", - "valid": true - }, - { - "description": "a valid URL with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid URL based on IPv4", - "data": "http://223.255.255.254", - "valid": true - }, - { - "description": "a valid URL with ftp scheme", - "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", - "valid": true - }, - { - "description": "a valid URL for a simple text file", - "data": "http://www.ietf.org/rfc/rfc2396.txt", - "valid": true - }, - { - "description": "a valid URL ", - "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", - "valid": true - }, - { - "description": "a valid mailto URI", - "data": "mailto:John.Doe@example.com", - "valid": true - }, - { - "description": "a valid newsgroup URI", - "data": "news:comp.infosystems.www.servers.unix", - "valid": true - }, - { - "description": "a valid tel URI", - "data": "tel:+1-816-555-1212", - "valid": true - }, - { - "description": "a valid URN", - "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - "valid": true - }, - { - "description": "an invalid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": false - }, - { - "description": "an invalid relative URI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid URI", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "an invalid URI though valid URI reference", - "data": "abc", - "valid": false - }, - { - "description": "an invalid URI with spaces", - "data": "http:// shouldfail.com", - "valid": false - }, - { - "description": "an invalid URI with spaces and missing scheme", - "data": ":// should fail", - "valid": false - }, - { - "description": "an invalid URI with comma in scheme", - "data": "bar,baz:foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/patternProperties.json b/src/test/resources/draft4/patternProperties.json deleted file mode 100644 index 5f741dfca..000000000 --- a/src/test/resources/draft4/patternProperties.json +++ /dev/null @@ -1,120 +0,0 @@ -[ - { - "description": - "patternProperties validates properties matching a regex", - "schema": { - "patternProperties": { - "f.*o": {"type": "integer"} - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "multiple valid matches is valid", - "data": {"foo": 1, "foooooo" : 2}, - "valid": true - }, - { - "description": "a single invalid match is invalid", - "data": {"foo": "bar", "fooooo": 2}, - "valid": false - }, - { - "description": "multiple invalid matches is invalid", - "data": {"foo": "bar", "foooooo" : "baz"}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "multiple simultaneous patternProperties are validated", - "schema": { - "patternProperties": { - "a*": {"type": "integer"}, - "aaa*": {"maximum": 20} - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": {"a": 21}, - "valid": true - }, - { - "description": "a simultaneous match is valid", - "data": {"aaaa": 18}, - "valid": true - }, - { - "description": "multiple matches is valid", - "data": {"a": 21, "aaaa": 18}, - "valid": true - }, - { - "description": "an invalid due to one is invalid", - "data": {"a": "bar"}, - "valid": false - }, - { - "description": "an invalid due to the other is invalid", - "data": {"aaaa": 31}, - "valid": false - }, - { - "description": "an invalid due to both is invalid", - "data": {"aaa": "foo", "aaaa": 31}, - "valid": false - } - ] - }, - { - "description": "regexes are not anchored by default and are case sensitive", - "schema": { - "patternProperties": { - "[0-9]{2,}": { "type": "boolean" }, - "X_": { "type": "string" } - } - }, - "tests": [ - { - "description": "non recognized members are ignored", - "data": { "answer 1": "42" }, - "valid": true - }, - { - "description": "recognized members are accounted for", - "data": { "a31b": null }, - "valid": false - }, - { - "description": "regexes are case sensitive", - "data": { "a_x_3": 3 }, - "valid": true - }, - { - "description": "regexes are case sensitive, 2", - "data": { "a_X_3": 3 }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/properties.json b/src/test/resources/draft4/properties.json deleted file mode 100644 index 688527bc6..000000000 --- a/src/test/resources/draft4/properties.json +++ /dev/null @@ -1,136 +0,0 @@ -[ - { - "description": "object properties validation", - "schema": { - "properties": { - "foo": {"type": "integer"}, - "bar": {"type": "string"} - } - }, - "tests": [ - { - "description": "both properties present and valid is valid", - "data": {"foo": 1, "bar": "baz"}, - "valid": true - }, - { - "description": "one property invalid is invalid", - "data": {"foo": 1, "bar": {}}, - "valid": false - }, - { - "description": "both properties invalid is invalid", - "data": {"foo": [], "bar": {}}, - "valid": false - }, - { - "description": "doesn't invalidate other properties", - "data": {"quux": []}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": - "properties, patternProperties, additionalProperties interaction", - "schema": { - "properties": { - "foo": {"type": "array", "maxItems": 3}, - "bar": {"type": "array"} - }, - "patternProperties": {"f.o": {"minItems": 2}}, - "additionalProperties": {"type": "integer"} - }, - "tests": [ - { - "description": "property validates property", - "data": {"foo": [1, 2]}, - "valid": true - }, - { - "description": "property invalidates property", - "data": {"foo": [1, 2, 3, 4]}, - "valid": false - }, - { - "description": "patternProperty invalidates property", - "data": {"foo": []}, - "valid": false - }, - { - "description": "patternProperty validates nonproperty", - "data": {"fxo": [1, 2]}, - "valid": true - }, - { - "description": "patternProperty invalidates nonproperty", - "data": {"fxo": []}, - "valid": false - }, - { - "description": "additionalProperty ignores property", - "data": {"bar": []}, - "valid": true - }, - { - "description": "additionalProperty validates others", - "data": {"quux": 3}, - "valid": true - }, - { - "description": "additionalProperty invalidates others", - "data": {"quux": "foo"}, - "valid": false - } - ] - }, - { - "description": "properties with escaped characters", - "schema": { - "properties": { - "foo\nbar": {"type": "number"}, - "foo\"bar": {"type": "number"}, - "foo\\bar": {"type": "number"}, - "foo\rbar": {"type": "number"}, - "foo\tbar": {"type": "number"}, - "foo\fbar": {"type": "number"} - } - }, - "tests": [ - { - "description": "object with all numbers is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1", - "foo\\bar": "1", - "foo\rbar": "1", - "foo\tbar": "1", - "foo\fbar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/ref.json b/src/test/resources/draft4/ref.json deleted file mode 100644 index d2c8f4f61..000000000 --- a/src/test/resources/draft4/ref.json +++ /dev/null @@ -1,213 +0,0 @@ -[ - { - "description": "root pointer ref", - "schema": { - "properties": { - "foo": { - "$ref": "#" - } - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "match", - "data": { - "foo": false - }, - "valid": true - }, - { - "description": "recursive match", - "data": { - "foo": { - "foo": false - } - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": false - }, - "valid": false - }, - { - "description": "recursive mismatch", - "data": { - "foo": { - "bar": false - } - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to object", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "$ref": "#/properties/foo" - } - } - }, - "tests": [ - { - "description": "match", - "data": { - "bar": 3 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": true - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to array", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "$ref": "#/items/0" - } - ] - }, - "tests": [ - { - "description": "match array", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "mismatch array", - "data": [ - 1, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "nested refs", - "schema": { - "definitions": { - "a": { - "type": "integer" - }, - "b": { - "$ref": "#/definitions/a" - }, - "c": { - "$ref": "#/definitions/b" - } - }, - "$ref": "#/definitions/c" - }, - "tests": [ - { - "description": "nested ref valid", - "data": 5, - "valid": true - }, - { - "description": "nested ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "nested refs with duplicate ref values for different files", - "schema": { - "definitions": { - "a": { - "$ref": "subSchemas.json#/definitions/a" - }, - "b": { - "$ref": "#/definitions/a" - }, - "c": { - "$ref": "#/definitions/b" - } - }, - "$ref": "#/definitions/c" - }, - "tests": [ - { - "description": "nested ref valid", - "data": 5, - "valid": true - }, - { - "description": "nested ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "remote ref, containing refs itself", - "schema": { - "$ref": "http://json-schema.org/draft-04/schema#" - }, - "tests": [ - { - "description": "remote ref valid", - "data": { - "minLength": 1 - }, - "valid": true - }, - { - "description": "remote ref invalid", - "data": { - "minLength": -1 - }, - "valid": false - } - ] - }, - { - "description": "property named $ref that is not a reference", - "schema": { - "properties": { - "$ref": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "property named $ref valid", - "data": { - "$ref": "a" - }, - "valid": true - }, - { - "description": "property named $ref invalid", - "data": { - "$ref": 2 - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/refRemote.json b/src/test/resources/draft4/refRemote.json deleted file mode 100644 index c2613e0fb..000000000 --- a/src/test/resources/draft4/refRemote.json +++ /dev/null @@ -1,88 +0,0 @@ -[ - { - "description": "remote ref", - "schema": { - "$ref": "http://localhost:1234/integer.json" - }, - "tests": [ - { - "description": "remote ref valid", - "data": 1, - "valid": true - }, - { - "description": "remote ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "fragment within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/integer" - }, - "tests": [ - { - "description": "remote fragment valid", - "data": 1, - "valid": true - }, - { - "description": "remote fragment invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" - }, - "tests": [ - { - "description": "ref within ref valid", - "data": 1, - "valid": true - }, - { - "description": "ref within ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "change resolution scope", - "schema": { - "id": "http://localhost:1234/", - "items": { - "id": "folder/", - "items": { - "$ref": "folderInteger.json" - } - } - }, - "tests": [ - { - "description": "changed scope ref valid", - "data": [ - [ - 1 - ] - ], - "valid": true - }, - { - "description": "changed scope ref invalid", - "data": [ - [ - "a" - ] - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/required.json b/src/test/resources/draft4/required.json deleted file mode 100644 index 9b05318f7..000000000 --- a/src/test/resources/draft4/required.json +++ /dev/null @@ -1,89 +0,0 @@ -[ - { - "description": "required validation", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "required": ["foo"] - }, - "tests": [ - { - "description": "present required property is valid", - "data": {"foo": 1}, - "valid": true - }, - { - "description": "non-present required property is invalid", - "data": {"bar": 1}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "required default validation", - "schema": { - "properties": { - "foo": {} - } - }, - "tests": [ - { - "description": "not required by default", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with escaped characters", - "schema": { - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ] - }, - "tests": [ - { - "description": "object with all properties present is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with some properties missing is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft4/uniqueItems.json b/src/test/resources/draft4/uniqueItems.json deleted file mode 100644 index 2ccf666d7..000000000 --- a/src/test/resources/draft4/uniqueItems.json +++ /dev/null @@ -1,404 +0,0 @@ -[ - { - "description": "uniqueItems validation", - "schema": {"uniqueItems": true}, - "tests": [ - { - "description": "unique array of integers is valid", - "data": [1, 2], - "valid": true - }, - { - "description": "non-unique array of integers is invalid", - "data": [1, 1], - "valid": false - }, - { - "description": "non-unique array of more than two integers is invalid", - "data": [1, 2, 1], - "valid": false - }, - { - "description": "numbers are unique if mathematically unequal", - "data": [1.0, 1.00, 1], - "valid": false - }, - { - "description": "false is not equal to zero", - "data": [0, false], - "valid": true - }, - { - "description": "true is not equal to one", - "data": [1, true], - "valid": true - }, - { - "description": "unique array of strings is valid", - "data": ["foo", "bar", "baz"], - "valid": true - }, - { - "description": "non-unique array of strings is invalid", - "data": ["foo", "bar", "foo"], - "valid": false - }, - { - "description": "unique array of objects is valid", - "data": [{"foo": "bar"}, {"foo": "baz"}], - "valid": true - }, - { - "description": "non-unique array of objects is invalid", - "data": [{"foo": "bar"}, {"foo": "bar"}], - "valid": false - }, - { - "description": "unique array of nested objects is valid", - "data": [ - {"foo": {"bar" : {"baz" : true}}}, - {"foo": {"bar" : {"baz" : false}}} - ], - "valid": true - }, - { - "description": "non-unique array of nested objects is invalid", - "data": [ - {"foo": {"bar" : {"baz" : true}}}, - {"foo": {"bar" : {"baz" : true}}} - ], - "valid": false - }, - { - "description": "unique array of arrays is valid", - "data": [["foo"], ["bar"]], - "valid": true - }, - { - "description": "non-unique array of arrays is invalid", - "data": [["foo"], ["foo"]], - "valid": false - }, - { - "description": "non-unique array of more than two arrays is invalid", - "data": [["foo"], ["bar"], ["foo"]], - "valid": false - }, - { - "description": "1 and true are unique", - "data": [1, true], - "valid": true - }, - { - "description": "0 and false are unique", - "data": [0, false], - "valid": true - }, - { - "description": "[1] and [true] are unique", - "data": [[1], [true]], - "valid": true - }, - { - "description": "[0] and [false] are unique", - "data": [[0], [false]], - "valid": true - }, - { - "description": "nested [1] and [true] are unique", - "data": [[[1], "foo"], [[true], "foo"]], - "valid": true - }, - { - "description": "nested [0] and [false] are unique", - "data": [[[0], "foo"], [[false], "foo"]], - "valid": true - }, - { - "description": "unique heterogeneous types are valid", - "data": [{}, [1], true, null, 1, "{}"], - "valid": true - }, - { - "description": "non-unique heterogeneous types are invalid", - "data": [{}, [1], true, null, {}, 1], - "valid": false - }, - { - "description": "different objects are unique", - "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], - "valid": true - }, - { - "description": "objects are non-unique despite key order", - "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], - "valid": false - }, - { - "description": "{\"a\": false} and {\"a\": 0} are unique", - "data": [{"a": false}, {"a": 0}], - "valid": true - }, - { - "description": "{\"a\": true} and {\"a\": 1} are unique", - "data": [{"a": true}, {"a": 1}], - "valid": true - } - ] - }, - { - "description": "uniqueItems with an array of items", - "schema": { - "items": [{"type": "boolean"}, {"type": "boolean"}], - "uniqueItems": true - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [false, true], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [true, false], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [false, false], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [true, true], - "valid": false - }, - { - "description": "unique array extended from [false, true] is valid", - "data": [false, true, "foo", "bar"], - "valid": true - }, - { - "description": "unique array extended from [true, false] is valid", - "data": [true, false, "foo", "bar"], - "valid": true - }, - { - "description": "non-unique array extended from [false, true] is not valid", - "data": [false, true, "foo", "foo"], - "valid": false - }, - { - "description": "non-unique array extended from [true, false] is not valid", - "data": [true, false, "foo", "foo"], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items and additionalItems=false", - "schema": { - "items": [{"type": "boolean"}, {"type": "boolean"}], - "uniqueItems": true, - "additionalItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [false, true], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [true, false], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [false, false], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [true, true], - "valid": false - }, - { - "description": "extra items are invalid even if unique", - "data": [false, true, null], - "valid": false - } - ] - }, - { - "description": "uniqueItems=false validation", - "schema": { "uniqueItems": false }, - "tests": [ - { - "description": "unique array of integers is valid", - "data": [1, 2], - "valid": true - }, - { - "description": "non-unique array of integers is valid", - "data": [1, 1], - "valid": true - }, - { - "description": "numbers are unique if mathematically unequal", - "data": [1.0, 1.00, 1], - "valid": true - }, - { - "description": "false is not equal to zero", - "data": [0, false], - "valid": true - }, - { - "description": "true is not equal to one", - "data": [1, true], - "valid": true - }, - { - "description": "unique array of objects is valid", - "data": [{"foo": "bar"}, {"foo": "baz"}], - "valid": true - }, - { - "description": "non-unique array of objects is valid", - "data": [{"foo": "bar"}, {"foo": "bar"}], - "valid": true - }, - { - "description": "unique array of nested objects is valid", - "data": [ - {"foo": {"bar" : {"baz" : true}}}, - {"foo": {"bar" : {"baz" : false}}} - ], - "valid": true - }, - { - "description": "non-unique array of nested objects is valid", - "data": [ - {"foo": {"bar" : {"baz" : true}}}, - {"foo": {"bar" : {"baz" : true}}} - ], - "valid": true - }, - { - "description": "unique array of arrays is valid", - "data": [["foo"], ["bar"]], - "valid": true - }, - { - "description": "non-unique array of arrays is valid", - "data": [["foo"], ["foo"]], - "valid": true - }, - { - "description": "1 and true are unique", - "data": [1, true], - "valid": true - }, - { - "description": "0 and false are unique", - "data": [0, false], - "valid": true - }, - { - "description": "unique heterogeneous types are valid", - "data": [{}, [1], true, null, 1], - "valid": true - }, - { - "description": "non-unique heterogeneous types are valid", - "data": [{}, [1], true, null, {}, 1], - "valid": true - } - ] - }, - { - "description": "uniqueItems=false with an array of items", - "schema": { - "items": [{"type": "boolean"}, {"type": "boolean"}], - "uniqueItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [false, true], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [true, false], - "valid": true - }, - { - "description": "[false, false] from items array is valid", - "data": [false, false], - "valid": true - }, - { - "description": "[true, true] from items array is valid", - "data": [true, true], - "valid": true - }, - { - "description": "unique array extended from [false, true] is valid", - "data": [false, true, "foo", "bar"], - "valid": true - }, - { - "description": "unique array extended from [true, false] is valid", - "data": [true, false, "foo", "bar"], - "valid": true - }, - { - "description": "non-unique array extended from [false, true] is valid", - "data": [false, true, "foo", "foo"], - "valid": true - }, - { - "description": "non-unique array extended from [true, false] is valid", - "data": [true, false, "foo", "foo"], - "valid": true - } - ] - }, - { - "description": "uniqueItems=false with an array of items and additionalItems=false", - "schema": { - "items": [{"type": "boolean"}, {"type": "boolean"}], - "uniqueItems": false, - "additionalItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [false, true], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [true, false], - "valid": true - }, - { - "description": "[false, false] from items array is valid", - "data": [false, false], - "valid": true - }, - { - "description": "[true, true] from items array is valid", - "data": [true, true], - "valid": true - }, - { - "description": "extra items are invalid even if unique", - "data": [false, true, null], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/additionalItems.json b/src/test/resources/draft6/additionalItems.json deleted file mode 100644 index 5fe4ddb1e..000000000 --- a/src/test/resources/draft6/additionalItems.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "description": "additionalItems as schema", - "schema": { - "items": [ - {} - ], - "additionalItems": { - "type": "integer" - } - }, - "tests": [ - { - "description": "additional items match schema", - "data": [ - null, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "additional items do not match schema", - "data": [ - null, - 2, - 3, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "items is schema, no additionalItems", - "schema": { - "items": {}, - "additionalItems": false - }, - "tests": [ - { - "description": "all items match schema", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - } - ] - }, - { - "description": "array of items with no additionalItems", - "schema": { - "items": [ - {}, - {}, - {} - ], - "additionalItems": false - }, - "tests": [ - { - "description": "fewer number of items present", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "equal number of items present", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "additional items are not permitted", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "additionalItems as false without items", - "schema": { - "additionalItems": false - }, - "tests": [ - { - "description": "items defaults to empty schema so everything is valid", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - } - ] - }, - { - "description": "additionalItems are allowed by default", - "schema": { - "items": [ - { - "type": "integer" - } - ] - }, - "tests": [ - { - "description": "only the first item is validated", - "data": [ - 1, - "foo", - false - ], - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/additionalProperties.json b/src/test/resources/draft6/additionalProperties.json deleted file mode 100644 index 827bb1fba..000000000 --- a/src/test/resources/draft6/additionalProperties.json +++ /dev/null @@ -1,193 +0,0 @@ -[ - { - "description": "additionalProperties being false does not allow other properties", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "patternProperties": { - "^v": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": "boom" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobarbaz", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - }, - { - "description": "patternProperties are not additional properties", - "data": { - "foo": 1, - "vroom": 2 - }, - "valid": true - } - ] - }, - { - "description": "non-ASCII pattern with additionalProperties", - "schema": { - "patternProperties": { - "^á": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "matching the pattern is valid", - "data": { - "ármányos": 2 - }, - "valid": true - }, - { - "description": "not matching the pattern is invalid", - "data": { - "élmény": 2 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties allows a schema which should validate", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional valid property is valid", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": 12 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties can exist by itself", - "schema": { - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "an additional valid property is valid", - "data": { - "foo": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties are allowed by default", - "schema": { - "properties": { - "foo": {}, - "bar": {} - } - }, - "tests": [ - { - "description": "additional properties are allowed", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - } - ] - }, - { - "description": "additionalProperties should not look in applicators", - "schema": { - "allOf": [ - { - "properties": { - "foo": {} - } - } - ], - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "properties defined in allOf are not allowed", - "data": { - "foo": 1, - "bar": true - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/allOf.json b/src/test/resources/draft6/allOf.json deleted file mode 100644 index 703d4c050..000000000 --- a/src/test/resources/draft6/allOf.json +++ /dev/null @@ -1,288 +0,0 @@ -[ - { - "description": "allOf", - "schema": { - "allOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "allOf", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "mismatch second", - "data": { - "foo": "baz" - }, - "valid": false - }, - { - "description": "mismatch first", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type", - "data": { - "foo": "baz", - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "allOf with base schema", - "schema": { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ], - "allOf": [ - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - }, - { - "properties": { - "baz": { - "type": "null" - } - }, - "required": [ - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": "quux", - "bar": 2, - "baz": null - }, - "valid": true - }, - { - "description": "mismatch base schema", - "data": { - "foo": "quux", - "baz": null - }, - "valid": false - }, - { - "description": "mismatch first allOf", - "data": { - "bar": 2, - "baz": null - }, - "valid": false - }, - { - "description": "mismatch second allOf", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "mismatch both", - "data": { - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "allOf simple types", - "schema": { - "allOf": [ - { - "maximum": 30 - }, - { - "minimum": 20 - } - ] - }, - "tests": [ - { - "description": "valid", - "data": 25, - "valid": true - }, - { - "description": "mismatch one", - "data": 35, - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all true", - "schema": { - "allOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "allOf with boolean schemas, some false", - "schema": { - "allOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all false", - "schema": { - "allOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with one empty schema", - "schema": { - "allOf": [ - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with two empty schemas", - "schema": { - "allOf": [ - {}, - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with the first empty schema", - "schema": { - "allOf": [ - {}, - { - "type": "number" - } - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with the last empty schema", - "schema": { - "allOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/anyOf.json b/src/test/resources/draft6/anyOf.json deleted file mode 100644 index f35e2194b..000000000 --- a/src/test/resources/draft6/anyOf.json +++ /dev/null @@ -1,224 +0,0 @@ -[ - { - "description": "anyOf", - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first anyOf valid", - "data": 1, - "valid": true - }, - { - "description": "second anyOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both anyOf valid", - "data": 3, - "valid": true - }, - { - "description": "neither anyOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "anyOf with base schema", - "schema": { - "type": "string", - "anyOf": [ - { - "maxLength": 2 - }, - { - "minLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one anyOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both anyOf invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf with boolean schemas, all true", - "schema": { - "anyOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, some true", - "schema": { - "anyOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, all false", - "schema": { - "anyOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf complex types", - "schema": { - "anyOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first anyOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second anyOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both anyOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "neither anyOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "anyOf with one empty schema", - "schema": { - "anyOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is valid", - "data": 123, - "valid": true - } - ] - }, - { - "description": "nested anyOf, to check validation semantics", - "schema": { - "anyOf": [ - { - "anyOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/boolean_schema.json b/src/test/resources/draft6/boolean_schema.json deleted file mode 100644 index ee241ef92..000000000 --- a/src/test/resources/draft6/boolean_schema.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "description": "boolean schema 'true'", - "schema": true, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "boolean true is valid", - "data": true, - "valid": true - }, - { - "description": "boolean false is valid", - "data": false, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - }, - { - "description": "array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "boolean schema 'false'", - "schema": false, - "tests": [ - { - "description": "number is invalid", - "data": 1, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "boolean true is invalid", - "data": true, - "valid": false - }, - { - "description": "boolean false is invalid", - "data": false, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - }, - { - "description": "object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "empty object is invalid", - "data": {}, - "valid": false - }, - { - "description": "array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/const.json b/src/test/resources/draft6/const.json deleted file mode 100644 index 69b5012f2..000000000 --- a/src/test/resources/draft6/const.json +++ /dev/null @@ -1,214 +0,0 @@ -[ - { - "description": "const validation", - "schema": { - "const": 2 - }, - "tests": [ - { - "description": "same value is valid", - "data": 2, - "valid": true - }, - { - "description": "another value is invalid", - "data": 5, - "valid": false - }, - { - "description": "another type is invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "const with object", - "schema": { - "const": { - "foo": "bar", - "baz": "bax" - } - }, - "tests": [ - { - "description": "same object is valid", - "data": { - "foo": "bar", - "baz": "bax" - }, - "valid": true - }, - { - "description": "same object with different property order is valid", - "data": { - "baz": "bax", - "foo": "bar" - }, - "valid": true - }, - { - "description": "another object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "another type is invalid", - "data": [ - 1, - 2 - ], - "valid": false - } - ] - }, - { - "description": "const with array", - "schema": { - "const": [ - { - "foo": "bar" - } - ] - }, - "tests": [ - { - "description": "same array is valid", - "data": [ - { - "foo": "bar" - } - ], - "valid": true - }, - { - "description": "another array item is invalid", - "data": [ - 2 - ], - "valid": false - }, - { - "description": "array with additional items is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - } - ] - }, - { - "description": "const with null", - "schema": { - "const": null - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "not null is invalid", - "data": 0, - "valid": false - } - ] - }, - { - "description": "const with false does not match 0", - "schema": { - "const": false - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "const with true does not match 1", - "schema": { - "const": true - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "const with 0 does not match false", - "schema": { - "const": 0 - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "const with 1 does not match true", - "schema": { - "const": 1 - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/contains.json b/src/test/resources/draft6/contains.json deleted file mode 100644 index 62b081f3f..000000000 --- a/src/test/resources/draft6/contains.json +++ /dev/null @@ -1,143 +0,0 @@ -[ - { - "description": "contains keyword validation", - "schema": { - "contains": { - "minimum": 5 - } - }, - "tests": [ - { - "description": "array with item matching schema (5) is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with item matching schema (6) is valid", - "data": [ - 3, - 4, - 6 - ], - "valid": true - }, - { - "description": "array with two items matching schema (5, 6) is valid", - "data": [ - 3, - 4, - 5, - 6 - ], - "valid": true - }, - { - "description": "array without items matching schema is invalid", - "data": [ - 2, - 3, - 4 - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - }, - { - "description": "not array is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "contains keyword with const keyword", - "schema": { - "contains": { - "const": 5 - } - }, - "tests": [ - { - "description": "array with item 5 is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with two items 5 is valid", - "data": [ - 3, - 4, - 5, - 5 - ], - "valid": true - }, - { - "description": "array without item 5 is invalid", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema true", - "schema": { - "contains": true - }, - "tests": [ - { - "description": "any non-empty array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema false", - "schema": { - "contains": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - }, - { - "description": "non-arrays are valid", - "data": "contains does not apply to strings", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/default.json b/src/test/resources/draft6/default.json deleted file mode 100644 index 983713399..000000000 --- a/src/test/resources/draft6/default.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "description": "invalid type for default", - "schema": { - "properties": { - "foo": { - "type": "integer", - "default": [] - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "foo": 13 - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - }, - { - "description": "invalid string value for default", - "schema": { - "properties": { - "bar": { - "type": "string", - "minLength": 4, - "default": "bad" - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "bar": "good" - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/definitions.json b/src/test/resources/draft6/definitions.json deleted file mode 100644 index 60cc79cb2..000000000 --- a/src/test/resources/draft6/definitions.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "description": "valid definition", - "schema": { - "$ref": "http://json-schema.org/draft-06/schema#" - }, - "tests": [ - { - "description": "valid definition schema", - "data": { - "definitions": { - "foo": { - "type": "integer" - } - } - }, - "valid": true - } - ] - }, - { - "description": "invalid definition", - "schema": { - "$ref": "http://json-schema.org/draft-06/schema#" - }, - "tests": [ - { - "description": "invalid definition schema", - "data": { - "definitions": { - "foo": { - "type": 1 - } - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/dependencies.json b/src/test/resources/draft6/dependencies.json deleted file mode 100644 index 3a750ffac..000000000 --- a/src/test/resources/draft6/dependencies.json +++ /dev/null @@ -1,340 +0,0 @@ -[ - { - "description": "dependencies", - "schema": { - "dependencies": { - "bar": [ - "foo" - ] - } - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependant", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "with dependency", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "bar" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "dependencies with empty array", - "schema": { - "dependencies": { - "bar": [] - } - }, - "tests": [ - { - "description": "empty object", - "data": {}, - "valid": true - }, - { - "description": "object with one property", - "data": { - "bar": 2 - }, - "valid": true - } - ] - }, - { - "description": "multiple dependencies", - "schema": { - "dependencies": { - "quux": [ - "foo", - "bar" - ] - } - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependants", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "with dependencies", - "data": { - "foo": 1, - "bar": 2, - "quux": 3 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "foo": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing other dependency", - "data": { - "bar": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing both dependencies", - "data": { - "quux": 1 - }, - "valid": false - } - ] - }, - { - "description": "multiple dependencies subschema", - "schema": { - "dependencies": { - "bar": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "no dependency", - "data": { - "foo": "quux" - }, - "valid": true - }, - { - "description": "wrong type", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type other", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - }, - { - "description": "wrong type both", - "data": { - "foo": "quux", - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "dependencies with boolean subschemas", - "schema": { - "dependencies": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "object with property having schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property having schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "empty array of dependencies", - "schema": { - "dependencies": { - "foo": [] - } - }, - "tests": [ - { - "description": "object with property is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - }, - { - "description": "non-object is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "dependencies": { - "foo\nbar": [ - "foo\rbar" - ], - "foo\tbar": { - "minProperties": 4 - }, - "foo'bar": { - "required": [ - "foo\"bar" - ] - }, - "foo\"bar": [ - "foo'bar" - ] - } - }, - "tests": [ - { - "description": "valid object 1", - "data": { - "foo\nbar": 1, - "foo\rbar": 2 - }, - "valid": true - }, - { - "description": "valid object 2", - "data": { - "foo\tbar": 1, - "a": 2, - "b": 3, - "c": 4 - }, - "valid": true - }, - { - "description": "valid object 3", - "data": { - "foo'bar": 1, - "foo\"bar": 2 - }, - "valid": true - }, - { - "description": "invalid object 1", - "data": { - "foo\nbar": 1, - "foo": 2 - }, - "valid": false - }, - { - "description": "invalid object 2", - "data": { - "foo\tbar": 1, - "a": 2 - }, - "valid": false - }, - { - "description": "invalid object 3", - "data": { - "foo'bar": 1 - }, - "valid": false - }, - { - "description": "invalid object 4", - "data": { - "foo\"bar": 2 - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/enum.json b/src/test/resources/draft6/enum.json deleted file mode 100644 index d0b8aacdf..000000000 --- a/src/test/resources/draft6/enum.json +++ /dev/null @@ -1,233 +0,0 @@ -[ - { - "description": "simple enum validation", - "schema": { - "enum": [ - 1, - 2, - 3 - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": 1, - "valid": true - }, - { - "description": "something else is invalid", - "data": 4, - "valid": false - } - ] - }, - { - "description": "heterogeneous enum validation", - "schema": { - "enum": [ - 6, - "foo", - [], - true, - { - "foo": 12 - } - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": [], - "valid": true - }, - { - "description": "something else is invalid", - "data": null, - "valid": false - }, - { - "description": "objects are deep compared", - "data": { - "foo": false - }, - "valid": false - } - ] - }, - { - "description": "enums in properties", - "schema": { - "type": "object", - "properties": { - "foo": { - "enum": [ - "foo" - ] - }, - "bar": { - "enum": [ - "bar" - ] - } - }, - "required": [ - "bar" - ] - }, - "tests": [ - { - "description": "both properties are valid", - "data": { - "foo": "foo", - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing optional property is valid", - "data": { - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing required property is invalid", - "data": { - "foo": "foo" - }, - "valid": false - }, - { - "description": "missing all properties is invalid", - "data": {}, - "valid": false - } - ] - }, - { - "description": "enum with escaped characters", - "schema": { - "enum": [ - "foo\nbar", - "foo\rbar" - ] - }, - "tests": [ - { - "description": "member 1 is valid", - "data": "foo\nbar", - "valid": true - }, - { - "description": "member 2 is valid", - "data": "foo\rbar", - "valid": true - }, - { - "description": "another string is invalid", - "data": "abc", - "valid": false - } - ] - }, - { - "description": "enum with false does not match 0", - "schema": { - "enum": [ - false - ] - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "enum with true does not match 1", - "schema": { - "enum": [ - true - ] - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "enum with 0 does not match false", - "schema": { - "enum": [ - 0 - ] - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "enum with 1 does not match true", - "schema": { - "enum": [ - 1 - ] - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/exclusiveMaximum.json b/src/test/resources/draft6/exclusiveMaximum.json deleted file mode 100644 index fc8c4bb47..000000000 --- a/src/test/resources/draft6/exclusiveMaximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMaximum validation", - "schema": { - "exclusiveMaximum": 3.0 - }, - "tests": [ - { - "description": "below the exclusiveMaximum is valid", - "data": 2.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 3.0, - "valid": false - }, - { - "description": "above the exclusiveMaximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/exclusiveMinimum.json b/src/test/resources/draft6/exclusiveMinimum.json deleted file mode 100644 index 815d8fbe1..000000000 --- a/src/test/resources/draft6/exclusiveMinimum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMinimum validation", - "schema": { - "exclusiveMinimum": 1.1 - }, - "tests": [ - { - "description": "above the exclusiveMinimum is valid", - "data": 1.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "below the exclusiveMinimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/format.json b/src/test/resources/draft6/format.json deleted file mode 100644 index 08211c067..000000000 --- a/src/test/resources/draft6/format.json +++ /dev/null @@ -1,344 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of hostnames", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of JSON pointers", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI references", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI templates", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/items.json b/src/test/resources/draft6/items.json deleted file mode 100644 index d40f7b50a..000000000 --- a/src/test/resources/draft6/items.json +++ /dev/null @@ -1,506 +0,0 @@ -[ - { - "description": "a schema given for items", - "schema": { - "items": { - "type": "integer" - } - }, - "tests": [ - { - "description": "valid items", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "wrong type of items", - "data": [ - 1, - "x" - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "length": 1 - }, - "valid": true - } - ] - }, - { - "description": "an array of schemas for items", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "tests": [ - { - "description": "correct types", - "data": [ - 1, - "foo" - ], - "valid": true - }, - { - "description": "wrong types", - "data": [ - "foo", - 1 - ], - "valid": false - }, - { - "description": "incomplete array of items", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with additional items", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array", - "data": [], - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "1": "valid", - "length": 2 - }, - "valid": true - } - ] - }, - { - "description": "items with boolean schema (true)", - "schema": { - "items": true - }, - "tests": [ - { - "description": "any array is valid", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schema (false)", - "schema": { - "items": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - 1, - "foo", - true - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schemas", - "schema": { - "items": [ - true, - false - ] - }, - "tests": [ - { - "description": "array with one item is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with two items is invalid", - "data": [ - 1, - "foo" - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items and subitems", - "schema": { - "definitions": { - "item": { - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/definitions/sub-item" - }, - { - "$ref": "#/definitions/sub-item" - } - ] - }, - "sub-item": { - "type": "object", - "required": [ - "foo" - ] - } - }, - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/definitions/item" - }, - { - "$ref": "#/definitions/item" - }, - { - "$ref": "#/definitions/item" - } - ] - }, - "tests": [ - { - "description": "valid items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": true - }, - { - "description": "too many items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "too many sub-items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong item", - "data": [ - { - "foo": null - }, - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong sub-item", - "data": [ - [ - {}, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "fewer items is valid", - "data": [ - [ - { - "foo": null - } - ], - [ - { - "foo": null - } - ] - ], - "valid": true - } - ] - }, - { - "description": "nested items", - "schema": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - } - } - } - } - }, - "tests": [ - { - "description": "valid nested array", - "data": [ - [ - [ - [ - 1 - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": true - }, - { - "description": "nested array with invalid type", - "data": [ - [ - [ - [ - "1" - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": false - }, - { - "description": "not deep enough", - "data": [ - [ - [ - 1 - ], - [ - 2 - ], - [ - 3 - ] - ], - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/maxItems.json b/src/test/resources/draft6/maxItems.json deleted file mode 100644 index 52f0f2a81..000000000 --- a/src/test/resources/draft6/maxItems.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "description": "maxItems validation", - "schema": { - "maxItems": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "too long is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "foobar", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/maxLength.json b/src/test/resources/draft6/maxLength.json deleted file mode 100644 index f297da029..000000000 --- a/src/test/resources/draft6/maxLength.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "maxLength validation", - "schema": { - "maxLength": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": "f", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too long is invalid", - "data": "foo", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - }, - { - "description": "two supplementary Unicode code points is long enough", - "data": "\uD83D\uDCA9\uD83D\uDCA9", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/maxProperties.json b/src/test/resources/draft6/maxProperties.json deleted file mode 100644 index cffe1ba3d..000000000 --- a/src/test/resources/draft6/maxProperties.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "description": "maxProperties validation", - "schema": { - "maxProperties": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "too long is invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/maximum.json b/src/test/resources/draft6/maximum.json deleted file mode 100644 index 194ee32ee..000000000 --- a/src/test/resources/draft6/maximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "maximum validation", - "schema": { - "maximum": 3.0 - }, - "tests": [ - { - "description": "below the maximum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 3.0, - "valid": true - }, - { - "description": "above the maximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/minItems.json b/src/test/resources/draft6/minItems.json deleted file mode 100644 index 8a89e7aed..000000000 --- a/src/test/resources/draft6/minItems.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "minItems validation", - "schema": { - "minItems": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "too short is invalid", - "data": [], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/minLength.json b/src/test/resources/draft6/minLength.json deleted file mode 100644 index ee310f821..000000000 --- a/src/test/resources/draft6/minLength.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "minLength validation", - "schema": { - "minLength": 2 - }, - "tests": [ - { - "description": "longer is valid", - "data": "foo", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too short is invalid", - "data": "f", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1, - "valid": true - }, - { - "description": "one supplementary Unicode code point is not long enough", - "data": "\uD83D\uDCA9", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/minProperties.json b/src/test/resources/draft6/minProperties.json deleted file mode 100644 index c700fe42e..000000000 --- a/src/test/resources/draft6/minProperties.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "minProperties validation", - "schema": { - "minProperties": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "too short is invalid", - "data": {}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/minimum.json b/src/test/resources/draft6/minimum.json deleted file mode 100644 index 47070907a..000000000 --- a/src/test/resources/draft6/minimum.json +++ /dev/null @@ -1,63 +0,0 @@ -[ - { - "description": "minimum validation", - "schema": { - "minimum": 1.1 - }, - "tests": [ - { - "description": "above the minimum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 1.1, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - }, - { - "description": "minimum validation with signed integer", - "schema": { - "minimum": -2 - }, - "tests": [ - { - "description": "negative above the minimum is valid", - "data": -1, - "valid": true - }, - { - "description": "positive above the minimum is valid", - "data": 0, - "valid": true - }, - { - "description": "boundary point is valid", - "data": -2, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": -3, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/multipleOf.json b/src/test/resources/draft6/multipleOf.json deleted file mode 100644 index 886ea837e..000000000 --- a/src/test/resources/draft6/multipleOf.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "description": "by int", - "schema": { - "multipleOf": 2 - }, - "tests": [ - { - "description": "int by int", - "data": 10, - "valid": true - }, - { - "description": "int by int fail", - "data": 7, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "by number", - "schema": { - "multipleOf": 1.5 - }, - "tests": [ - { - "description": "zero is multiple of anything", - "data": 0, - "valid": true - }, - { - "description": "4.5 is multiple of 1.5", - "data": 4.5, - "valid": true - }, - { - "description": "35 is not multiple of 1.5", - "data": 35, - "valid": false - } - ] - }, - { - "description": "by small number", - "schema": { - "multipleOf": 0.0001 - }, - "tests": [ - { - "description": "0.0075 is multiple of 0.0001", - "data": 0.0075, - "valid": true - }, - { - "description": "0.00751 is not multiple of 0.0001", - "data": 0.00751, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/not.json b/src/test/resources/draft6/not.json deleted file mode 100644 index c2d2e411e..000000000 --- a/src/test/resources/draft6/not.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "description": "not", - "schema": { - "not": { - "type": "integer" - } - }, - "tests": [ - { - "description": "allowed", - "data": "foo", - "valid": true - }, - { - "description": "disallowed", - "data": 1, - "valid": false - } - ] - }, - { - "description": "not multiple types", - "schema": { - "not": { - "type": [ - "integer", - "boolean" - ] - } - }, - "tests": [ - { - "description": "valid", - "data": "foo", - "valid": true - }, - { - "description": "mismatch", - "data": 1, - "valid": false - }, - { - "description": "other mismatch", - "data": true, - "valid": false - } - ] - }, - { - "description": "not more complex schema", - "schema": { - "not": { - "type": "object", - "properties": { - "foo": { - "type": "string" - } - } - } - }, - "tests": [ - { - "description": "match", - "data": 1, - "valid": true - }, - { - "description": "other match", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "foo": "bar" - }, - "valid": false - } - ] - }, - { - "description": "forbidden property", - "schema": { - "properties": { - "foo": { - "not": {} - } - } - }, - "tests": [ - { - "description": "property present", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "property absent", - "data": { - "bar": 1, - "baz": 2 - }, - "valid": true - } - ] - }, - { - "description": "not with boolean schema true", - "schema": { - "not": true - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "not with boolean schema false", - "schema": { - "not": false - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/oneOf.json b/src/test/resources/draft6/oneOf.json deleted file mode 100644 index c637edda0..000000000 --- a/src/test/resources/draft6/oneOf.json +++ /dev/null @@ -1,332 +0,0 @@ -[ - { - "description": "oneOf", - "schema": { - "oneOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": 1, - "valid": true - }, - { - "description": "second oneOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both oneOf valid", - "data": 3, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "oneOf with base schema", - "schema": { - "type": "string", - "oneOf": [ - { - "minLength": 2 - }, - { - "maxLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one oneOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both oneOf valid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all true", - "schema": { - "oneOf": [ - true, - true, - true - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, one true", - "schema": { - "oneOf": [ - true, - false, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "oneOf with boolean schemas, more than one true", - "schema": { - "oneOf": [ - true, - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all false", - "schema": { - "oneOf": [ - false, - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf complex types", - "schema": { - "oneOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second oneOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both oneOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": false - }, - { - "description": "neither oneOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - }, - { - "description": "neither oneOf given (complex)", - "data": {}, - "valid": false - } - ] - }, - { - "description": "oneOf with empty schema", - "schema": { - "oneOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "one valid - valid", - "data": "foo", - "valid": true - }, - { - "description": "both valid - invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "oneOf with required", - "schema": { - "type": "object", - "oneOf": [ - { - "required": [ - "foo", - "bar" - ] - }, - { - "required": [ - "foo", - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "both invalid - invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "first valid - valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "second valid - valid", - "data": { - "foo": 1, - "baz": 3 - }, - "valid": true - }, - { - "description": "both valid - invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - } - ] - }, - { - "description": "oneOf with missing optional property", - "schema": { - "oneOf": [ - { - "properties": { - "bar": true, - "baz": true - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": true - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": { - "bar": 8 - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "foo": "foo" - }, - "valid": true - }, - { - "description": "both oneOf valid", - "data": { - "foo": "foo", - "bar": 8 - }, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": { - "baz": "quux" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/optional/bignum.json b/src/test/resources/draft6/optional/bignum.json deleted file mode 100644 index 4e5bafdfa..000000000 --- a/src/test/resources/draft6/optional/bignum.json +++ /dev/null @@ -1,119 +0,0 @@ -[ - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a bignum is an integer", - "data": 12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a bignum is a number", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a negative bignum is an integer", - "data": -12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a negative bignum is a number", - "data": -98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "string", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "a bignum is not a string", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "maximum": 18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision", - "schema": { - "exclusiveMaximum": 972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 972783798187987123879878123.188781371, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "minimum": -18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision on negative numbers", - "schema": { - "exclusiveMinimum": -972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -972783798187987123879878123.188781371, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/optional/ecmascript-regex.json b/src/test/resources/draft6/optional/ecmascript-regex.json deleted file mode 100644 index a27652188..000000000 --- a/src/test/resources/draft6/optional/ecmascript-regex.json +++ /dev/null @@ -1,215 +0,0 @@ -[ - { - "description": "ECMA 262 regex non-compliance", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "ECMA 262 has no support for \\Z anchor from .NET", - "data": "^\\S(|(.|\\n)*\\S)\\Z", - "valid": false - } - ] - }, - { - "description": "ECMA 262 regex $ does not match trailing newline", - "schema": { - "type": "string", - "pattern": "^abc$" - }, - "tests": [ - { - "description": "matches in Python, but should not in jsonschema", - "data": "abc\n", - "valid": false - }, - { - "description": "should match", - "data": "abc", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex converts \\a to ascii BEL", - "schema": { - "type": "string", - "pattern": "^\\a$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\a", - "valid": false - }, - { - "description": "matches", - "data": "\u0007", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and upper letter", - "schema": { - "type": "string", - "pattern": "^\\cC$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cC", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and lower letter", - "schema": { - "type": "string", - "pattern": "^\\cc$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cc", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\d matches ascii digits only", - "schema": { - "type": "string", - "pattern": "^\\d$" - }, - "tests": [ - { - "description": "ASCII zero matches", - "data": "0", - "valid": true - }, - { - "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", - "data": "߀", - "valid": false - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) does not match", - "data": "\u07c0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\D matches everything but ascii digits", - "schema": { - "type": "string", - "pattern": "^\\D$" - }, - "tests": [ - { - "description": "ASCII zero does not match", - "data": "0", - "valid": false - }, - { - "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", - "data": "߀", - "valid": true - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) matches", - "data": "\u07c0", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\w matches ascii letters only", - "schema": { - "type": "string", - "pattern": "^\\w$" - }, - "tests": [ - { - "description": "ASCII 'a' matches", - "data": "a", - "valid": true - }, - { - "description": "latin-1 e-acute does not match (unlike e.g. Python)", - "data": "é", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\w matches everything but ascii letters", - "schema": { - "type": "string", - "pattern": "^\\W$" - }, - "tests": [ - { - "description": "ASCII 'a' does not match", - "data": "a", - "valid": false - }, - { - "description": "latin-1 e-acute matches (unlike e.g. Python)", - "data": "é", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\s matches ascii whitespace only", - "schema": { - "type": "string", - "pattern": "^\\s$" - }, - "tests": [ - { - "description": "ASCII space matches", - "data": " ", - "valid": true - }, - { - "description": "latin-1 non-breaking-space does not match (unlike e.g. Python)", - "data": "\u00a0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\S matches everything but ascii whitespace", - "schema": { - "type": "string", - "pattern": "^\\S$" - }, - "tests": [ - { - "description": "ASCII space does not match", - "data": " ", - "valid": false - }, - { - "description": "latin-1 non-breaking-space matches (unlike e.g. Python)", - "data": "\u00a0", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/optional/format.json b/src/test/resources/draft6/optional/format.json deleted file mode 100644 index d556f2eff..000000000 --- a/src/test/resources/draft6/optional/format.json +++ /dev/null @@ -1,509 +0,0 @@ -[ - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "a valid date-time string", - "data": "1963-06-19T08:30:06.283185Z", - "valid": true - }, - { - "description": "a valid date-time string without second fraction", - "data": "1963-06-19T08:30:06Z", - "valid": true - }, - { - "description": "a valid date-time string with plus offset", - "data": "1937-01-01T12:00:27.87+00:20", - "valid": true - }, - { - "description": "a valid date-time string with minus offset", - "data": "1990-12-31T15:59:50.123-08:00", - "valid": true - }, - { - "description": "a invalid day in date-time string", - "data": "1990-02-31T15:59:60.123-08:00", - "valid": false - }, - { - "description": "an invalid offset in date-time string", - "data": "1990-12-31T15:59:60-24:00", - "valid": false - }, - { - "description": "an invalid closing Z after time-zone offset", - "data": "1963-06-19T08:30:06.28123+01:00Z", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963 08:30:06 PST", - "valid": false - }, - { - "description": "case-insensitive T and Z", - "data": "1963-06-19t08:30:06.283185z", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350T01:01:01", - "valid": false - } - ] - }, - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "a valid URL with anchor tag", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid URL with anchor tag and parantheses", - "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", - "valid": true - }, - { - "description": "a valid URL with URL-encoded stuff", - "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid puny-coded URL ", - "data": "http://xn--nw2a.xn--j6w193g/", - "valid": true - }, - { - "description": "a valid URL with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid URL based on IPv4", - "data": "http://223.255.255.254", - "valid": true - }, - { - "description": "a valid URL with ftp scheme", - "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", - "valid": true - }, - { - "description": "a valid URL for a simple text file", - "data": "http://www.ietf.org/rfc/rfc2396.txt", - "valid": true - }, - { - "description": "a valid URL ", - "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", - "valid": true - }, - { - "description": "a valid mailto URI", - "data": "mailto:John.Doe@example.com", - "valid": true - }, - { - "description": "a valid newsgroup URI", - "data": "news:comp.infosystems.www.servers.unix", - "valid": true - }, - { - "description": "a valid tel URI", - "data": "tel:+1-816-555-1212", - "valid": true - }, - { - "description": "a valid URN", - "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - "valid": true - }, - { - "description": "an invalid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": false - }, - { - "description": "an invalid relative URI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid URI", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "an invalid URI though valid URI reference", - "data": "abc", - "valid": false - }, - { - "description": "an invalid URI with spaces", - "data": "http:// shouldfail.com", - "valid": false - }, - { - "description": "an invalid URI with spaces and missing scheme", - "data": ":// should fail", - "valid": false - } - ] - }, - { - "description": "validation of URI References", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "a valid URI", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid relative URI Reference", - "data": "/abc", - "valid": true - }, - { - "description": "an invalid URI Reference", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "a valid URI Reference", - "data": "abc", - "valid": true - }, - { - "description": "a valid URI fragment", - "data": "#fragment", - "valid": true - }, - { - "description": "an invalid URI fragment", - "data": "#frag\\ment", - "valid": false - } - ] - }, - { - "description": "format: uri-template", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "a valid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term}", - "valid": true - }, - { - "description": "an invalid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term", - "valid": false - }, - { - "description": "a valid uri-template without variables", - "data": "http://example.com/dictionary", - "valid": true - }, - { - "description": "a valid relative uri-template", - "data": "dictionary/{term:1}/{term}", - "valid": true - } - ] - }, - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "a valid e-mail address", - "data": "joe.bloggs@example.com", - "valid": true - }, - { - "description": "an invalid e-mail address", - "data": "2962", - "valid": false - } - ] - }, - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - } - ] - }, - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "a valid IPv6 address", - "data": "::1", - "valid": true - }, - { - "description": "an IPv6 address with out-of-range values", - "data": "12345::", - "valid": false - }, - { - "description": "an IPv6 address with too many components", - "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", - "valid": false - }, - { - "description": "an IPv6 address containing illegal characters", - "data": "::laptop", - "valid": false - } - ] - }, - { - "description": "validation of host names", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "a valid host name", - "data": "www.example.com", - "valid": true - }, - { - "description": "a host name starting with an illegal character", - "data": "-a-host-name-that-starts-with--", - "valid": false - }, - { - "description": "a host name containing illegal characters", - "data": "not_a_valid_host_name", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", - "valid": false - } - ] - }, - { - "description": "validation of JSON-pointers (JSON String Representation)", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "a valid JSON-pointer", - "data": "/foo/bar~0/baz~1/%a", - "valid": true - }, - { - "description": "not a valid JSON-pointer (~ not escaped)", - "data": "/foo/bar~", - "valid": false - }, - { - "description": "valid JSON-pointer with empty segment", - "data": "/foo//bar", - "valid": true - }, - { - "description": "valid JSON-pointer with the last empty segment", - "data": "/foo/bar/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #1", - "data": "", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #2", - "data": "/foo", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #3", - "data": "/foo/0", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #4", - "data": "/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #5", - "data": "/a~1b", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #6", - "data": "/c%d", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #7", - "data": "/e^f", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #8", - "data": "/g|h", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #9", - "data": "/i\\j", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #10", - "data": "/k\"l", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #11", - "data": "/ ", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #12", - "data": "/m~0n", - "valid": true - }, - { - "description": "valid JSON-pointer used adding to the last array position", - "data": "/foo/-", - "valid": true - }, - { - "description": "valid JSON-pointer (- used as object member name)", - "data": "/foo/-/bar", - "valid": true - }, - { - "description": "valid JSON-pointer (multiple escaped characters)", - "data": "/~1~0~0~1~1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #1", - "data": "/~1.1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #2", - "data": "/~0.1", - "valid": true - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", - "data": "#", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", - "data": "#/", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", - "data": "#a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #1", - "data": "/~0~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #2", - "data": "/~0/~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #1", - "data": "/~2", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #2", - "data": "/~-1", - "valid": false - }, - { - "description": "not a valid JSON-pointer (multiple characters not escaped)", - "data": "/~~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", - "data": "a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", - "data": "0", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", - "data": "a/a", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/optional/ipv4.json b/src/test/resources/draft6/optional/ipv4.json deleted file mode 100644 index 6b166c70c..000000000 --- a/src/test/resources/draft6/optional/ipv4.json +++ /dev/null @@ -1,84 +0,0 @@ -[ - { - "description": "validation of IP addresses", - "schema": { "format": "ipv4" }, - "tests": [ - { - "description": "all string formats ignore integers", - "data": 12, - "valid": true - }, - { - "description": "all string formats ignore floats", - "data": 13.7, - "valid": true - }, - { - "description": "all string formats ignore objects", - "data": {}, - "valid": true - }, - { - "description": "all string formats ignore arrays", - "data": [], - "valid": true - }, - { - "description": "all string formats ignore booleans", - "data": false, - "valid": true - }, - { - "description": "all string formats ignore nulls", - "data": null, - "valid": true - }, - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - }, - { - "description": "an IP address as an integer (decimal)", - "data": "2130706433", - "valid": false - }, - { - "description": "leading zeroes should be rejected, as they are treated as octals", - "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", - "data": "087.10.0.1", - "valid": false - }, - { - "description": "value without leading zero is valid", - "data": "87.10.0.1", - "valid": true - }, - { - "description": "non-ascii digits should be rejected", - "data": "1২7.0.0.1", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/optional/zeroTerminatedFloats.json b/src/test/resources/draft6/optional/zeroTerminatedFloats.json deleted file mode 100644 index 046c7a5e3..000000000 --- a/src/test/resources/draft6/optional/zeroTerminatedFloats.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "description": "some languages do not distinguish between different types of numeric value", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a float without fractional part is an integer", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/pattern.json b/src/test/resources/draft6/pattern.json deleted file mode 100644 index 273559706..000000000 --- a/src/test/resources/draft6/pattern.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "description": "pattern validation", - "schema": { - "pattern": "^a*$" - }, - "tests": [ - { - "description": "a matching pattern is valid", - "data": "aaa", - "valid": true - }, - { - "description": "a non-matching pattern is invalid", - "data": "abc", - "valid": false - }, - { - "description": "ignores non-strings", - "data": true, - "valid": true - } - ] - }, - { - "description": "pattern is not anchored", - "schema": { - "pattern": "a+" - }, - "tests": [ - { - "description": "matches a substring", - "data": "xxaayy", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/patternProperties.json b/src/test/resources/draft6/patternProperties.json deleted file mode 100644 index 8823ed971..000000000 --- a/src/test/resources/draft6/patternProperties.json +++ /dev/null @@ -1,202 +0,0 @@ -[ - { - "description": "patternProperties validates properties matching a regex", - "schema": { - "patternProperties": { - "f.*o": { - "type": "integer" - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "multiple valid matches is valid", - "data": { - "foo": 1, - "foooooo": 2 - }, - "valid": true - }, - { - "description": "a single invalid match is invalid", - "data": { - "foo": "bar", - "fooooo": 2 - }, - "valid": false - }, - { - "description": "multiple invalid matches is invalid", - "data": { - "foo": "bar", - "foooooo": "baz" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foo", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "multiple simultaneous patternProperties are validated", - "schema": { - "patternProperties": { - "a*": { - "type": "integer" - }, - "aaa*": { - "maximum": 20 - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "a": 21 - }, - "valid": true - }, - { - "description": "a simultaneous match is valid", - "data": { - "aaaa": 18 - }, - "valid": true - }, - { - "description": "multiple matches is valid", - "data": { - "a": 21, - "aaaa": 18 - }, - "valid": true - }, - { - "description": "an invalid due to one is invalid", - "data": { - "a": "bar" - }, - "valid": false - }, - { - "description": "an invalid due to the other is invalid", - "data": { - "aaaa": 31 - }, - "valid": false - }, - { - "description": "an invalid due to both is invalid", - "data": { - "aaa": "foo", - "aaaa": 31 - }, - "valid": false - } - ] - }, - { - "description": "regexes are not anchored by default and are case sensitive", - "schema": { - "patternProperties": { - "[0-9]{2,}": { - "type": "boolean" - }, - "X_": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "non recognized members are ignored", - "data": { - "answer 1": "42" - }, - "valid": true - }, - { - "description": "recognized members are accounted for", - "data": { - "a31b": null - }, - "valid": false - }, - { - "description": "regexes are case sensitive", - "data": { - "a_x_3": 3 - }, - "valid": true - }, - { - "description": "regexes are case sensitive, 2", - "data": { - "a_X_3": 3 - }, - "valid": false - } - ] - }, - { - "description": "patternProperties with boolean schemas", - "schema": { - "patternProperties": { - "f.*": true, - "b.*": false - } - }, - "tests": [ - { - "description": "object with property matching schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property matching schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/properties.json b/src/test/resources/draft6/properties.json deleted file mode 100644 index daf6708f3..000000000 --- a/src/test/resources/draft6/properties.json +++ /dev/null @@ -1,238 +0,0 @@ -[ - { - "description": "object properties validation", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "both properties present and valid is valid", - "data": { - "foo": 1, - "bar": "baz" - }, - "valid": true - }, - { - "description": "one property invalid is invalid", - "data": { - "foo": 1, - "bar": {} - }, - "valid": false - }, - { - "description": "both properties invalid is invalid", - "data": { - "foo": [], - "bar": {} - }, - "valid": false - }, - { - "description": "doesn't invalidate other properties", - "data": { - "quux": [] - }, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "properties, patternProperties, additionalProperties interaction", - "schema": { - "properties": { - "foo": { - "type": "array", - "maxItems": 3 - }, - "bar": { - "type": "array" - } - }, - "patternProperties": { - "f.o": { - "minItems": 2 - } - }, - "additionalProperties": { - "type": "integer" - } - }, - "tests": [ - { - "description": "property validates property", - "data": { - "foo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "property invalidates property", - "data": { - "foo": [ - 1, - 2, - 3, - 4 - ] - }, - "valid": false - }, - { - "description": "patternProperty invalidates property", - "data": { - "foo": [] - }, - "valid": false - }, - { - "description": "patternProperty validates nonproperty", - "data": { - "fxo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "patternProperty invalidates nonproperty", - "data": { - "fxo": [] - }, - "valid": false - }, - { - "description": "additionalProperty ignores property", - "data": { - "bar": [] - }, - "valid": true - }, - { - "description": "additionalProperty validates others", - "data": { - "quux": 3 - }, - "valid": true - }, - { - "description": "additionalProperty invalidates others", - "data": { - "quux": "foo" - }, - "valid": false - } - ] - }, - { - "description": "properties with boolean schema", - "schema": { - "properties": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "no property present is valid", - "data": {}, - "valid": true - }, - { - "description": "only 'true' property present is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "only 'false' property present is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "both properties present is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "properties with escaped characters", - "schema": { - "properties": { - "foo\nbar": { - "type": "number" - }, - "foo\"bar": { - "type": "number" - }, - "foo\\bar": { - "type": "number" - }, - "foo\rbar": { - "type": "number" - }, - "foo\tbar": { - "type": "number" - }, - "foo\fbar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with all numbers is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1", - "foo\\bar": "1", - "foo\rbar": "1", - "foo\tbar": "1", - "foo\fbar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/propertyNames.json b/src/test/resources/draft6/propertyNames.json deleted file mode 100644 index 54cd1f2ab..000000000 --- a/src/test/resources/draft6/propertyNames.json +++ /dev/null @@ -1,122 +0,0 @@ -[ - { - "description": "propertyNames validation", - "schema": { - "propertyNames": { - "maxLength": 3 - } - }, - "tests": [ - { - "description": "all property names valid", - "data": { - "f": {}, - "foo": {} - }, - "valid": true - }, - { - "description": "some property names invalid", - "data": { - "foo": {}, - "foobar": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "propertyNames validation with pattern", - "schema": { - "propertyNames": { "pattern": "^a+$" } - }, - "tests": [ - { - "description": "matching property names are valid", - "data": { - "a": {}, - "aa": {}, - "aaa": {} - }, - "valid": true - }, - { - "description": "non-matching property name is invalid", - "data": { - "aaA": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema true", - "schema": { - "propertyNames": true - }, - "tests": [ - { - "description": "object with any properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema false", - "schema": { - "propertyNames": false - }, - "tests": [ - { - "description": "object with any properties is invalid", - "data": { - "foo": 1 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft6/ref.json b/src/test/resources/draft6/ref.json deleted file mode 100644 index adad08f18..000000000 --- a/src/test/resources/draft6/ref.json +++ /dev/null @@ -1,566 +0,0 @@ -[ - { - "description": "root pointer ref", - "schema": { - "properties": { - "foo": { - "$ref": "#" - } - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "match", - "data": { - "foo": false - }, - "valid": true - }, - { - "description": "recursive match", - "data": { - "foo": { - "foo": false - } - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": false - }, - "valid": false - }, - { - "description": "recursive mismatch", - "data": { - "foo": { - "bar": false - } - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to object", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "$ref": "#/properties/foo" - } - } - }, - "tests": [ - { - "description": "match", - "data": { - "bar": 3 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": true - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to array", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "$ref": "#/items/0" - } - ] - }, - "tests": [ - { - "description": "match array", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "mismatch array", - "data": [ - 1, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "escaped pointer ref", - "schema": { - "tilda~field": { - "type": "integer" - }, - "slash/field": { - "type": "integer" - }, - "percent%field": { - "type": "integer" - }, - "properties": { - "tilda": { - "$ref": "#/tilda~0field" - }, - "slash": { - "$ref": "#/slash~1field" - }, - "percent": { - "$ref": "#/percent%25field" - } - } - }, - "tests": [ - { - "description": "slash invalid", - "data": { - "slash": "aoeu" - }, - "valid": false - }, - { - "description": "tilda invalid", - "data": { - "tilda": "aoeu" - }, - "valid": false - }, - { - "description": "percent invalid", - "data": { - "percent": "aoeu" - }, - "valid": false - }, - { - "description": "slash valid", - "data": { - "slash": 123 - }, - "valid": true - }, - { - "description": "tilda valid", - "data": { - "tilda": 123 - }, - "valid": true - }, - { - "description": "percent valid", - "data": { - "percent": 123 - }, - "valid": true - } - ] - }, - { - "description": "nested refs", - "schema": { - "definitions": { - "a": { - "type": "integer" - }, - "b": { - "$ref": "#/definitions/a" - }, - "c": { - "$ref": "#/definitions/b" - } - }, - "$ref": "#/definitions/c" - }, - "tests": [ - { - "description": "nested ref valid", - "data": 5, - "valid": true - }, - { - "description": "nested ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref overrides any sibling keywords", - "schema": { - "definitions": { - "reffed": { - "type": "array" - } - }, - "properties": { - "foo": { - "$ref": "#/definitions/reffed", - "maxItems": 2 - } - } - }, - "tests": [ - { - "description": "ref valid", - "data": { - "foo": [] - }, - "valid": true - }, - { - "description": "ref valid, maxItems ignored", - "data": { - "foo": [ - 1, - 2, - 3 - ] - }, - "valid": true - }, - { - "description": "ref invalid", - "data": { - "foo": "string" - }, - "valid": false - } - ] - }, - { - "description": "remote ref, containing refs itself", - "schema": { - "$ref": "http://json-schema.org/draft-06/schema#" - }, - "tests": [ - { - "description": "remote ref valid", - "data": { - "minLength": 1 - }, - "valid": true - }, - { - "description": "remote ref invalid", - "data": { - "minLength": -1 - }, - "valid": false - } - ] - }, - { - "description": "property named $ref that is not a reference", - "schema": { - "properties": { - "$ref": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "property named $ref valid", - "data": { - "$ref": "a" - }, - "valid": true - }, - { - "description": "property named $ref invalid", - "data": { - "$ref": 2 - }, - "valid": false - } - ] - }, - { - "description": "$ref to boolean schema true", - "schema": { - "$ref": "#/definitions/bool", - "definitions": { - "bool": true - } - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "$ref to boolean schema false", - "schema": { - "$ref": "#/definitions/bool", - "definitions": { - "bool": false - } - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "Recursive references between schemas", - "schema": { - "$id": "http://localhost:1234/tree", - "description": "tree of nodes", - "type": "object", - "properties": { - "meta": { - "type": "string" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "node" - } - } - }, - "required": [ - "meta", - "nodes" - ], - "definitions": { - "node": { - "$id": "http://localhost:1234/node", - "description": "node", - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "subtree": { - "$ref": "tree" - } - }, - "required": [ - "value" - ] - } - } - }, - "tests": [ - { - "description": "valid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 1.1 - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": true - }, - { - "description": "invalid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": "string is invalid" - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": false - } - ] - }, - { - "description": "refs with quote", - "schema": { - "properties": { - "foo\"bar": { - "$ref": "#/definitions/foo%22bar" - } - }, - "definitions": { - "foo\"bar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with numbers is valid", - "data": { - "foo\"bar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\"bar": "1" - }, - "valid": false - } - ] - }, - { - "description": "Location-independent identifier", - "schema": { - "allOf": [ - { - "$ref": "#foo" - } - ], - "definitions": { - "A": { - "$id": "#foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with absolute URI", - "schema": { - "allOf": [ - { - "$ref": "http://localhost:1234/bar#foo" - } - ], - "definitions": { - "A": { - "$id": "http://localhost:1234/bar#foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with base URI change in subschema", - "schema": { - "$id": "http://localhost:1234/root", - "allOf": [ - { - "$ref": "http://localhost:1234/nested.json#foo" - } - ], - "definitions": { - "A": { - "$id": "nested.json", - "definitions": { - "B": { - "$id": "#foo", - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/refRemote.json b/src/test/resources/draft6/refRemote.json deleted file mode 100644 index 19dd7c82d..000000000 --- a/src/test/resources/draft6/refRemote.json +++ /dev/null @@ -1,166 +0,0 @@ -[ - { - "description": "remote ref", - "schema": { - "$ref": "http://localhost:1234/integer.json" - }, - "tests": [ - { - "description": "remote ref valid", - "data": 1, - "valid": true - }, - { - "description": "remote ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "fragment within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/integer" - }, - "tests": [ - { - "description": "remote fragment valid", - "data": 1, - "valid": true - }, - { - "description": "remote fragment invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" - }, - "tests": [ - { - "description": "ref within ref valid", - "data": 1, - "valid": true - }, - { - "description": "ref within ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "base URI change", - "schema": { - "$id": "http://localhost:1234/", - "items": { - "$id": "folder/", - "items": { - "$ref": "folderInteger.json" - } - } - }, - "tests": [ - { - "description": "base URI change ref valid", - "data": [ - [ - 1 - ] - ], - "valid": true - }, - { - "description": "base URI change ref invalid", - "data": [ - [ - "a" - ] - ], - "valid": false - } - ] - }, - { - "description": "base URI change - change folder", - "schema": { - "$id": "http://localhost:1234/scope_change_defs1.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/definitions/baz" - } - }, - "definitions": { - "baz": { - "$id": "folder/", - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - }, - { - "description": "root ref in remote ref", - "schema": { - "$id": "http://localhost:1234/object", - "type": "object", - "properties": { - "name": { - "$ref": "name.json#/definitions/orNull" - } - } - }, - "tests": [ - { - "description": "string is valid", - "data": { - "name": "foo" - }, - "valid": true - }, - { - "description": "null is valid", - "data": { - "name": null - }, - "valid": true - }, - { - "description": "object is invalid", - "data": { - "name": { - "name": null - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/refRemote_ignored.json b/src/test/resources/draft6/refRemote_ignored.json deleted file mode 100644 index 4bd19f7f5..000000000 --- a/src/test/resources/draft6/refRemote_ignored.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "description": "base URI change - change folder in subschema", - "schema": { - "$id": "http://localhost:1234/scope_change_defs2.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/definitions/baz/definitions/bar" - } - }, - "definitions": { - "baz": { - "$id": "folder/", - "definitions": { - "bar": { - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/src/test/resources/draft6/required.json b/src/test/resources/draft6/required.json deleted file mode 100644 index 6358d4e1f..000000000 --- a/src/test/resources/draft6/required.json +++ /dev/null @@ -1,111 +0,0 @@ -[ - { - "description": "required validation", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "required": [ - "foo" - ] - }, - "tests": [ - { - "description": "present required property is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "non-present required property is invalid", - "data": { - "bar": 1 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "required default validation", - "schema": { - "properties": { - "foo": {} - } - }, - "tests": [ - { - "description": "not required by default", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with empty array", - "schema": { - "properties": { - "foo": {} - }, - "required": [] - }, - "tests": [ - { - "description": "property not required", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with escaped characters", - "schema": { - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ] - }, - "tests": [ - { - "description": "object with all properties present is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with some properties missing is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/type.json b/src/test/resources/draft6/type.json deleted file mode 100644 index 83348a468..000000000 --- a/src/test/resources/draft6/type.json +++ /dev/null @@ -1,504 +0,0 @@ -[ - { - "description": "integer type matches integers", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "an integer is an integer", - "data": 1, - "valid": true - }, - { - "description": "a float is not an integer", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an integer", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not an integer, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not an integer", - "data": {}, - "valid": false - }, - { - "description": "an array is not an integer", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an integer", - "data": true, - "valid": false - }, - { - "description": "null is not an integer", - "data": null, - "valid": false - } - ] - }, - { - "description": "number type matches numbers", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "an integer is a number", - "data": 1, - "valid": true - }, - { - "description": "a float is a number", - "data": 1.1, - "valid": true - }, - { - "description": "a string is not a number", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not a number, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not a number", - "data": {}, - "valid": false - }, - { - "description": "an array is not a number", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a number", - "data": true, - "valid": false - }, - { - "description": "null is not a number", - "data": null, - "valid": false - } - ] - }, - { - "description": "string type matches strings", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "1 is not a string", - "data": 1, - "valid": false - }, - { - "description": "a float is not a string", - "data": 1.1, - "valid": false - }, - { - "description": "a string is a string", - "data": "foo", - "valid": true - }, - { - "description": "a string is still a string, even if it looks like a number", - "data": "1", - "valid": true - }, - { - "description": "an empty string is still a string", - "data": "", - "valid": true - }, - { - "description": "an object is not a string", - "data": {}, - "valid": false - }, - { - "description": "an array is not a string", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a string", - "data": true, - "valid": false - }, - { - "description": "null is not a string", - "data": null, - "valid": false - } - ] - }, - { - "description": "object type matches objects", - "schema": { - "type": "object" - }, - "tests": [ - { - "description": "an integer is not an object", - "data": 1, - "valid": false - }, - { - "description": "a float is not an object", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an object", - "data": "foo", - "valid": false - }, - { - "description": "an object is an object", - "data": {}, - "valid": true - }, - { - "description": "an array is not an object", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an object", - "data": true, - "valid": false - }, - { - "description": "null is not an object", - "data": null, - "valid": false - } - ] - }, - { - "description": "array type matches arrays", - "schema": { - "type": "array" - }, - "tests": [ - { - "description": "an integer is not an array", - "data": 1, - "valid": false - }, - { - "description": "a float is not an array", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an array", - "data": "foo", - "valid": false - }, - { - "description": "an object is not an array", - "data": {}, - "valid": false - }, - { - "description": "an array is an array", - "data": [], - "valid": true - }, - { - "description": "a boolean is not an array", - "data": true, - "valid": false - }, - { - "description": "null is not an array", - "data": null, - "valid": false - } - ] - }, - { - "description": "boolean type matches booleans", - "schema": { - "type": "boolean" - }, - "tests": [ - { - "description": "an integer is not a boolean", - "data": 1, - "valid": false - }, - { - "description": "zero is not a boolean", - "data": 0, - "valid": false - }, - { - "description": "a float is not a boolean", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not a boolean", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not a boolean", - "data": "", - "valid": false - }, - { - "description": "an object is not a boolean", - "data": {}, - "valid": false - }, - { - "description": "an array is not a boolean", - "data": [], - "valid": false - }, - { - "description": "true is a boolean", - "data": true, - "valid": true - }, - { - "description": "false is a boolean", - "data": false, - "valid": true - }, - { - "description": "null is not a boolean", - "data": null, - "valid": false - } - ] - }, - { - "description": "null type matches only the null object", - "schema": { - "type": "null" - }, - "tests": [ - { - "description": "an integer is not null", - "data": 1, - "valid": false - }, - { - "description": "a float is not null", - "data": 1.1, - "valid": false - }, - { - "description": "zero is not null", - "data": 0, - "valid": false - }, - { - "description": "a string is not null", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not null", - "data": "", - "valid": false - }, - { - "description": "an object is not null", - "data": {}, - "valid": false - }, - { - "description": "an array is not null", - "data": [], - "valid": false - }, - { - "description": "true is not null", - "data": true, - "valid": false - }, - { - "description": "false is not null", - "data": false, - "valid": false - }, - { - "description": "null is null", - "data": null, - "valid": true - } - ] - }, - { - "description": "multiple types can be specified in an array", - "schema": { - "type": [ - "integer", - "string" - ] - }, - "tests": [ - { - "description": "an integer is valid", - "data": 1, - "valid": true - }, - { - "description": "a string is valid", - "data": "foo", - "valid": true - }, - { - "description": "a float is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "an object is invalid", - "data": {}, - "valid": false - }, - { - "description": "an array is invalid", - "data": [], - "valid": false - }, - { - "description": "a boolean is invalid", - "data": true, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type as array with one item", - "schema": { - "type": [ - "string" - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "type: array or object", - "schema": { - "type": [ - "array", - "object" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type: array, object or null", - "schema": { - "type": [ - "array", - "object", - "null" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft6/uniqueItems.json b/src/test/resources/draft6/uniqueItems.json deleted file mode 100644 index c64cdbbda..000000000 --- a/src/test/resources/draft6/uniqueItems.json +++ /dev/null @@ -1,328 +0,0 @@ -[ - { - "description": "uniqueItems validation", - "schema": { - "uniqueItems": true - }, - "tests": [ - { - "description": "unique array of integers is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "non-unique array of integers is invalid", - "data": [ - 1, - 1 - ], - "valid": false - }, - { - "description": "numbers are unique if mathematically unequal", - "data": [ - 1.0, - 1.00, - 1 - ], - "valid": false - }, - { - "description": "false is not equal to zero", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "true is not equal to one", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "unique array of objects is valid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "baz" - } - ], - "valid": true - }, - { - "description": "non-unique array of objects is invalid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "bar" - } - ], - "valid": false - }, - { - "description": "unique array of nested objects is valid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": false - } - } - } - ], - "valid": true - }, - { - "description": "non-unique array of nested objects is invalid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": true - } - } - } - ], - "valid": false - }, - { - "description": "unique array of arrays is valid", - "data": [ - [ - "foo" - ], - [ - "bar" - ] - ], - "valid": true - }, - { - "description": "non-unique array of arrays is invalid", - "data": [ - [ - "foo" - ], - [ - "foo" - ] - ], - "valid": false - }, - { - "description": "1 and true are unique", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "0 and false are unique", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "unique heterogeneous types are valid", - "data": [ - {}, - [ - 1 - ], - true, - null, - 1 - ], - "valid": true - }, - { - "description": "non-unique heterogeneous types are invalid", - "data": [ - {}, - [ - 1 - ], - true, - null, - {}, - 1 - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "unique array extended from [false, true] is valid", - "data": [ - false, - true, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "unique array extended from [true, false] is valid", - "data": [ - true, - false, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "non-unique array extended from [false, true] is not valid", - "data": [ - false, - true, - "foo", - "foo" - ], - "valid": false - }, - { - "description": "non-unique array extended from [true, false] is not valid", - "data": [ - true, - false, - "foo", - "foo" - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items and additionalItems=false", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true, - "additionalItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "extra items are invalid even if unique", - "data": [ - false, - true, - null - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/additionalItems.json b/src/test/resources/draft7/additionalItems.json deleted file mode 100644 index 5fe4ddb1e..000000000 --- a/src/test/resources/draft7/additionalItems.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "description": "additionalItems as schema", - "schema": { - "items": [ - {} - ], - "additionalItems": { - "type": "integer" - } - }, - "tests": [ - { - "description": "additional items match schema", - "data": [ - null, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "additional items do not match schema", - "data": [ - null, - 2, - 3, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "items is schema, no additionalItems", - "schema": { - "items": {}, - "additionalItems": false - }, - "tests": [ - { - "description": "all items match schema", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - } - ] - }, - { - "description": "array of items with no additionalItems", - "schema": { - "items": [ - {}, - {}, - {} - ], - "additionalItems": false - }, - "tests": [ - { - "description": "fewer number of items present", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "equal number of items present", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "additional items are not permitted", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "additionalItems as false without items", - "schema": { - "additionalItems": false - }, - "tests": [ - { - "description": "items defaults to empty schema so everything is valid", - "data": [ - 1, - 2, - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - } - ] - }, - { - "description": "additionalItems are allowed by default", - "schema": { - "items": [ - { - "type": "integer" - } - ] - }, - "tests": [ - { - "description": "only the first item is validated", - "data": [ - 1, - "foo", - false - ], - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/additionalProperties.json b/src/test/resources/draft7/additionalProperties.json deleted file mode 100644 index 827bb1fba..000000000 --- a/src/test/resources/draft7/additionalProperties.json +++ /dev/null @@ -1,193 +0,0 @@ -[ - { - "description": "additionalProperties being false does not allow other properties", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "patternProperties": { - "^v": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": "boom" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobarbaz", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - }, - { - "description": "patternProperties are not additional properties", - "data": { - "foo": 1, - "vroom": 2 - }, - "valid": true - } - ] - }, - { - "description": "non-ASCII pattern with additionalProperties", - "schema": { - "patternProperties": { - "^á": {} - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "matching the pattern is valid", - "data": { - "ármányos": 2 - }, - "valid": true - }, - { - "description": "not matching the pattern is invalid", - "data": { - "élmény": 2 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties allows a schema which should validate", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "no additional properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "an additional valid property is valid", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1, - "bar": 2, - "quux": 12 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties can exist by itself", - "schema": { - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "an additional valid property is valid", - "data": { - "foo": true - }, - "valid": true - }, - { - "description": "an additional invalid property is invalid", - "data": { - "foo": 1 - }, - "valid": false - } - ] - }, - { - "description": "additionalProperties are allowed by default", - "schema": { - "properties": { - "foo": {}, - "bar": {} - } - }, - "tests": [ - { - "description": "additional properties are allowed", - "data": { - "foo": 1, - "bar": 2, - "quux": true - }, - "valid": true - } - ] - }, - { - "description": "additionalProperties should not look in applicators", - "schema": { - "allOf": [ - { - "properties": { - "foo": {} - } - } - ], - "additionalProperties": { - "type": "boolean" - } - }, - "tests": [ - { - "description": "properties defined in allOf are not allowed", - "data": { - "foo": 1, - "bar": true - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/allOf.json b/src/test/resources/draft7/allOf.json deleted file mode 100644 index 703d4c050..000000000 --- a/src/test/resources/draft7/allOf.json +++ /dev/null @@ -1,288 +0,0 @@ -[ - { - "description": "allOf", - "schema": { - "allOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "allOf", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "mismatch second", - "data": { - "foo": "baz" - }, - "valid": false - }, - { - "description": "mismatch first", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type", - "data": { - "foo": "baz", - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "allOf with base schema", - "schema": { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ], - "allOf": [ - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - }, - { - "properties": { - "baz": { - "type": "null" - } - }, - "required": [ - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": "quux", - "bar": 2, - "baz": null - }, - "valid": true - }, - { - "description": "mismatch base schema", - "data": { - "foo": "quux", - "baz": null - }, - "valid": false - }, - { - "description": "mismatch first allOf", - "data": { - "bar": 2, - "baz": null - }, - "valid": false - }, - { - "description": "mismatch second allOf", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "mismatch both", - "data": { - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "allOf simple types", - "schema": { - "allOf": [ - { - "maximum": 30 - }, - { - "minimum": 20 - } - ] - }, - "tests": [ - { - "description": "valid", - "data": 25, - "valid": true - }, - { - "description": "mismatch one", - "data": 35, - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all true", - "schema": { - "allOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "allOf with boolean schemas, some false", - "schema": { - "allOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with boolean schemas, all false", - "schema": { - "allOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with one empty schema", - "schema": { - "allOf": [ - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with two empty schemas", - "schema": { - "allOf": [ - {}, - {} - ] - }, - "tests": [ - { - "description": "any data is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "allOf with the first empty schema", - "schema": { - "allOf": [ - {}, - { - "type": "number" - } - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "allOf with the last empty schema", - "schema": { - "allOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/anyOf.json b/src/test/resources/draft7/anyOf.json deleted file mode 100644 index f35e2194b..000000000 --- a/src/test/resources/draft7/anyOf.json +++ /dev/null @@ -1,224 +0,0 @@ -[ - { - "description": "anyOf", - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first anyOf valid", - "data": 1, - "valid": true - }, - { - "description": "second anyOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both anyOf valid", - "data": 3, - "valid": true - }, - { - "description": "neither anyOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "anyOf with base schema", - "schema": { - "type": "string", - "anyOf": [ - { - "maxLength": 2 - }, - { - "minLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one anyOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both anyOf invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf with boolean schemas, all true", - "schema": { - "anyOf": [ - true, - true - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, some true", - "schema": { - "anyOf": [ - true, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "anyOf with boolean schemas, all false", - "schema": { - "anyOf": [ - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "anyOf complex types", - "schema": { - "anyOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first anyOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second anyOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both anyOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": true - }, - { - "description": "neither anyOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "anyOf with one empty schema", - "schema": { - "anyOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is valid", - "data": 123, - "valid": true - } - ] - }, - { - "description": "nested anyOf, to check validation semantics", - "schema": { - "anyOf": [ - { - "anyOf": [ - { - "type": "null" - } - ] - } - ] - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "anything non-null is invalid", - "data": 123, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/boolean_schema.json b/src/test/resources/draft7/boolean_schema.json deleted file mode 100644 index ee241ef92..000000000 --- a/src/test/resources/draft7/boolean_schema.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "description": "boolean schema 'true'", - "schema": true, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "boolean true is valid", - "data": true, - "valid": true - }, - { - "description": "boolean false is valid", - "data": false, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - }, - { - "description": "array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "boolean schema 'false'", - "schema": false, - "tests": [ - { - "description": "number is invalid", - "data": 1, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "boolean true is invalid", - "data": true, - "valid": false - }, - { - "description": "boolean false is invalid", - "data": false, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - }, - { - "description": "object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "empty object is invalid", - "data": {}, - "valid": false - }, - { - "description": "array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/const.json b/src/test/resources/draft7/const.json deleted file mode 100644 index 69b5012f2..000000000 --- a/src/test/resources/draft7/const.json +++ /dev/null @@ -1,214 +0,0 @@ -[ - { - "description": "const validation", - "schema": { - "const": 2 - }, - "tests": [ - { - "description": "same value is valid", - "data": 2, - "valid": true - }, - { - "description": "another value is invalid", - "data": 5, - "valid": false - }, - { - "description": "another type is invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "const with object", - "schema": { - "const": { - "foo": "bar", - "baz": "bax" - } - }, - "tests": [ - { - "description": "same object is valid", - "data": { - "foo": "bar", - "baz": "bax" - }, - "valid": true - }, - { - "description": "same object with different property order is valid", - "data": { - "baz": "bax", - "foo": "bar" - }, - "valid": true - }, - { - "description": "another object is invalid", - "data": { - "foo": "bar" - }, - "valid": false - }, - { - "description": "another type is invalid", - "data": [ - 1, - 2 - ], - "valid": false - } - ] - }, - { - "description": "const with array", - "schema": { - "const": [ - { - "foo": "bar" - } - ] - }, - "tests": [ - { - "description": "same array is valid", - "data": [ - { - "foo": "bar" - } - ], - "valid": true - }, - { - "description": "another array item is invalid", - "data": [ - 2 - ], - "valid": false - }, - { - "description": "array with additional items is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - } - ] - }, - { - "description": "const with null", - "schema": { - "const": null - }, - "tests": [ - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "not null is invalid", - "data": 0, - "valid": false - } - ] - }, - { - "description": "const with false does not match 0", - "schema": { - "const": false - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "const with true does not match 1", - "schema": { - "const": true - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "const with 0 does not match false", - "schema": { - "const": 0 - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "const with 1 does not match true", - "schema": { - "const": 1 - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/contains.json b/src/test/resources/draft7/contains.json deleted file mode 100644 index 62b081f3f..000000000 --- a/src/test/resources/draft7/contains.json +++ /dev/null @@ -1,143 +0,0 @@ -[ - { - "description": "contains keyword validation", - "schema": { - "contains": { - "minimum": 5 - } - }, - "tests": [ - { - "description": "array with item matching schema (5) is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with item matching schema (6) is valid", - "data": [ - 3, - 4, - 6 - ], - "valid": true - }, - { - "description": "array with two items matching schema (5, 6) is valid", - "data": [ - 3, - 4, - 5, - 6 - ], - "valid": true - }, - { - "description": "array without items matching schema is invalid", - "data": [ - 2, - 3, - 4 - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - }, - { - "description": "not array is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "contains keyword with const keyword", - "schema": { - "contains": { - "const": 5 - } - }, - "tests": [ - { - "description": "array with item 5 is valid", - "data": [ - 3, - 4, - 5 - ], - "valid": true - }, - { - "description": "array with two items 5 is valid", - "data": [ - 3, - 4, - 5, - 5 - ], - "valid": true - }, - { - "description": "array without item 5 is invalid", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema true", - "schema": { - "contains": true - }, - "tests": [ - { - "description": "any non-empty array is valid", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - } - ] - }, - { - "description": "contains keyword with boolean schema false", - "schema": { - "contains": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - "foo" - ], - "valid": false - }, - { - "description": "empty array is invalid", - "data": [], - "valid": false - }, - { - "description": "non-arrays are valid", - "data": "contains does not apply to strings", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/default.json b/src/test/resources/draft7/default.json deleted file mode 100644 index 983713399..000000000 --- a/src/test/resources/draft7/default.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "description": "invalid type for default", - "schema": { - "properties": { - "foo": { - "type": "integer", - "default": [] - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "foo": 13 - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - }, - { - "description": "invalid string value for default", - "schema": { - "properties": { - "bar": { - "type": "string", - "minLength": 4, - "default": "bad" - } - } - }, - "tests": [ - { - "description": "valid when property is specified", - "data": { - "bar": "good" - }, - "valid": true - }, - { - "description": "still valid when the invalid default is used", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/definitions.json b/src/test/resources/draft7/definitions.json deleted file mode 100644 index 87812c576..000000000 --- a/src/test/resources/draft7/definitions.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "description": "valid definition", - "schema": { - "$ref": "http://json-schema.org/draft-07/schema#" - }, - "tests": [ - { - "description": "valid definition schema", - "data": { - "definitions": { - "foo": { - "type": "integer" - } - } - }, - "valid": true - } - ] - }, - { - "description": "invalid definition", - "schema": { - "$ref": "http://json-schema.org/draft-07/schema#" - }, - "tests": [ - { - "description": "invalid definition schema", - "data": { - "definitions": { - "foo": { - "type": 1 - } - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/dependencies.json b/src/test/resources/draft7/dependencies.json deleted file mode 100644 index 3a750ffac..000000000 --- a/src/test/resources/draft7/dependencies.json +++ /dev/null @@ -1,340 +0,0 @@ -[ - { - "description": "dependencies", - "schema": { - "dependencies": { - "bar": [ - "foo" - ] - } - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependant", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "with dependency", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "bar" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "dependencies with empty array", - "schema": { - "dependencies": { - "bar": [] - } - }, - "tests": [ - { - "description": "empty object", - "data": {}, - "valid": true - }, - { - "description": "object with one property", - "data": { - "bar": 2 - }, - "valid": true - } - ] - }, - { - "description": "multiple dependencies", - "schema": { - "dependencies": { - "quux": [ - "foo", - "bar" - ] - } - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependants", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "with dependencies", - "data": { - "foo": 1, - "bar": 2, - "quux": 3 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "foo": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing other dependency", - "data": { - "bar": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing both dependencies", - "data": { - "quux": 1 - }, - "valid": false - } - ] - }, - { - "description": "multiple dependencies subschema", - "schema": { - "dependencies": { - "bar": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "no dependency", - "data": { - "foo": "quux" - }, - "valid": true - }, - { - "description": "wrong type", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type other", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - }, - { - "description": "wrong type both", - "data": { - "foo": "quux", - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "dependencies with boolean subschemas", - "schema": { - "dependencies": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "object with property having schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property having schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "empty array of dependencies", - "schema": { - "dependencies": { - "foo": [] - } - }, - "tests": [ - { - "description": "object with property is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - }, - { - "description": "non-object is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "dependencies": { - "foo\nbar": [ - "foo\rbar" - ], - "foo\tbar": { - "minProperties": 4 - }, - "foo'bar": { - "required": [ - "foo\"bar" - ] - }, - "foo\"bar": [ - "foo'bar" - ] - } - }, - "tests": [ - { - "description": "valid object 1", - "data": { - "foo\nbar": 1, - "foo\rbar": 2 - }, - "valid": true - }, - { - "description": "valid object 2", - "data": { - "foo\tbar": 1, - "a": 2, - "b": 3, - "c": 4 - }, - "valid": true - }, - { - "description": "valid object 3", - "data": { - "foo'bar": 1, - "foo\"bar": 2 - }, - "valid": true - }, - { - "description": "invalid object 1", - "data": { - "foo\nbar": 1, - "foo": 2 - }, - "valid": false - }, - { - "description": "invalid object 2", - "data": { - "foo\tbar": 1, - "a": 2 - }, - "valid": false - }, - { - "description": "invalid object 3", - "data": { - "foo'bar": 1 - }, - "valid": false - }, - { - "description": "invalid object 4", - "data": { - "foo\"bar": 2 - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/enum.json b/src/test/resources/draft7/enum.json deleted file mode 100644 index d0b8aacdf..000000000 --- a/src/test/resources/draft7/enum.json +++ /dev/null @@ -1,233 +0,0 @@ -[ - { - "description": "simple enum validation", - "schema": { - "enum": [ - 1, - 2, - 3 - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": 1, - "valid": true - }, - { - "description": "something else is invalid", - "data": 4, - "valid": false - } - ] - }, - { - "description": "heterogeneous enum validation", - "schema": { - "enum": [ - 6, - "foo", - [], - true, - { - "foo": 12 - } - ] - }, - "tests": [ - { - "description": "one of the enum is valid", - "data": [], - "valid": true - }, - { - "description": "something else is invalid", - "data": null, - "valid": false - }, - { - "description": "objects are deep compared", - "data": { - "foo": false - }, - "valid": false - } - ] - }, - { - "description": "enums in properties", - "schema": { - "type": "object", - "properties": { - "foo": { - "enum": [ - "foo" - ] - }, - "bar": { - "enum": [ - "bar" - ] - } - }, - "required": [ - "bar" - ] - }, - "tests": [ - { - "description": "both properties are valid", - "data": { - "foo": "foo", - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing optional property is valid", - "data": { - "bar": "bar" - }, - "valid": true - }, - { - "description": "missing required property is invalid", - "data": { - "foo": "foo" - }, - "valid": false - }, - { - "description": "missing all properties is invalid", - "data": {}, - "valid": false - } - ] - }, - { - "description": "enum with escaped characters", - "schema": { - "enum": [ - "foo\nbar", - "foo\rbar" - ] - }, - "tests": [ - { - "description": "member 1 is valid", - "data": "foo\nbar", - "valid": true - }, - { - "description": "member 2 is valid", - "data": "foo\rbar", - "valid": true - }, - { - "description": "another string is invalid", - "data": "abc", - "valid": false - } - ] - }, - { - "description": "enum with false does not match 0", - "schema": { - "enum": [ - false - ] - }, - "tests": [ - { - "description": "false is valid", - "data": false, - "valid": true - }, - { - "description": "integer zero is invalid", - "data": 0, - "valid": false - }, - { - "description": "float zero is invalid", - "data": 0.0, - "valid": false - } - ] - }, - { - "description": "enum with true does not match 1", - "schema": { - "enum": [ - true - ] - }, - "tests": [ - { - "description": "true is valid", - "data": true, - "valid": true - }, - { - "description": "integer one is invalid", - "data": 1, - "valid": false - }, - { - "description": "float one is invalid", - "data": 1.0, - "valid": false - } - ] - }, - { - "description": "enum with 0 does not match false", - "schema": { - "enum": [ - 0 - ] - }, - "tests": [ - { - "description": "false is invalid", - "data": false, - "valid": false - }, - { - "description": "integer zero is valid", - "data": 0, - "valid": true - }, - { - "description": "float zero is valid", - "data": 0.0, - "valid": true - } - ] - }, - { - "description": "enum with 1 does not match true", - "schema": { - "enum": [ - 1 - ] - }, - "tests": [ - { - "description": "true is invalid", - "data": true, - "valid": false - }, - { - "description": "integer one is valid", - "data": 1, - "valid": true - }, - { - "description": "float one is valid", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/exclusiveMaximum.json b/src/test/resources/draft7/exclusiveMaximum.json deleted file mode 100644 index fc8c4bb47..000000000 --- a/src/test/resources/draft7/exclusiveMaximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMaximum validation", - "schema": { - "exclusiveMaximum": 3.0 - }, - "tests": [ - { - "description": "below the exclusiveMaximum is valid", - "data": 2.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 3.0, - "valid": false - }, - { - "description": "above the exclusiveMaximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/exclusiveMinimum.json b/src/test/resources/draft7/exclusiveMinimum.json deleted file mode 100644 index 815d8fbe1..000000000 --- a/src/test/resources/draft7/exclusiveMinimum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "exclusiveMinimum validation", - "schema": { - "exclusiveMinimum": 1.1 - }, - "tests": [ - { - "description": "above the exclusiveMinimum is valid", - "data": 1.2, - "valid": true - }, - { - "description": "boundary point is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "below the exclusiveMinimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/format.json b/src/test/resources/draft7/format.json deleted file mode 100644 index 6e6cd01a7..000000000 --- a/src/test/resources/draft7/format.json +++ /dev/null @@ -1,648 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IDN e-mail addresses", - "schema": { - "format": "idn-email" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of regexes", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IDN hostnames", - "schema": { - "format": "idn-hostname" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of hostnames", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of date strings", - "schema": { - "format": "date" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of time strings", - "schema": { - "format": "time" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of JSON pointers", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of relative JSON pointers", - "schema": { - "format": "relative-json-pointer" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IRIs", - "schema": { - "format": "iri" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of IRI references", - "schema": { - "format": "iri-reference" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI references", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - }, - { - "description": "validation of URI templates", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "ignores integers", - "data": 12, - "valid": true - }, - { - "description": "ignores floats", - "data": 13.7, - "valid": true - }, - { - "description": "ignores objects", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores booleans", - "data": false, - "valid": true - }, - { - "description": "ignores null", - "data": null, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/if-then-else.json b/src/test/resources/draft7/if-then-else.json deleted file mode 100644 index 7b014aeac..000000000 --- a/src/test/resources/draft7/if-then-else.json +++ /dev/null @@ -1,366 +0,0 @@ -[ - { - "description": "ignore if without then or else", - "schema": { - "if": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone if", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone if", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "ignore then without if", - "schema": { - "then": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone then", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone then", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "ignore else without if", - "schema": { - "else": { - "const": 0 - } - }, - "tests": [ - { - "description": "valid when valid against lone else", - "data": 0, - "valid": true - }, - { - "description": "valid when invalid against lone else", - "data": "hello", - "valid": true - } - ] - }, - { - "description": "if and then without else", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - } - }, - "tests": [ - { - "description": "valid through then", - "data": -1, - "valid": true - }, - { - "description": "invalid through then", - "data": -100, - "valid": false - }, - { - "description": "valid when if test fails", - "data": 3, - "valid": true - } - ] - }, - { - "description": "if and else without then", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "else": { - "multipleOf": 2 - } - }, - "tests": [ - { - "description": "valid when if test passes", - "data": -1, - "valid": true - }, - { - "description": "valid through else", - "data": 4, - "valid": true - }, - { - "description": "invalid through else", - "data": 3, - "valid": false - } - ] - }, - { - "description": "validate against correct branch, then vs else", - "schema": { - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - }, - "else": { - "multipleOf": 2 - } - }, - "tests": [ - { - "description": "valid through then", - "data": -1, - "valid": true - }, - { - "description": "invalid through then", - "data": -100, - "valid": false - }, - { - "description": "valid through else", - "data": 4, - "valid": true - }, - { - "description": "invalid through else", - "data": 3, - "valid": false - } - ] - }, - { - "description": "non-interference across combined schemas", - "schema": { - "allOf": [ - { - "if": { - "exclusiveMaximum": 0 - } - }, - { - "then": { - "minimum": -10 - } - }, - { - "else": { - "multipleOf": 2 - } - } - ] - }, - "tests": [ - { - "description": "valid, but would have been invalid through then", - "data": -100, - "valid": true - }, - { - "description": "valid, but would have been invalid through else", - "data": 3, - "valid": true - } - ] - }, - { - "description": "conditions by properties", - "schema": { - "type": "object", - "properties": { - "street_address": { - "type": "string" - }, - "country": { - "enum": ["United States of America", "Canada"] - } - }, - "if": { - "properties": { - "country": { - "const": "United States of America" - } - } - }, - "then": { - "properties": { - "postal_code": { - "pattern": "[0-9]{5}(-[0-9]{4})?" - } - } - }, - "else": { - "properties": { - "postal_code": { - "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" - } - } - } - }, - "tests": [ - { - "description": "valid through then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "country": "United States of America", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid through then, alternative match", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid through else", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "K1M 1M4" - }, - "valid": true - }, - { - "description": "invalid through else", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "10000" - }, - "valid": false - } - , - { - "description": "invalid through then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "K1M 1M4" - }, - "valid": false - } - ] - }, - { - "description": "conditions by allOf properties", - "schema": { - "type": "object", - "properties": { - "street_address": { - "type": "string" - }, - "country": { - "default": "United States of America", - "enum": ["United States of America", "Canada", "Netherlands"] - } - }, - "allOf": [ - { - "if": { - "properties": { "country": { "const": "United States of America" } } - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Canada" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Netherlands" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } - } - } - ] - }, - "tests": [ - { - "description": "valid first if", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "country": "United States of America", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid first if, alternative match", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "20500" - }, - "valid": true - }, - { - "description": "valid second if", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "K1M 1M4" - }, - "valid": true - }, - { - "description": "valid third if", - "data": { - "street_address": "Adriaan Goekooplaan", - "country": "Netherlands", - "postal_code": "2517 JX" - }, - "valid": true - }, - { - "description": "invalid through second then", - "data": { - "street_address": "24 Sussex Drive", - "country": "Canada", - "postal_code": "10000" - }, - "valid": false - }, - { - "description": "invalid through first then", - "data": { - "street_address": "1600 Pennsylvania Avenue NW", - "postal_code": "K1M 1M4" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/issue386.json b/src/test/resources/draft7/issue386.json new file mode 100644 index 000000000..c1281adb1 --- /dev/null +++ b/src/test/resources/draft7/issue386.json @@ -0,0 +1,134 @@ +[ + { + "description": "issue386", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue386.json", + "type": "object", + "properties": { + "street_address": { + "type": "string" + }, + "country": { + "default": "United States of America", + "enum": [ + "United States of America", + "Canada", + "Netherlands" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "country": { + "const": "United States of America" + } + } + }, + "then": { + "properties": { + "postal_code": { + "pattern": "[0-9]{5}(-[0-9]{4})?" + } + } + } + }, + { + "if": { + "properties": { + "country": { + "const": "Canada" + } + }, + "required": [ + "country" + ] + }, + "then": { + "properties": { + "postal_code": { + "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + } + } + } + }, + { + "if": { + "properties": { + "country": { + "const": "Netherlands" + } + }, + "required": [ + "country" + ] + }, + "then": { + "properties": { + "postal_code": { + "pattern": "[0-9]{4} [A-Z]{2}" + } + } + } + } + ] + }, + "tests": [ + { + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "country": "United States of America", + "postal_code": "20500" + }, + "valid": true + }, + { + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + }, + "valid": true + }, + { + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "K1M 1M4" + }, + "valid": true + }, + { + "data": { + "street_address": "Adriaan Goekooplaan", + "country": "Netherlands", + "postal_code": "2517 JX" + }, + "valid": true + }, + { + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "10000" + }, + "valid": false, + "expectedErrors": [ + "$.postal_code: does not match the regex pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + ] + }, + { + "description": "invalid through first then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "valid": false, + "expectedErrors": [ + "$.postal_code: does not match the regex pattern [0-9]{5}(-[0-9]{4})?" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue470.json b/src/test/resources/draft7/issue470.json new file mode 100644 index 000000000..7956a85f3 --- /dev/null +++ b/src/test/resources/draft7/issue470.json @@ -0,0 +1,142 @@ +[ + { + "description": "Issue470Test", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue-470.json", + "title": "OneOf validation message", + "description": "Test description", + "type": "object", + "properties": { + "search": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "byName": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "byAge": { + "type": "object", + "properties": { + "age": { + "type": "integer", + "maximum": 150, + "minimum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "Test valid oneOf option 1", + "data": { + "search": { + "byName": { + "name": "John" + } + } + }, + "valid": true + }, + { + "description": "Test valid oneOf option 2", + "data": { + "search": { + "byAge": { + "age": 35 + } + } + }, + "valid": true + }, + { + "description": "Test invalid oneOf option 1 - wrong type", + "data": { + "search": { + "byName": { + "name": 123 + } + } + }, + "valid": false, + "errors": [ + "/search: must be valid to one and only one schema, but 0 are valid", + "/search: property 'byName' is not defined in the schema and the schema does not allow additional properties", + "/search/byName/name: integer found, string expected" + ] + }, + { + "description": "Test invalid oneOf option 1 - invalid value", + "data": { + "search": { + "byName": { + "name": "Too loooooooooong name" + } + } + }, + "valid": false, + "errors": [ + "/search: must be valid to one and only one schema, but 0 are valid", + "/search: property 'byName' is not defined in the schema and the schema does not allow additional properties", + "/search/byName/name: must be at most 20 characters long" + ] + }, + { + "description": "Test invalid oneOf option 2 - wrong type", + "data": { + "search": { + "byAge": { + "age": "20" + } + } + }, + "valid": false, + "errors": [ + "/search: must be valid to one and only one schema, but 0 are valid", + "/search/byAge/age: string found, integer expected", + "/search: property 'byAge' is not defined in the schema and the schema does not allow additional properties" + ] + }, + { + "description": "Test invalid oneOf option 2 - invalid value", + "data": { + "search": { + "byAge": { + "age": 200 + } + } + }, + "valid": false, + "errors": [ + "/search: must be valid to one and only one schema, but 0 are valid", + "/search/byAge/age: must have a maximum value of 150", + "/search: property 'byAge' is not defined in the schema and the schema does not allow additional properties" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue491.json b/src/test/resources/draft7/issue491.json new file mode 100644 index 000000000..f52f0334d --- /dev/null +++ b/src/test/resources/draft7/issue491.json @@ -0,0 +1,328 @@ +[ + { + "description": "issue491 Schema 1", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue-470.json", + "title": "OneOf validation message", + "description": "Test description", + "type": "object", + "properties": { + "search": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "searchAge": { + "type": "object", + "properties": { + "age": { + "type": "integer", + "maximum": 150, + "minimum": 1 + } + }, + "required": [ + "age" + ] + } + }, + "required": [ + "searchAge" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "Test valid oneOf option 1", + "data": { + "search": { + "searchAge": { + "age": 50 + } + } + }, + "valid": true + }, + { + "description": "Test valid oneOf option 2", + "data": { + "search": { + "name": "Steve" + } + }, + "valid": true + }, + { + "description": "Test invalid oneOf option 1 - wrong type", + "data": { + "search": { + "searchAge": { + "age": "Steve" + } + } + }, + "valid": false, + "errors": [ + "/search: required property 'name' not found", + "/search/searchAge/age: string found, integer expected", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + }, + { + "description": "Test invalid oneOf option 2 - wrong type", + "data": { + "search": { + "name": 123 + } + }, + "valid": false, + "errors": [ + "/search/name: integer found, string expected", + "/search: required property 'searchAge' not found", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + } + ] + }, + { + "description": "issue491 Schema 2", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue-470.json", + "title": "OneOf validation message", + "description": "Test description", + "type": "object", + "properties": { + "search": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "byAge": { + "type": "object", + "properties": { + "age": { + "type": "integer", + "maximum": 150, + "minimum": 1 + } + }, + "required": [ + "age" + ] + } + }, + "required": [ + "byAge" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "Test valid oneOf option 1", + "data": { + "search": { + "name": "Steve" + } + }, + "valid": true + }, + { + "description": "Test valid oneOf option 2", + "data": { + "search": { + "name": "Steve" + } + }, + "valid": true + }, + { + "description": "Test invalid oneOf option 1 - wrong type", + "data": { + "search": { + "byAge": { + "age": "Steve" + } + } + }, + "valid": false, + "errors": [ + "/search: required property 'name' not found", + "/search/byAge/age: string found, integer expected", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + }, + { + "description": "Test invalid oneOf option 2 - wrong type", + "data": { + "search": { + "name": 123 + } + }, + "valid": false, + "errors": [ + "/search/name: integer found, string expected", + "/search: required property 'byAge' not found", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + } + ] + }, + { + "description": "issue491 Schema 3", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue-470.json", + "title": "OneOf validation message", + "description": "Test description", + "type": "object", + "properties": { + "search": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "age": { + "type": "integer", + "maximum": 150, + "minimum": 1 + } + }, + "required": [ + "age" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "Test valid oneOf option 1", + "data": { + "search": { + "age": 50 + } + }, + "valid": true + }, + { + "description": "Test valid oneOf option 2", + "data": { + "search": { + "name": "Steve" + } + }, + "valid": true + }, + { + "description": "Test invalid oneOf option - wrong types or values 1", + "data": { + "search": { + "age": "Steve" + } + }, + "valid": false, + "errors": [ + "/search: required property 'name' not found", + "/search/age: string found, integer expected", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + }, + { + "description": "Test invalid oneOf option - wrong types or values 2", + "data": { + "search": { + "name": 123 + } + }, + "valid": false, + "errors": [ + "/search/name: integer found, string expected", + "/search: required property 'age' not found", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + }, + { + "description": "Test invalid oneOf option - wrong types or values 3", + "data": { + "search": { + "age": 200 + } + }, + "valid": false, + "errors": [ + "/search: required property 'name' not found", + "/search/age: must have a maximum value of 150", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + }, + { + "description": "Test invalid oneOf option - wrong types or values 4", + "data": { + "search": { + "name": "TooLoooooooooooooooooooooooooooooooooongName" + } + }, + "valid": false, + "errors": [ + "/search/name: must be at most 20 characters long", + "/search: required property 'age' not found", + "/search: must be valid to one and only one schema, but 0 are valid" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue516.json b/src/test/resources/draft7/issue516.json new file mode 100644 index 000000000..dd0a10c81 --- /dev/null +++ b/src/test/resources/draft7/issue516.json @@ -0,0 +1,144 @@ +[ + { + "description": "issue516", + "schema": { + "type": "object", + "properties": { + "locationName": { + "type": "string" + }, + "activities": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": [ + "activityType", + "weight", + "height" + ], + "additionalProperties": false, + "properties": { + "activityType": { + "enum": [ + "machine" + ] + }, + "weight": { + "type": "integer" + }, + "height": { + "type": "integer" + } + } + }, + { + "type": "object", + "required": [ + "activityType", + "chemicalCharacteristic" + ], + "additionalProperties": false, + "properties": { + "activityType": { + "enum": [ + "chemical" + ] + }, + "chemicalCharacteristic": { + "oneOf": [ + { + "type": "object", + "required": [ + "chemicalName" + ], + "additionalProperties": false, + "properties": { + "commonName": { + "type": "string" + }, + "chemicalName": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "chemicalName" + ], + "additionalProperties": false, + "properties": { + "categoryName": { + "type": "string" + }, + "chemicalName": { + "type": "string" + } + } + } + ] + } + } + } + ] + } + } + } + }, + "tests": [ + { + "description": "OneOfValidator is filtering out the required errors if all the oneOf schemas are having the issues", + "data": { + "locationName": "factoryLocation", + "activities": [ + { + "activityType": "machine", + "age": "(additionalProperty not allowed)", + "height": 10.5 + }, + { + "activityType": "chemical", + "toxic": "(additionalProperty not allowed)", + "chemicalCharacteristic": { + "commonName": "methane", + "chemicalName": "CH4" + } + }, + { + "activityType": "chemical", + "toxic": "(additionalProperty not allowed)", + "chemicalCharacteristic": { + "name": "methane", + "categoryName": "gasses", + "chemicalName": "CH4" + } + } + ] + }, + "valid": false, + "errors": [ + "/activities/0: must be valid to one and only one schema, but 0 are valid", + "/activities/0: property 'age' is not defined in the schema and the schema does not allow additional properties", + "/activities/0: required property 'chemicalCharacteristic' not found", + "/activities/0: property 'height' is not defined in the schema and the schema does not allow additional properties", + "/activities/0: required property 'weight' not found", + "/activities/1: must be valid to one and only one schema, but 0 are valid", + "/activities/1: property 'chemicalCharacteristic' is not defined in the schema and the schema does not allow additional properties", + "/activities/1: required property 'height' not found", + "/activities/1: property 'toxic' is not defined in the schema and the schema does not allow additional properties", + "/activities/1: required property 'weight' not found", + "/activities/2: must be valid to one and only one schema, but 0 are valid", + "/activities/2: property 'chemicalCharacteristic' is not defined in the schema and the schema does not allow additional properties", + "/activities/2/chemicalCharacteristic: must be valid to one and only one schema, but 0 are valid", + "/activities/2/chemicalCharacteristic: property 'categoryName' is not defined in the schema and the schema does not allow additional properties", + "/activities/2/chemicalCharacteristic: property 'name' is not defined in the schema and the schema does not allow additional properties", + "/activities/2: required property 'height' not found", + "/activities/2: property 'toxic' is not defined in the schema and the schema does not allow additional properties", + "/activities/2: required property 'weight' not found" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue590.json b/src/test/resources/draft7/issue590.json new file mode 100644 index 000000000..2cf9b57a4 --- /dev/null +++ b/src/test/resources/draft7/issue590.json @@ -0,0 +1,88 @@ +[ + { + "description": "issue590", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "attributes": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "format": "date-time" + }, + "disabled": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "handle": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "modifiedAt": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "serviceAccount": { + "type": "boolean" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "org": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "tests": [ + { + "description": "should parse", + "data": { "id": "an id" }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue650.json b/src/test/resources/draft7/issue650.json new file mode 100644 index 000000000..2eb430c2f --- /dev/null +++ b/src/test/resources/draft7/issue650.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$id": "#/properties/data", + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/test/resources/draft7/issue653.json b/src/test/resources/draft7/issue653.json new file mode 100644 index 000000000..9d14f2717 --- /dev/null +++ b/src/test/resources/draft7/issue653.json @@ -0,0 +1,90 @@ +[ + { + "description": "issue653", + "schema": { + "title": "PetArray", + "type": "object", + "properties": { + "pets": { + "type": "array", + "items": { + "oneOf": [ + { + "if": { + "properties": { + "pet_type": { + "const": "Cat" + } + } + }, + "then": { + "type": "object", + "properties": { + "hunts": { + "type": "boolean" + }, + "age": { + "type": "integer" + } + }, + "required": [ + "age" + ] + }, + "else": false + }, + { + "if": { + "properties": { + "pet_type": { + "const": "Dog" + } + } + }, + "then": { + "type": "object", + "properties": { + "bark": { + "type": "boolean" + }, + "breed": { + "type": "string" + } + }, + "required": [ + "bark" + ] + }, + "else": false + } + ] + } + } + }, + "additionalProperties": false, + "required": [ + "pets" + ] + }, + "tests": [ + { + "description": "OneOf Validation removes validations, making invalid data appear valid", + "data": { + "pets": [ + { + "pet_type": "Cat", + "hunts": "asdf", + "additionaValue": "asdf" + } + ] + }, + "valid": false, + "errors": [ + "/pets/0: must be valid to one and only one schema, but 0 are valid", + "/pets/0: required property 'age' not found", + "/pets/0: schema for 'else' is false" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/issue678.json b/src/test/resources/draft7/issue678.json new file mode 100644 index 000000000..08603efe6 --- /dev/null +++ b/src/test/resources/draft7/issue678.json @@ -0,0 +1,60 @@ +[ + { + "description": "issue678", + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/issue-470.json", + "title": "OneOf validation message", + "description": "Test description", + "type": "object", + "properties": { + "outerObject": { + "type": "object", + "properties": { + "innerObject": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "unit": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "value", + "unit" + ] + } + ] + } + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "OneOfValidator is filtering out the required errors if all the oneOf schemas are having the issues", + "data": { + "outerObject": { + "innerObject": {} + } + }, + "valid": false, + "errors": [ + "/outerObject/innerObject: object found, string expected", + "/outerObject/innerObject: required property 'value' not found", + "/outerObject/innerObject: required property 'unit' not found", + "/outerObject/innerObject: must be valid to one and only one schema, but 0 are valid" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/draft7/items.json b/src/test/resources/draft7/items.json deleted file mode 100644 index d40f7b50a..000000000 --- a/src/test/resources/draft7/items.json +++ /dev/null @@ -1,506 +0,0 @@ -[ - { - "description": "a schema given for items", - "schema": { - "items": { - "type": "integer" - } - }, - "tests": [ - { - "description": "valid items", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "wrong type of items", - "data": [ - 1, - "x" - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": { - "foo": "bar" - }, - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "length": 1 - }, - "valid": true - } - ] - }, - { - "description": "an array of schemas for items", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "tests": [ - { - "description": "correct types", - "data": [ - 1, - "foo" - ], - "valid": true - }, - { - "description": "wrong types", - "data": [ - "foo", - 1 - ], - "valid": false - }, - { - "description": "incomplete array of items", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with additional items", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array", - "data": [], - "valid": true - }, - { - "description": "JavaScript pseudo-array is valid", - "data": { - "0": "invalid", - "1": "valid", - "length": 2 - }, - "valid": true - } - ] - }, - { - "description": "items with boolean schema (true)", - "schema": { - "items": true - }, - "tests": [ - { - "description": "any array is valid", - "data": [ - 1, - "foo", - true - ], - "valid": true - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schema (false)", - "schema": { - "items": false - }, - "tests": [ - { - "description": "any non-empty array is invalid", - "data": [ - 1, - "foo", - true - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items with boolean schemas", - "schema": { - "items": [ - true, - false - ] - }, - "tests": [ - { - "description": "array with one item is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "array with two items is invalid", - "data": [ - 1, - "foo" - ], - "valid": false - }, - { - "description": "empty array is valid", - "data": [], - "valid": true - } - ] - }, - { - "description": "items and subitems", - "schema": { - "definitions": { - "item": { - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/definitions/sub-item" - }, - { - "$ref": "#/definitions/sub-item" - } - ] - }, - "sub-item": { - "type": "object", - "required": [ - "foo" - ] - } - }, - "type": "array", - "additionalItems": false, - "items": [ - { - "$ref": "#/definitions/item" - }, - { - "$ref": "#/definitions/item" - }, - { - "$ref": "#/definitions/item" - } - ] - }, - "tests": [ - { - "description": "valid items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": true - }, - { - "description": "too many items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "too many sub-items", - "data": [ - [ - { - "foo": null - }, - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong item", - "data": [ - { - "foo": null - }, - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "wrong sub-item", - "data": [ - [ - {}, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ], - [ - { - "foo": null - }, - { - "foo": null - } - ] - ], - "valid": false - }, - { - "description": "fewer items is valid", - "data": [ - [ - { - "foo": null - } - ], - [ - { - "foo": null - } - ] - ], - "valid": true - } - ] - }, - { - "description": "nested items", - "schema": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - } - } - } - } - }, - "tests": [ - { - "description": "valid nested array", - "data": [ - [ - [ - [ - 1 - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": true - }, - { - "description": "nested array with invalid type", - "data": [ - [ - [ - [ - "1" - ] - ], - [ - [ - 2 - ], - [ - 3 - ] - ] - ], - [ - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ] - ], - "valid": false - }, - { - "description": "not deep enough", - "data": [ - [ - [ - 1 - ], - [ - 2 - ], - [ - 3 - ] - ], - [ - [ - 4 - ], - [ - 5 - ], - [ - 6 - ] - ] - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/maxItems.json b/src/test/resources/draft7/maxItems.json deleted file mode 100644 index f25cb2897..000000000 --- a/src/test/resources/draft7/maxItems.json +++ /dev/null @@ -1,76 +0,0 @@ -[ - { - "description": "maxItems validation", - "schema": { - "maxItems": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "too long is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "foobar", - "valid": true - } - ] - }, - { - "description": "maxItems decimal validation", - "schema": { - "maxItems": 2.0 - }, - "tests": [ - { - "description": "shorter is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "too long is invalid", - "data": [ - 1, - 2, - 3 - ], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "foobar", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/maxLength.json b/src/test/resources/draft7/maxLength.json deleted file mode 100644 index edac73a50..000000000 --- a/src/test/resources/draft7/maxLength.json +++ /dev/null @@ -1,68 +0,0 @@ -[ - { - "description": "maxLength validation", - "schema": { - "maxLength": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": "f", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too long is invalid", - "data": "foo", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - }, - { - "description": "two supplementary Unicode code points is long enough", - "data": "\uD83D\uDCA9\uD83D\uDCA9", - "valid": true - } - ] - }, - { - "description": "maxLength decimal validation", - "schema": { - "maxLength": 2.0 -}, - "tests": [ - { - "description": "shorter is valid", - "data": "f", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too long is invalid", - "data": "foo", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - }, - { - "description": "two supplementary Unicode code points is long enough", - "data": "\uD83D\uDCA9\uD83D\uDCA9", - "valid": true - } -] - } -] diff --git a/src/test/resources/draft7/maxProperties.json b/src/test/resources/draft7/maxProperties.json deleted file mode 100644 index 982caeb74..000000000 --- a/src/test/resources/draft7/maxProperties.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "description": "maxProperties validation", - "schema": { - "maxProperties": 2 - }, - "tests": [ - { - "description": "shorter is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "too long is invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "maxProperties decimal validation", - "schema": { - "maxProperties": 2.0 - }, - "tests": [ - { - "description": "shorter is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "too long is invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/maximum.json b/src/test/resources/draft7/maximum.json deleted file mode 100644 index 194ee32ee..000000000 --- a/src/test/resources/draft7/maximum.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "maximum validation", - "schema": { - "maximum": 3.0 - }, - "tests": [ - { - "description": "below the maximum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 3.0, - "valid": true - }, - { - "description": "above the maximum is invalid", - "data": 3.5, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/minItems.json b/src/test/resources/draft7/minItems.json deleted file mode 100644 index 431adc3bd..000000000 --- a/src/test/resources/draft7/minItems.json +++ /dev/null @@ -1,68 +0,0 @@ -[ - { - "description": "minItems validation", - "schema": { - "minItems": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "too short is invalid", - "data": [], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "", - "valid": true - } - ] - }, - { - "description": "minItems decimal validation", - "schema": { - "minItems": 1.0 - }, - "tests": [ - { - "description": "longer is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "exact length is valid", - "data": [ - 1 - ], - "valid": true - }, - { - "description": "too short is invalid", - "data": [], - "valid": false - }, - { - "description": "ignores non-arrays", - "data": "", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/minLength.json b/src/test/resources/draft7/minLength.json deleted file mode 100644 index fdfb841c2..000000000 --- a/src/test/resources/draft7/minLength.json +++ /dev/null @@ -1,68 +0,0 @@ -[ - { - "description": "minLength validation", - "schema": { - "minLength": 2 - }, - "tests": [ - { - "description": "longer is valid", - "data": "foo", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too short is invalid", - "data": "f", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1, - "valid": true - }, - { - "description": "one supplementary Unicode code point is not long enough", - "data": "\uD83D\uDCA9", - "valid": false - } - ] - }, - { - "description": "minLength decimal validation", - "schema": { - "minLength": 2.0 - }, - "tests": [ - { - "description": "longer is valid", - "data": "foo", - "valid": true - }, - { - "description": "exact length is valid", - "data": "fo", - "valid": true - }, - { - "description": "too short is invalid", - "data": "f", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1, - "valid": true - }, - { - "description": "one supplementary Unicode code point is not long enough", - "data": "\uD83D\uDCA9", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/minProperties.json b/src/test/resources/draft7/minProperties.json deleted file mode 100644 index 2f22f682d..000000000 --- a/src/test/resources/draft7/minProperties.json +++ /dev/null @@ -1,88 +0,0 @@ -[ - { - "description": "minProperties validation", - "schema": { - "minProperties": 1 - }, - "tests": [ - { - "description": "longer is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "too short is invalid", - "data": {}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "minProperties decimal validation", - "schema": { - "minProperties": 1.0 - }, - "tests": [ - { - "description": "longer is valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "exact length is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "too short is invalid", - "data": {}, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/minimum.json b/src/test/resources/draft7/minimum.json deleted file mode 100644 index 47070907a..000000000 --- a/src/test/resources/draft7/minimum.json +++ /dev/null @@ -1,63 +0,0 @@ -[ - { - "description": "minimum validation", - "schema": { - "minimum": 1.1 - }, - "tests": [ - { - "description": "above the minimum is valid", - "data": 2.6, - "valid": true - }, - { - "description": "boundary point is valid", - "data": 1.1, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": 0.6, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - }, - { - "description": "minimum validation with signed integer", - "schema": { - "minimum": -2 - }, - "tests": [ - { - "description": "negative above the minimum is valid", - "data": -1, - "valid": true - }, - { - "description": "positive above the minimum is valid", - "data": 0, - "valid": true - }, - { - "description": "boundary point is valid", - "data": -2, - "valid": true - }, - { - "description": "below the minimum is invalid", - "data": -3, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "x", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/multipleOf.json b/src/test/resources/draft7/multipleOf.json deleted file mode 100644 index 886ea837e..000000000 --- a/src/test/resources/draft7/multipleOf.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "description": "by int", - "schema": { - "multipleOf": 2 - }, - "tests": [ - { - "description": "int by int", - "data": 10, - "valid": true - }, - { - "description": "int by int fail", - "data": 7, - "valid": false - }, - { - "description": "ignores non-numbers", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "by number", - "schema": { - "multipleOf": 1.5 - }, - "tests": [ - { - "description": "zero is multiple of anything", - "data": 0, - "valid": true - }, - { - "description": "4.5 is multiple of 1.5", - "data": 4.5, - "valid": true - }, - { - "description": "35 is not multiple of 1.5", - "data": 35, - "valid": false - } - ] - }, - { - "description": "by small number", - "schema": { - "multipleOf": 0.0001 - }, - "tests": [ - { - "description": "0.0075 is multiple of 0.0001", - "data": 0.0075, - "valid": true - }, - { - "description": "0.00751 is not multiple of 0.0001", - "data": 0.00751, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/not.json b/src/test/resources/draft7/not.json deleted file mode 100644 index c2d2e411e..000000000 --- a/src/test/resources/draft7/not.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "description": "not", - "schema": { - "not": { - "type": "integer" - } - }, - "tests": [ - { - "description": "allowed", - "data": "foo", - "valid": true - }, - { - "description": "disallowed", - "data": 1, - "valid": false - } - ] - }, - { - "description": "not multiple types", - "schema": { - "not": { - "type": [ - "integer", - "boolean" - ] - } - }, - "tests": [ - { - "description": "valid", - "data": "foo", - "valid": true - }, - { - "description": "mismatch", - "data": 1, - "valid": false - }, - { - "description": "other mismatch", - "data": true, - "valid": false - } - ] - }, - { - "description": "not more complex schema", - "schema": { - "not": { - "type": "object", - "properties": { - "foo": { - "type": "string" - } - } - } - }, - "tests": [ - { - "description": "match", - "data": 1, - "valid": true - }, - { - "description": "other match", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "foo": "bar" - }, - "valid": false - } - ] - }, - { - "description": "forbidden property", - "schema": { - "properties": { - "foo": { - "not": {} - } - } - }, - "tests": [ - { - "description": "property present", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "property absent", - "data": { - "bar": 1, - "baz": 2 - }, - "valid": true - } - ] - }, - { - "description": "not with boolean schema true", - "schema": { - "not": true - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "not with boolean schema false", - "schema": { - "not": false - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/oneOf.json b/src/test/resources/draft7/oneOf.json deleted file mode 100644 index 65ab47d0a..000000000 --- a/src/test/resources/draft7/oneOf.json +++ /dev/null @@ -1,327 +0,0 @@ -[ - { - "description": "oneOf", - "schema": { - "oneOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": 1, - "valid": true - }, - { - "description": "second oneOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both oneOf valid", - "data": 3, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "oneOf with base schema", - "schema": { - "type": "string", - "oneOf": [ - { - "minLength": 2 - }, - { - "maxLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one oneOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both oneOf valid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all true", - "schema": { - "oneOf": [ - true, - true, - true - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, one true", - "schema": { - "oneOf": [ - true, - false, - false - ] - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "oneOf with boolean schemas, more than one true", - "schema": { - "oneOf": [ - true, - true, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf with boolean schemas, all false", - "schema": { - "oneOf": [ - false, - false, - false - ] - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "oneOf complex types", - "schema": { - "oneOf": [ - { - "properties": { - "bar": { - "type": "integer" - } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid (complex)", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "second oneOf valid (complex)", - "data": { - "foo": "baz" - }, - "valid": true - }, - { - "description": "both oneOf valid (complex)", - "data": { - "foo": "baz", - "bar": 2 - }, - "valid": false - }, - { - "description": "neither oneOf valid (complex)", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - } - ] - }, - { - "description": "oneOf with empty schema", - "schema": { - "oneOf": [ - { - "type": "number" - }, - {} - ] - }, - "tests": [ - { - "description": "one valid - valid", - "data": "foo", - "valid": true - }, - { - "description": "both valid - invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "oneOf with required", - "schema": { - "type": "object", - "oneOf": [ - { - "required": [ - "foo", - "bar" - ] - }, - { - "required": [ - "foo", - "baz" - ] - } - ] - }, - "tests": [ - { - "description": "both invalid - invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "first valid - valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "second valid - valid", - "data": { - "foo": 1, - "baz": 3 - }, - "valid": true - }, - { - "description": "both valid - invalid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": false - } - ] - }, - { - "description": "oneOf with missing optional property", - "schema": { - "oneOf": [ - { - "properties": { - "bar": true, - "baz": true - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": true - }, - "required": [ - "foo" - ] - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": { - "bar": 8 - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "foo": "foo" - }, - "valid": true - }, - { - "description": "both oneOf valid", - "data": { - "foo": "foo", - "bar": 8 - }, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": { - "baz": "quux" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/bignum.json b/src/test/resources/draft7/optional/bignum.json deleted file mode 100644 index 4e5bafdfa..000000000 --- a/src/test/resources/draft7/optional/bignum.json +++ /dev/null @@ -1,119 +0,0 @@ -[ - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a bignum is an integer", - "data": 12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a bignum is a number", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "integer", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a negative bignum is an integer", - "data": -12345678910111213141516171819202122232425262728293031, - "valid": true - } - ] - }, - { - "description": "number", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "a negative bignum is a number", - "data": -98249283749234923498293171823948729348710298301928331, - "valid": true - } - ] - }, - { - "description": "string", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "a bignum is not a string", - "data": 98249283749234923498293171823948729348710298301928331, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "maximum": 18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision", - "schema": { - "exclusiveMaximum": 972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for high numbers", - "data": 972783798187987123879878123.188781371, - "valid": false - } - ] - }, - { - "description": "integer comparison", - "schema": { - "minimum": -18446744073709551615 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -18446744073709551600, - "valid": true - } - ] - }, - { - "description": "float comparison with high precision on negative numbers", - "schema": { - "exclusiveMinimum": -972783798187987123879878123.18878137 - }, - "tests": [ - { - "description": "comparison works for very negative numbers", - "data": -972783798187987123879878123.188781371, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/content.json b/src/test/resources/draft7/optional/content.json deleted file mode 100644 index 0efc737a2..000000000 --- a/src/test/resources/draft7/optional/content.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "description": "validation of string-encoded content based on media type", - "schema": { - "contentMediaType": "application/json" - }, - "tests": [ - { - "description": "a valid JSON document", - "data": "{\"foo\": \"bar\"}", - "valid": true - }, - { - "description": "an invalid JSON document", - "data": "{:}", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - }, - { - "description": "validation of binary string-encoding", - "schema": { - "contentEncoding": "base64" - }, - "tests": [ - { - "description": "a valid base64 string", - "data": "eyJmb28iOiAiYmFyIn0K", - "valid": true - }, - { - "description": "an invalid base64 string (% is not a valid character)", - "data": "eyJmb28iOi%iYmFyIn0K", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - }, - { - "description": "validation of binary-encoded media type documents", - "schema": { - "contentMediaType": "application/json", - "contentEncoding": "base64" - }, - "tests": [ - { - "description": "a valid base64-encoded JSON document", - "data": "eyJmb28iOiAiYmFyIn0K", - "valid": true - }, - { - "description": "a validly-encoded invalid JSON document", - "data": "ezp9Cg==", - "valid": false - }, - { - "description": "an invalid base64 string that is valid JSON", - "data": "{}", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 100, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/optional/ecmascript-regex.json b/src/test/resources/draft7/optional/ecmascript-regex.json deleted file mode 100644 index a27652188..000000000 --- a/src/test/resources/draft7/optional/ecmascript-regex.json +++ /dev/null @@ -1,215 +0,0 @@ -[ - { - "description": "ECMA 262 regex non-compliance", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "ECMA 262 has no support for \\Z anchor from .NET", - "data": "^\\S(|(.|\\n)*\\S)\\Z", - "valid": false - } - ] - }, - { - "description": "ECMA 262 regex $ does not match trailing newline", - "schema": { - "type": "string", - "pattern": "^abc$" - }, - "tests": [ - { - "description": "matches in Python, but should not in jsonschema", - "data": "abc\n", - "valid": false - }, - { - "description": "should match", - "data": "abc", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex converts \\a to ascii BEL", - "schema": { - "type": "string", - "pattern": "^\\a$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\a", - "valid": false - }, - { - "description": "matches", - "data": "\u0007", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and upper letter", - "schema": { - "type": "string", - "pattern": "^\\cC$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cC", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 regex escapes control codes with \\c and lower letter", - "schema": { - "type": "string", - "pattern": "^\\cc$" - }, - "tests": [ - { - "description": "does not match", - "data": "\\cc", - "valid": false - }, - { - "description": "matches", - "data": "\u0003", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\d matches ascii digits only", - "schema": { - "type": "string", - "pattern": "^\\d$" - }, - "tests": [ - { - "description": "ASCII zero matches", - "data": "0", - "valid": true - }, - { - "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", - "data": "߀", - "valid": false - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) does not match", - "data": "\u07c0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\D matches everything but ascii digits", - "schema": { - "type": "string", - "pattern": "^\\D$" - }, - "tests": [ - { - "description": "ASCII zero does not match", - "data": "0", - "valid": false - }, - { - "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", - "data": "߀", - "valid": true - }, - { - "description": "NKO DIGIT ZERO (as \\u escape) matches", - "data": "\u07c0", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\w matches ascii letters only", - "schema": { - "type": "string", - "pattern": "^\\w$" - }, - "tests": [ - { - "description": "ASCII 'a' matches", - "data": "a", - "valid": true - }, - { - "description": "latin-1 e-acute does not match (unlike e.g. Python)", - "data": "é", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\w matches everything but ascii letters", - "schema": { - "type": "string", - "pattern": "^\\W$" - }, - "tests": [ - { - "description": "ASCII 'a' does not match", - "data": "a", - "valid": false - }, - { - "description": "latin-1 e-acute matches (unlike e.g. Python)", - "data": "é", - "valid": true - } - ] - }, - { - "description": "ECMA 262 \\s matches ascii whitespace only", - "schema": { - "type": "string", - "pattern": "^\\s$" - }, - "tests": [ - { - "description": "ASCII space matches", - "data": " ", - "valid": true - }, - { - "description": "latin-1 non-breaking-space does not match (unlike e.g. Python)", - "data": "\u00a0", - "valid": false - } - ] - }, - { - "description": "ECMA 262 \\S matches everything but ascii whitespace", - "schema": { - "type": "string", - "pattern": "^\\S$" - }, - "tests": [ - { - "description": "ASCII space does not match", - "data": " ", - "valid": false - }, - { - "description": "latin-1 non-breaking-space matches (unlike e.g. Python)", - "data": "\u00a0", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/date-time.json b/src/test/resources/draft7/optional/format/date-time.json deleted file mode 100644 index 6baa1a564..000000000 --- a/src/test/resources/draft7/optional/format/date-time.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "description": "validation of date-time strings", - "schema": { - "format": "date-time" - }, - "tests": [ - { - "description": "a valid date-time string", - "data": "1963-06-19T08:30:06.283185Z", - "valid": true - }, - { - "description": "a valid date-time string without second fraction", - "data": "1963-06-19T08:30:06Z", - "valid": true - }, - { - "description": "a valid date-time string with plus offset", - "data": "1937-01-01T12:00:27.87+00:20", - "valid": true - }, - { - "description": "a valid date-time string with minus offset", - "data": "1990-12-31T15:59:50.123-08:00", - "valid": true - }, - { - "description": "a invalid day in date-time string", - "data": "1990-02-31T15:59:60.123-08:00", - "valid": false - }, - { - "description": "an invalid offset in date-time string", - "data": "1990-12-31T15:59:60-24:00", - "valid": false - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963 08:30:06 PST", - "valid": false - }, - { - "description": "case-insensitive T and Z", - "data": "1963-06-19t08:30:06.283185z", - "valid": true - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350T01:01:01", - "valid": false - }, - { - "description": "an invalid date-time string without offset", - "data": "1963-06-19T08:30:06", - "valid": false - }, - { - "description": "an invalid date-time string with only hours in offset", - "data": "1963-06-19T08:30:06+02", - "valid": false - }, - { - "description": "an invalid date-time string without colon in offset", - "data": "1963-06-19T08:30:06+0200", - "valid": false - }, - { - "description": "a valid date-time string with leap second", - "data": "1998-12-31T23:59:60Z", - "valid": true - }, - { - "description": "an invalid date-time string with leap second", - "data": "1997-12-31T23:59:60Z", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/date.json b/src/test/resources/draft7/optional/format/date.json deleted file mode 100644 index 39c66593f..000000000 --- a/src/test/resources/draft7/optional/format/date.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "description": "validation of date strings", - "schema": { - "format": "date" - }, - "tests": [ - { - "description": "a valid date string", - "data": "1963-06-19", - "valid": true - }, - { - "description": "an invalid date-time string", - "data": "06/19/1963", - "valid": false - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "2013-350", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/email.json b/src/test/resources/draft7/optional/format/email.json deleted file mode 100644 index 9873d6c10..000000000 --- a/src/test/resources/draft7/optional/format/email.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of e-mail addresses", - "schema": { - "format": "email" - }, - "tests": [ - { - "description": "a valid e-mail address", - "data": "joe.bloggs@example.com", - "valid": true - }, - { - "description": "an invalid e-mail address", - "data": "2962", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/hostname.json b/src/test/resources/draft7/optional/format/hostname.json deleted file mode 100644 index e2ec7960d..000000000 --- a/src/test/resources/draft7/optional/format/hostname.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of host names", - "schema": { - "format": "hostname" - }, - "tests": [ - { - "description": "a valid host name", - "data": "www.example.com", - "valid": true - }, - { - "description": "a valid punycoded IDN hostname", - "data": "xn--4gbwdl.xn--wgbh1c", - "valid": true - }, - { - "description": "a host name starting with an illegal character", - "data": "-a-host-name-that-starts-with--", - "valid": false - }, - { - "description": "a host name containing illegal characters", - "data": "not_a_valid_host_name", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/idn-email.json b/src/test/resources/draft7/optional/format/idn-email.json deleted file mode 100644 index 7619a1870..000000000 --- a/src/test/resources/draft7/optional/format/idn-email.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of an internationalized e-mail addresses", - "schema": { - "format": "idn-email" - }, - "tests": [ - { - "description": "a valid idn e-mail (example@example.test in Hangul)", - "data": "실례@실례.테스트", - "valid": true - }, - { - "description": "an invalid idn e-mail address", - "data": "2962", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/idn-hostname.json b/src/test/resources/draft7/optional/format/idn-hostname.json deleted file mode 100644 index c17b6572d..000000000 --- a/src/test/resources/draft7/optional/format/idn-hostname.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "validation of internationalized host names", - "schema": { - "format": "idn-hostname" - }, - "tests": [ - { - "description": "a valid host name (example.test in Hangul)", - "data": "실례.테스트", - "valid": true - }, - { - "description": "illegal first char U+302E Hangul single dot tone mark", - "data": "〮실례.테스트", - "valid": false - }, - { - "description": "contains illegal char U+302E Hangul single dot tone mark", - "data": "실〮례.테스트", - "valid": false - }, - { - "description": "a host name with a component too long", - "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/ipv4.json b/src/test/resources/draft7/optional/format/ipv4.json deleted file mode 100644 index f8d8df0cc..000000000 --- a/src/test/resources/draft7/optional/format/ipv4.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of IP addresses", - "schema": { - "format": "ipv4" - }, - "tests": [ - { - "description": "a valid IP address", - "data": "192.168.0.1", - "valid": true - }, - { - "description": "an IP address with too many components", - "data": "127.0.0.0.1", - "valid": false - }, - { - "description": "an IP address with out-of-range values", - "data": "256.256.256.256", - "valid": false - }, - { - "description": "an IP address without 4 components", - "data": "127.0", - "valid": false - }, - { - "description": "an IP address as an integer", - "data": "0x7f000001", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/ipv6.json b/src/test/resources/draft7/optional/format/ipv6.json deleted file mode 100644 index ee728012d..000000000 --- a/src/test/resources/draft7/optional/format/ipv6.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "validation of IPv6 addresses", - "schema": { - "format": "ipv6" - }, - "tests": [ - { - "description": "a valid IPv6 address", - "data": "::1", - "valid": true - }, - { - "description": "an IPv6 address with out-of-range values", - "data": "12345::", - "valid": false - }, - { - "description": "an IPv6 address with too many components", - "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", - "valid": false - }, - { - "description": "an IPv6 address containing illegal characters", - "data": "::laptop", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/iri-reference.json b/src/test/resources/draft7/optional/format/iri-reference.json deleted file mode 100644 index a82de6389..000000000 --- a/src/test/resources/draft7/optional/format/iri-reference.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "validation of IRI References", - "schema": { - "format": "iri-reference" - }, - "tests": [ - { - "description": "a valid IRI", - "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid protocol-relative IRI Reference", - "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid relative IRI Reference", - "data": "/âππ", - "valid": true - }, - { - "description": "an invalid IRI Reference", - "data": "\\\\WINDOWS\\filëßåré", - "valid": false - }, - { - "description": "a valid IRI Reference", - "data": "âππ", - "valid": true - }, - { - "description": "a valid IRI fragment", - "data": "#ƒrägmênt", - "valid": true - }, - { - "description": "an invalid IRI fragment", - "data": "#ƒräg\\mênt", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/iri.json b/src/test/resources/draft7/optional/format/iri.json deleted file mode 100644 index e0e182ade..000000000 --- a/src/test/resources/draft7/optional/format/iri.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "description": "validation of IRIs", - "schema": { - "format": "iri" - }, - "tests": [ - { - "description": "a valid IRI with anchor tag", - "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", - "valid": true - }, - { - "description": "a valid IRI with anchor tag and parantheses", - "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", - "valid": true - }, - { - "description": "a valid IRI with URL-encoded stuff", - "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid IRI with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid IRI based on IPv6", - "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", - "valid": true - }, - { - "description": "an invalid IRI based on IPv6", - "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", - "valid": false - }, - { - "description": "an invalid relative IRI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid IRI", - "data": "\\\\WINDOWS\\filëßåré", - "valid": false - }, - { - "description": "an invalid IRI though valid IRI reference", - "data": "âππ", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/json-pointer.json b/src/test/resources/draft7/optional/format/json-pointer.json deleted file mode 100644 index da3c341e6..000000000 --- a/src/test/resources/draft7/optional/format/json-pointer.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "description": "validation of JSON-pointers (JSON String Representation)", - "schema": { - "format": "json-pointer" - }, - "tests": [ - { - "description": "a valid JSON-pointer", - "data": "/foo/bar~0/baz~1/%a", - "valid": true - }, - { - "description": "not a valid JSON-pointer (~ not escaped)", - "data": "/foo/bar~", - "valid": false - }, - { - "description": "valid JSON-pointer with empty segment", - "data": "/foo//bar", - "valid": true - }, - { - "description": "valid JSON-pointer with the last empty segment", - "data": "/foo/bar/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #1", - "data": "", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #2", - "data": "/foo", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #3", - "data": "/foo/0", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #4", - "data": "/", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #5", - "data": "/a~1b", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #6", - "data": "/c%d", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #7", - "data": "/e^f", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #8", - "data": "/g|h", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #9", - "data": "/i\\j", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #10", - "data": "/k\"l", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #11", - "data": "/ ", - "valid": true - }, - { - "description": "valid JSON-pointer as stated in RFC 6901 #12", - "data": "/m~0n", - "valid": true - }, - { - "description": "valid JSON-pointer used adding to the last array position", - "data": "/foo/-", - "valid": true - }, - { - "description": "valid JSON-pointer (- used as object member name)", - "data": "/foo/-/bar", - "valid": true - }, - { - "description": "valid JSON-pointer (multiple escaped characters)", - "data": "/~1~0~0~1~1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #1", - "data": "/~1.1", - "valid": true - }, - { - "description": "valid JSON-pointer (escaped with fraction part) #2", - "data": "/~0.1", - "valid": true - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", - "data": "#", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", - "data": "#/", - "valid": false - }, - { - "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", - "data": "#a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #1", - "data": "/~0~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (some escaped, but not all) #2", - "data": "/~0/~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #1", - "data": "/~2", - "valid": false - }, - { - "description": "not a valid JSON-pointer (wrong escape character) #2", - "data": "/~-1", - "valid": false - }, - { - "description": "not a valid JSON-pointer (multiple characters not escaped)", - "data": "/~~", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", - "data": "a", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", - "data": "0", - "valid": false - }, - { - "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", - "data": "a/a", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/regex.json b/src/test/resources/draft7/optional/format/regex.json deleted file mode 100644 index 7907f6a96..000000000 --- a/src/test/resources/draft7/optional/format/regex.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "description": "validation of regular expressions", - "schema": { - "format": "regex" - }, - "tests": [ - { - "description": "a valid regular expression", - "data": "([abc])+\\s+$", - "valid": true - }, - { - "description": "a regular expression with unclosed parens is invalid", - "data": "^(abc]", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/relative-json-pointer.json b/src/test/resources/draft7/optional/format/relative-json-pointer.json deleted file mode 100644 index cd30dfa3c..000000000 --- a/src/test/resources/draft7/optional/format/relative-json-pointer.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "description": "validation of Relative JSON Pointers (RJP)", - "schema": { - "format": "relative-json-pointer" - }, - "tests": [ - { - "description": "a valid upwards RJP", - "data": "1", - "valid": true - }, - { - "description": "a valid downwards RJP", - "data": "0/foo/bar", - "valid": true - }, - { - "description": "a valid up and then down RJP, with array index", - "data": "2/0/baz/1/zip", - "valid": true - }, - { - "description": "a valid RJP taking the member or index name", - "data": "0#", - "valid": true - }, - { - "description": "an invalid RJP that is a valid JSON Pointer", - "data": "/foo/bar", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/time.json b/src/test/resources/draft7/optional/format/time.json deleted file mode 100644 index 87ad9097a..000000000 --- a/src/test/resources/draft7/optional/format/time.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "description": "validation of time strings", - "schema": { - "format": "time" - }, - "tests": [ - { - "description": "a valid time string", - "data": "08:30:06.283185Z", - "valid": true - }, - { - "description": "an invalid time string", - "data": "08:30:06 PST", - "valid": false - }, - { - "description": "only RFC3339 not all of ISO 8601 are valid", - "data": "01:01:01,1111", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/uri-reference.json b/src/test/resources/draft7/optional/format/uri-reference.json deleted file mode 100644 index 89756d60b..000000000 --- a/src/test/resources/draft7/optional/format/uri-reference.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "description": "validation of URI References", - "schema": { - "format": "uri-reference" - }, - "tests": [ - { - "description": "a valid URI", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid relative URI Reference", - "data": "/abc", - "valid": true - }, - { - "description": "an invalid URI Reference", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "a valid URI Reference", - "data": "abc", - "valid": true - }, - { - "description": "a valid URI fragment", - "data": "#fragment", - "valid": true - }, - { - "description": "an invalid URI fragment", - "data": "#frag\\ment", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/uri-template.json b/src/test/resources/draft7/optional/format/uri-template.json deleted file mode 100644 index 58e20b422..000000000 --- a/src/test/resources/draft7/optional/format/uri-template.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "description": "format: uri-template", - "schema": { - "format": "uri-template" - }, - "tests": [ - { - "description": "a valid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term}", - "valid": true - }, - { - "description": "an invalid uri-template", - "data": "http://example.com/dictionary/{term:1}/{term", - "valid": false - }, - { - "description": "a valid uri-template without variables", - "data": "http://example.com/dictionary", - "valid": true - }, - { - "description": "a valid relative uri-template", - "data": "dictionary/{term:1}/{term}", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/optional/format/uri.json b/src/test/resources/draft7/optional/format/uri.json deleted file mode 100644 index 1937a0dac..000000000 --- a/src/test/resources/draft7/optional/format/uri.json +++ /dev/null @@ -1,105 +0,0 @@ -[ - { - "description": "validation of URIs", - "schema": { - "format": "uri" - }, - "tests": [ - { - "description": "a valid URL with anchor tag", - "data": "http://foo.bar/?baz=qux#quux", - "valid": true - }, - { - "description": "a valid URL with anchor tag and parantheses", - "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", - "valid": true - }, - { - "description": "a valid URL with URL-encoded stuff", - "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", - "valid": true - }, - { - "description": "a valid puny-coded URL ", - "data": "http://xn--nw2a.xn--j6w193g/", - "valid": true - }, - { - "description": "a valid URL with many special characters", - "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", - "valid": true - }, - { - "description": "a valid URL based on IPv4", - "data": "http://223.255.255.254", - "valid": true - }, - { - "description": "a valid URL with ftp scheme", - "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", - "valid": true - }, - { - "description": "a valid URL for a simple text file", - "data": "http://www.ietf.org/rfc/rfc2396.txt", - "valid": true - }, - { - "description": "a valid URL ", - "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", - "valid": true - }, - { - "description": "a valid mailto URI", - "data": "mailto:John.Doe@example.com", - "valid": true - }, - { - "description": "a valid newsgroup URI", - "data": "news:comp.infosystems.www.servers.unix", - "valid": true - }, - { - "description": "a valid tel URI", - "data": "tel:+1-816-555-1212", - "valid": true - }, - { - "description": "a valid URN", - "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - "valid": true - }, - { - "description": "an invalid protocol-relative URI Reference", - "data": "//foo.bar/?baz=qux#quux", - "valid": false - }, - { - "description": "an invalid relative URI Reference", - "data": "/abc", - "valid": false - }, - { - "description": "an invalid URI", - "data": "\\\\WINDOWS\\fileshare", - "valid": false - }, - { - "description": "an invalid URI though valid URI reference", - "data": "abc", - "valid": false - }, - { - "description": "an invalid URI with spaces", - "data": "http:// shouldfail.com", - "valid": false - }, - { - "description": "an invalid URI with spaces and missing scheme", - "data": ":// should fail", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/optional/zeroTerminatedFloats.json b/src/test/resources/draft7/optional/zeroTerminatedFloats.json deleted file mode 100644 index 046c7a5e3..000000000 --- a/src/test/resources/draft7/optional/zeroTerminatedFloats.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "description": "some languages do not distinguish between different types of numeric value", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "a float without fractional part is an integer", - "data": 1.0, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/pattern.json b/src/test/resources/draft7/pattern.json deleted file mode 100644 index 273559706..000000000 --- a/src/test/resources/draft7/pattern.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "description": "pattern validation", - "schema": { - "pattern": "^a*$" - }, - "tests": [ - { - "description": "a matching pattern is valid", - "data": "aaa", - "valid": true - }, - { - "description": "a non-matching pattern is invalid", - "data": "abc", - "valid": false - }, - { - "description": "ignores non-strings", - "data": true, - "valid": true - } - ] - }, - { - "description": "pattern is not anchored", - "schema": { - "pattern": "a+" - }, - "tests": [ - { - "description": "matches a substring", - "data": "xxaayy", - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/patternProperties.json b/src/test/resources/draft7/patternProperties.json deleted file mode 100644 index 8823ed971..000000000 --- a/src/test/resources/draft7/patternProperties.json +++ /dev/null @@ -1,202 +0,0 @@ -[ - { - "description": "patternProperties validates properties matching a regex", - "schema": { - "patternProperties": { - "f.*o": { - "type": "integer" - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "multiple valid matches is valid", - "data": { - "foo": 1, - "foooooo": 2 - }, - "valid": true - }, - { - "description": "a single invalid match is invalid", - "data": { - "foo": "bar", - "fooooo": 2 - }, - "valid": false - }, - { - "description": "multiple invalid matches is invalid", - "data": { - "foo": "bar", - "foooooo": "baz" - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "foo" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foo", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "multiple simultaneous patternProperties are validated", - "schema": { - "patternProperties": { - "a*": { - "type": "integer" - }, - "aaa*": { - "maximum": 20 - } - } - }, - "tests": [ - { - "description": "a single valid match is valid", - "data": { - "a": 21 - }, - "valid": true - }, - { - "description": "a simultaneous match is valid", - "data": { - "aaaa": 18 - }, - "valid": true - }, - { - "description": "multiple matches is valid", - "data": { - "a": 21, - "aaaa": 18 - }, - "valid": true - }, - { - "description": "an invalid due to one is invalid", - "data": { - "a": "bar" - }, - "valid": false - }, - { - "description": "an invalid due to the other is invalid", - "data": { - "aaaa": 31 - }, - "valid": false - }, - { - "description": "an invalid due to both is invalid", - "data": { - "aaa": "foo", - "aaaa": 31 - }, - "valid": false - } - ] - }, - { - "description": "regexes are not anchored by default and are case sensitive", - "schema": { - "patternProperties": { - "[0-9]{2,}": { - "type": "boolean" - }, - "X_": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "non recognized members are ignored", - "data": { - "answer 1": "42" - }, - "valid": true - }, - { - "description": "recognized members are accounted for", - "data": { - "a31b": null - }, - "valid": false - }, - { - "description": "regexes are case sensitive", - "data": { - "a_x_3": 3 - }, - "valid": true - }, - { - "description": "regexes are case sensitive, 2", - "data": { - "a_X_3": 3 - }, - "valid": false - } - ] - }, - { - "description": "patternProperties with boolean schemas", - "schema": { - "patternProperties": { - "f.*": true, - "b.*": false - } - }, - "tests": [ - { - "description": "object with property matching schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property matching schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/properties.json b/src/test/resources/draft7/properties.json deleted file mode 100644 index daf6708f3..000000000 --- a/src/test/resources/draft7/properties.json +++ /dev/null @@ -1,238 +0,0 @@ -[ - { - "description": "object properties validation", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "both properties present and valid is valid", - "data": { - "foo": 1, - "bar": "baz" - }, - "valid": true - }, - { - "description": "one property invalid is invalid", - "data": { - "foo": 1, - "bar": {} - }, - "valid": false - }, - { - "description": "both properties invalid is invalid", - "data": { - "foo": [], - "bar": {} - }, - "valid": false - }, - { - "description": "doesn't invalidate other properties", - "data": { - "quux": [] - }, - "valid": true - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "properties, patternProperties, additionalProperties interaction", - "schema": { - "properties": { - "foo": { - "type": "array", - "maxItems": 3 - }, - "bar": { - "type": "array" - } - }, - "patternProperties": { - "f.o": { - "minItems": 2 - } - }, - "additionalProperties": { - "type": "integer" - } - }, - "tests": [ - { - "description": "property validates property", - "data": { - "foo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "property invalidates property", - "data": { - "foo": [ - 1, - 2, - 3, - 4 - ] - }, - "valid": false - }, - { - "description": "patternProperty invalidates property", - "data": { - "foo": [] - }, - "valid": false - }, - { - "description": "patternProperty validates nonproperty", - "data": { - "fxo": [ - 1, - 2 - ] - }, - "valid": true - }, - { - "description": "patternProperty invalidates nonproperty", - "data": { - "fxo": [] - }, - "valid": false - }, - { - "description": "additionalProperty ignores property", - "data": { - "bar": [] - }, - "valid": true - }, - { - "description": "additionalProperty validates others", - "data": { - "quux": 3 - }, - "valid": true - }, - { - "description": "additionalProperty invalidates others", - "data": { - "quux": "foo" - }, - "valid": false - } - ] - }, - { - "description": "properties with boolean schema", - "schema": { - "properties": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "no property present is valid", - "data": {}, - "valid": true - }, - { - "description": "only 'true' property present is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "only 'false' property present is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "both properties present is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "properties with escaped characters", - "schema": { - "properties": { - "foo\nbar": { - "type": "number" - }, - "foo\"bar": { - "type": "number" - }, - "foo\\bar": { - "type": "number" - }, - "foo\rbar": { - "type": "number" - }, - "foo\tbar": { - "type": "number" - }, - "foo\fbar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with all numbers is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1", - "foo\\bar": "1", - "foo\rbar": "1", - "foo\tbar": "1", - "foo\fbar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/propertyNames.json b/src/test/resources/draft7/propertyNames.json deleted file mode 100644 index 54cd1f2ab..000000000 --- a/src/test/resources/draft7/propertyNames.json +++ /dev/null @@ -1,122 +0,0 @@ -[ - { - "description": "propertyNames validation", - "schema": { - "propertyNames": { - "maxLength": 3 - } - }, - "tests": [ - { - "description": "all property names valid", - "data": { - "f": {}, - "foo": {} - }, - "valid": true - }, - { - "description": "some property names invalid", - "data": { - "foo": {}, - "foobar": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - }, - { - "description": "ignores arrays", - "data": [ - 1, - 2, - 3, - 4 - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "propertyNames validation with pattern", - "schema": { - "propertyNames": { "pattern": "^a+$" } - }, - "tests": [ - { - "description": "matching property names are valid", - "data": { - "a": {}, - "aa": {}, - "aaa": {} - }, - "valid": true - }, - { - "description": "non-matching property name is invalid", - "data": { - "aaA": {} - }, - "valid": false - }, - { - "description": "object without properties is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema true", - "schema": { - "propertyNames": true - }, - "tests": [ - { - "description": "object with any properties is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "propertyNames with boolean schema false", - "schema": { - "propertyNames": false - }, - "tests": [ - { - "description": "object with any properties is invalid", - "data": { - "foo": 1 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - } -] diff --git a/src/test/resources/draft7/ref.json b/src/test/resources/draft7/ref.json deleted file mode 100644 index a08c2bf73..000000000 --- a/src/test/resources/draft7/ref.json +++ /dev/null @@ -1,566 +0,0 @@ -[ - { - "description": "root pointer ref", - "schema": { - "properties": { - "foo": { - "$ref": "#" - } - }, - "additionalProperties": false - }, - "tests": [ - { - "description": "match", - "data": { - "foo": false - }, - "valid": true - }, - { - "description": "recursive match", - "data": { - "foo": { - "foo": false - } - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": false - }, - "valid": false - }, - { - "description": "recursive mismatch", - "data": { - "foo": { - "bar": false - } - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to object", - "schema": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "$ref": "#/properties/foo" - } - } - }, - "tests": [ - { - "description": "match", - "data": { - "bar": 3 - }, - "valid": true - }, - { - "description": "mismatch", - "data": { - "bar": true - }, - "valid": false - } - ] - }, - { - "description": "relative pointer ref to array", - "schema": { - "items": [ - { - "type": "integer" - }, - { - "$ref": "#/items/0" - } - ] - }, - "tests": [ - { - "description": "match array", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "mismatch array", - "data": [ - 1, - "foo" - ], - "valid": false - } - ] - }, - { - "description": "escaped pointer ref", - "schema": { - "tilda~field": { - "type": "integer" - }, - "slash/field": { - "type": "integer" - }, - "percent%field": { - "type": "integer" - }, - "properties": { - "tilda": { - "$ref": "#/tilda~0field" - }, - "slash": { - "$ref": "#/slash~1field" - }, - "percent": { - "$ref": "#/percent%25field" - } - } - }, - "tests": [ - { - "description": "slash invalid", - "data": { - "slash": "aoeu" - }, - "valid": false - }, - { - "description": "tilda invalid", - "data": { - "tilda": "aoeu" - }, - "valid": false - }, - { - "description": "percent invalid", - "data": { - "percent": "aoeu" - }, - "valid": false - }, - { - "description": "slash valid", - "data": { - "slash": 123 - }, - "valid": true - }, - { - "description": "tilda valid", - "data": { - "tilda": 123 - }, - "valid": true - }, - { - "description": "percent valid", - "data": { - "percent": 123 - }, - "valid": true - } - ] - }, - { - "description": "nested refs", - "schema": { - "definitions": { - "a": { - "type": "integer" - }, - "b": { - "$ref": "#/definitions/a" - }, - "c": { - "$ref": "#/definitions/b" - } - }, - "$ref": "#/definitions/c" - }, - "tests": [ - { - "description": "nested ref valid", - "data": 5, - "valid": true - }, - { - "description": "nested ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref overrides any sibling keywords", - "schema": { - "definitions": { - "reffed": { - "type": "array" - } - }, - "properties": { - "foo": { - "$ref": "#/definitions/reffed", - "maxItems": 2 - } - } - }, - "tests": [ - { - "description": "ref valid", - "data": { - "foo": [] - }, - "valid": true - }, - { - "description": "ref valid, maxItems ignored", - "data": { - "foo": [ - 1, - 2, - 3 - ] - }, - "valid": true - }, - { - "description": "ref invalid", - "data": { - "foo": "string" - }, - "valid": false - } - ] - }, - { - "description": "remote ref, containing refs itself", - "schema": { - "$ref": "http://json-schema.org/draft-07/schema#" - }, - "tests": [ - { - "description": "remote ref valid", - "data": { - "minLength": 1 - }, - "valid": true - }, - { - "description": "remote ref invalid", - "data": { - "minLength": -1 - }, - "valid": false - } - ] - }, - { - "description": "property named $ref that is not a reference", - "schema": { - "properties": { - "$ref": { - "type": "string" - } - } - }, - "tests": [ - { - "description": "property named $ref valid", - "data": { - "$ref": "a" - }, - "valid": true - }, - { - "description": "property named $ref invalid", - "data": { - "$ref": 2 - }, - "valid": false - } - ] - }, - { - "description": "$ref to boolean schema true", - "schema": { - "$ref": "#/definitions/bool", - "definitions": { - "bool": true - } - }, - "tests": [ - { - "description": "any value is valid", - "data": "foo", - "valid": true - } - ] - }, - { - "description": "$ref to boolean schema false", - "schema": { - "$ref": "#/definitions/bool", - "definitions": { - "bool": false - } - }, - "tests": [ - { - "description": "any value is invalid", - "data": "foo", - "valid": false - } - ] - }, - { - "description": "Recursive references between schemas", - "schema": { - "$id": "http://localhost:1234/tree", - "description": "tree of nodes", - "type": "object", - "properties": { - "meta": { - "type": "string" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "node" - } - } - }, - "required": [ - "meta", - "nodes" - ], - "definitions": { - "node": { - "$id": "http://localhost:1234/node", - "description": "node", - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "subtree": { - "$ref": "tree" - } - }, - "required": [ - "value" - ] - } - } - }, - "tests": [ - { - "description": "valid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 1.1 - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": true - }, - { - "description": "invalid tree", - "data": { - "meta": "root", - "nodes": [ - { - "value": 1, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": "string is invalid" - }, - { - "value": 1.2 - } - ] - } - }, - { - "value": 2, - "subtree": { - "meta": "child", - "nodes": [ - { - "value": 2.1 - }, - { - "value": 2.2 - } - ] - } - } - ] - }, - "valid": false - } - ] - }, - { - "description": "refs with quote", - "schema": { - "properties": { - "foo\"bar": { - "$ref": "#/definitions/foo%22bar" - } - }, - "definitions": { - "foo\"bar": { - "type": "number" - } - } - }, - "tests": [ - { - "description": "object with numbers is valid", - "data": { - "foo\"bar": 1 - }, - "valid": true - }, - { - "description": "object with strings is invalid", - "data": { - "foo\"bar": "1" - }, - "valid": false - } - ] - }, - { - "description": "Location-independent identifier", - "schema": { - "allOf": [ - { - "$ref": "#foo" - } - ], - "definitions": { - "A": { - "$id": "#foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with absolute URI", - "schema": { - "allOf": [ - { - "$ref": "http://localhost:1234/bar#foo" - } - ], - "definitions": { - "A": { - "$id": "http://localhost:1234/bar#foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with base URI change in subschema", - "schema": { - "$id": "http://localhost:1234/root", - "allOf": [ - { - "$ref": "http://localhost:1234/nested.json#foo" - } - ], - "definitions": { - "A": { - "$id": "nested.json", - "definitions": { - "B": { - "$id": "#foo", - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/refRemote.json b/src/test/resources/draft7/refRemote.json deleted file mode 100644 index 19dd7c82d..000000000 --- a/src/test/resources/draft7/refRemote.json +++ /dev/null @@ -1,166 +0,0 @@ -[ - { - "description": "remote ref", - "schema": { - "$ref": "http://localhost:1234/integer.json" - }, - "tests": [ - { - "description": "remote ref valid", - "data": 1, - "valid": true - }, - { - "description": "remote ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "fragment within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/integer" - }, - "tests": [ - { - "description": "remote fragment valid", - "data": 1, - "valid": true - }, - { - "description": "remote fragment invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "ref within remote ref", - "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" - }, - "tests": [ - { - "description": "ref within ref valid", - "data": 1, - "valid": true - }, - { - "description": "ref within ref invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "base URI change", - "schema": { - "$id": "http://localhost:1234/", - "items": { - "$id": "folder/", - "items": { - "$ref": "folderInteger.json" - } - } - }, - "tests": [ - { - "description": "base URI change ref valid", - "data": [ - [ - 1 - ] - ], - "valid": true - }, - { - "description": "base URI change ref invalid", - "data": [ - [ - "a" - ] - ], - "valid": false - } - ] - }, - { - "description": "base URI change - change folder", - "schema": { - "$id": "http://localhost:1234/scope_change_defs1.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/definitions/baz" - } - }, - "definitions": { - "baz": { - "$id": "folder/", - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - }, - { - "description": "root ref in remote ref", - "schema": { - "$id": "http://localhost:1234/object", - "type": "object", - "properties": { - "name": { - "$ref": "name.json#/definitions/orNull" - } - } - }, - "tests": [ - { - "description": "string is valid", - "data": { - "name": "foo" - }, - "valid": true - }, - { - "description": "null is valid", - "data": { - "name": null - }, - "valid": true - }, - { - "description": "object is invalid", - "data": { - "name": { - "name": null - } - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/refRemote_ignored.json b/src/test/resources/draft7/refRemote_ignored.json deleted file mode 100644 index 4bd19f7f5..000000000 --- a/src/test/resources/draft7/refRemote_ignored.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "description": "base URI change - change folder in subschema", - "schema": { - "$id": "http://localhost:1234/scope_change_defs2.json", - "type": "object", - "properties": { - "list": { - "$ref": "#/definitions/baz/definitions/bar" - } - }, - "definitions": { - "baz": { - "$id": "folder/", - "definitions": { - "bar": { - "type": "array", - "items": { - "$ref": "folderInteger.json" - } - } - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": { - "list": [ - 1 - ] - }, - "valid": true - }, - { - "description": "string is invalid", - "data": { - "list": [ - "a" - ] - }, - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/src/test/resources/draft7/required.json b/src/test/resources/draft7/required.json deleted file mode 100644 index 6358d4e1f..000000000 --- a/src/test/resources/draft7/required.json +++ /dev/null @@ -1,111 +0,0 @@ -[ - { - "description": "required validation", - "schema": { - "properties": { - "foo": {}, - "bar": {} - }, - "required": [ - "foo" - ] - }, - "tests": [ - { - "description": "present required property is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "non-present required property is invalid", - "data": { - "bar": 1 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [], - "valid": true - }, - { - "description": "ignores strings", - "data": "", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "required default validation", - "schema": { - "properties": { - "foo": {} - } - }, - "tests": [ - { - "description": "not required by default", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with empty array", - "schema": { - "properties": { - "foo": {} - }, - "required": [] - }, - "tests": [ - { - "description": "property not required", - "data": {}, - "valid": true - } - ] - }, - { - "description": "required with escaped characters", - "schema": { - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ] - }, - "tests": [ - { - "description": "object with all properties present is valid", - "data": { - "foo\nbar": 1, - "foo\"bar": 1, - "foo\\bar": 1, - "foo\rbar": 1, - "foo\tbar": 1, - "foo\fbar": 1 - }, - "valid": true - }, - { - "description": "object with some properties missing is invalid", - "data": { - "foo\nbar": "1", - "foo\"bar": "1" - }, - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/type.json b/src/test/resources/draft7/type.json deleted file mode 100644 index 83348a468..000000000 --- a/src/test/resources/draft7/type.json +++ /dev/null @@ -1,504 +0,0 @@ -[ - { - "description": "integer type matches integers", - "schema": { - "type": "integer" - }, - "tests": [ - { - "description": "an integer is an integer", - "data": 1, - "valid": true - }, - { - "description": "a float is not an integer", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an integer", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not an integer, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not an integer", - "data": {}, - "valid": false - }, - { - "description": "an array is not an integer", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an integer", - "data": true, - "valid": false - }, - { - "description": "null is not an integer", - "data": null, - "valid": false - } - ] - }, - { - "description": "number type matches numbers", - "schema": { - "type": "number" - }, - "tests": [ - { - "description": "an integer is a number", - "data": 1, - "valid": true - }, - { - "description": "a float is a number", - "data": 1.1, - "valid": true - }, - { - "description": "a string is not a number", - "data": "foo", - "valid": false - }, - { - "description": "a string is still not a number, even if it looks like one", - "data": "1", - "valid": false - }, - { - "description": "an object is not a number", - "data": {}, - "valid": false - }, - { - "description": "an array is not a number", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a number", - "data": true, - "valid": false - }, - { - "description": "null is not a number", - "data": null, - "valid": false - } - ] - }, - { - "description": "string type matches strings", - "schema": { - "type": "string" - }, - "tests": [ - { - "description": "1 is not a string", - "data": 1, - "valid": false - }, - { - "description": "a float is not a string", - "data": 1.1, - "valid": false - }, - { - "description": "a string is a string", - "data": "foo", - "valid": true - }, - { - "description": "a string is still a string, even if it looks like a number", - "data": "1", - "valid": true - }, - { - "description": "an empty string is still a string", - "data": "", - "valid": true - }, - { - "description": "an object is not a string", - "data": {}, - "valid": false - }, - { - "description": "an array is not a string", - "data": [], - "valid": false - }, - { - "description": "a boolean is not a string", - "data": true, - "valid": false - }, - { - "description": "null is not a string", - "data": null, - "valid": false - } - ] - }, - { - "description": "object type matches objects", - "schema": { - "type": "object" - }, - "tests": [ - { - "description": "an integer is not an object", - "data": 1, - "valid": false - }, - { - "description": "a float is not an object", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an object", - "data": "foo", - "valid": false - }, - { - "description": "an object is an object", - "data": {}, - "valid": true - }, - { - "description": "an array is not an object", - "data": [], - "valid": false - }, - { - "description": "a boolean is not an object", - "data": true, - "valid": false - }, - { - "description": "null is not an object", - "data": null, - "valid": false - } - ] - }, - { - "description": "array type matches arrays", - "schema": { - "type": "array" - }, - "tests": [ - { - "description": "an integer is not an array", - "data": 1, - "valid": false - }, - { - "description": "a float is not an array", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not an array", - "data": "foo", - "valid": false - }, - { - "description": "an object is not an array", - "data": {}, - "valid": false - }, - { - "description": "an array is an array", - "data": [], - "valid": true - }, - { - "description": "a boolean is not an array", - "data": true, - "valid": false - }, - { - "description": "null is not an array", - "data": null, - "valid": false - } - ] - }, - { - "description": "boolean type matches booleans", - "schema": { - "type": "boolean" - }, - "tests": [ - { - "description": "an integer is not a boolean", - "data": 1, - "valid": false - }, - { - "description": "zero is not a boolean", - "data": 0, - "valid": false - }, - { - "description": "a float is not a boolean", - "data": 1.1, - "valid": false - }, - { - "description": "a string is not a boolean", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not a boolean", - "data": "", - "valid": false - }, - { - "description": "an object is not a boolean", - "data": {}, - "valid": false - }, - { - "description": "an array is not a boolean", - "data": [], - "valid": false - }, - { - "description": "true is a boolean", - "data": true, - "valid": true - }, - { - "description": "false is a boolean", - "data": false, - "valid": true - }, - { - "description": "null is not a boolean", - "data": null, - "valid": false - } - ] - }, - { - "description": "null type matches only the null object", - "schema": { - "type": "null" - }, - "tests": [ - { - "description": "an integer is not null", - "data": 1, - "valid": false - }, - { - "description": "a float is not null", - "data": 1.1, - "valid": false - }, - { - "description": "zero is not null", - "data": 0, - "valid": false - }, - { - "description": "a string is not null", - "data": "foo", - "valid": false - }, - { - "description": "an empty string is not null", - "data": "", - "valid": false - }, - { - "description": "an object is not null", - "data": {}, - "valid": false - }, - { - "description": "an array is not null", - "data": [], - "valid": false - }, - { - "description": "true is not null", - "data": true, - "valid": false - }, - { - "description": "false is not null", - "data": false, - "valid": false - }, - { - "description": "null is null", - "data": null, - "valid": true - } - ] - }, - { - "description": "multiple types can be specified in an array", - "schema": { - "type": [ - "integer", - "string" - ] - }, - "tests": [ - { - "description": "an integer is valid", - "data": 1, - "valid": true - }, - { - "description": "a string is valid", - "data": "foo", - "valid": true - }, - { - "description": "a float is invalid", - "data": 1.1, - "valid": false - }, - { - "description": "an object is invalid", - "data": {}, - "valid": false - }, - { - "description": "an array is invalid", - "data": [], - "valid": false - }, - { - "description": "a boolean is invalid", - "data": true, - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type as array with one item", - "schema": { - "type": [ - "string" - ] - }, - "tests": [ - { - "description": "string is valid", - "data": "foo", - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - } - ] - }, - { - "description": "type: array or object", - "schema": { - "type": [ - "array", - "object" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - }, - { - "description": "null is invalid", - "data": null, - "valid": false - } - ] - }, - { - "description": "type: array, object or null", - "schema": { - "type": [ - "array", - "object", - "null" - ] - }, - "tests": [ - { - "description": "array is valid", - "data": [ - 1, - 2, - 3 - ], - "valid": true - }, - { - "description": "object is valid", - "data": { - "foo": 123 - }, - "valid": true - }, - { - "description": "null is valid", - "data": null, - "valid": true - }, - { - "description": "number is invalid", - "data": 123, - "valid": false - }, - { - "description": "string is invalid", - "data": "foo", - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/uniqueItems.json b/src/test/resources/draft7/uniqueItems.json deleted file mode 100644 index c64cdbbda..000000000 --- a/src/test/resources/draft7/uniqueItems.json +++ /dev/null @@ -1,328 +0,0 @@ -[ - { - "description": "uniqueItems validation", - "schema": { - "uniqueItems": true - }, - "tests": [ - { - "description": "unique array of integers is valid", - "data": [ - 1, - 2 - ], - "valid": true - }, - { - "description": "non-unique array of integers is invalid", - "data": [ - 1, - 1 - ], - "valid": false - }, - { - "description": "numbers are unique if mathematically unequal", - "data": [ - 1.0, - 1.00, - 1 - ], - "valid": false - }, - { - "description": "false is not equal to zero", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "true is not equal to one", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "unique array of objects is valid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "baz" - } - ], - "valid": true - }, - { - "description": "non-unique array of objects is invalid", - "data": [ - { - "foo": "bar" - }, - { - "foo": "bar" - } - ], - "valid": false - }, - { - "description": "unique array of nested objects is valid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": false - } - } - } - ], - "valid": true - }, - { - "description": "non-unique array of nested objects is invalid", - "data": [ - { - "foo": { - "bar": { - "baz": true - } - } - }, - { - "foo": { - "bar": { - "baz": true - } - } - } - ], - "valid": false - }, - { - "description": "unique array of arrays is valid", - "data": [ - [ - "foo" - ], - [ - "bar" - ] - ], - "valid": true - }, - { - "description": "non-unique array of arrays is invalid", - "data": [ - [ - "foo" - ], - [ - "foo" - ] - ], - "valid": false - }, - { - "description": "1 and true are unique", - "data": [ - 1, - true - ], - "valid": true - }, - { - "description": "0 and false are unique", - "data": [ - 0, - false - ], - "valid": true - }, - { - "description": "unique heterogeneous types are valid", - "data": [ - {}, - [ - 1 - ], - true, - null, - 1 - ], - "valid": true - }, - { - "description": "non-unique heterogeneous types are invalid", - "data": [ - {}, - [ - 1 - ], - true, - null, - {}, - 1 - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "unique array extended from [false, true] is valid", - "data": [ - false, - true, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "unique array extended from [true, false] is valid", - "data": [ - true, - false, - "foo", - "bar" - ], - "valid": true - }, - { - "description": "non-unique array extended from [false, true] is not valid", - "data": [ - false, - true, - "foo", - "foo" - ], - "valid": false - }, - { - "description": "non-unique array extended from [true, false] is not valid", - "data": [ - true, - false, - "foo", - "foo" - ], - "valid": false - } - ] - }, - { - "description": "uniqueItems with an array of items and additionalItems=false", - "schema": { - "items": [ - { - "type": "boolean" - }, - { - "type": "boolean" - } - ], - "uniqueItems": true, - "additionalItems": false - }, - "tests": [ - { - "description": "[false, true] from items array is valid", - "data": [ - false, - true - ], - "valid": true - }, - { - "description": "[true, false] from items array is valid", - "data": [ - true, - false - ], - "valid": true - }, - { - "description": "[false, false] from items array is not valid", - "data": [ - false, - false - ], - "valid": false - }, - { - "description": "[true, true] from items array is not valid", - "data": [ - true, - true - ], - "valid": false - }, - { - "description": "extra items are invalid even if unique", - "data": [ - false, - true, - null - ], - "valid": false - } - ] - } -] diff --git a/src/test/resources/draft7/urn/issue665.json b/src/test/resources/draft7/urn/issue665.json new file mode 100644 index 000000000..57a3bf884 --- /dev/null +++ b/src/test/resources/draft7/urn/issue665.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "myData" + ], + "properties": { + "myData": { + "$ref": "urn:data" + } + }, + "definitions": { + "data": { + "$id": "urn:data", + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + } + } +} diff --git a/src/test/resources/draft7/urn/issue665_external_urn_ref.json b/src/test/resources/draft7/urn/issue665_external_urn_ref.json new file mode 100644 index 000000000..2929686b0 --- /dev/null +++ b/src/test/resources/draft7/urn/issue665_external_urn_ref.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "myData" + ], + "properties": { + "myData": { + "$ref": "urn:data" + } + } +} diff --git a/src/test/resources/draft7/urn/issue665_external_urn_subschema.json b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json new file mode 100644 index 000000000..026f879aa --- /dev/null +++ b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "urn:data", + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } +} diff --git a/src/test/resources/issue686/translations.properties b/src/test/resources/issue686/translations.properties new file mode 100644 index 000000000..b57350150 --- /dev/null +++ b/src/test/resources/issue686/translations.properties @@ -0,0 +1 @@ +type = {0} found, {1} expected (TEST) diff --git a/src/test/resources/issue686/translations_de.properties b/src/test/resources/issue686/translations_de.properties new file mode 100644 index 000000000..5db69d10b --- /dev/null +++ b/src/test/resources/issue686/translations_de.properties @@ -0,0 +1 @@ +type = {0} found, {1} expected (TEST) (DE) diff --git a/src/test/resources/issue686/translations_fr.properties b/src/test/resources/issue686/translations_fr.properties new file mode 100644 index 000000000..518a44aef --- /dev/null +++ b/src/test/resources/issue686/translations_fr.properties @@ -0,0 +1 @@ +type = {0} found, {1} expected (TEST) (FR) diff --git a/src/test/resources/issue784/schema.json b/src/test/resources/issue784/schema.json new file mode 100644 index 000000000..38d5a2de8 --- /dev/null +++ b/src/test/resources/issue784/schema.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "my-date-time": { + "type": "string", + "format": "date-time" + } + } +} diff --git a/src/test/resources/issues/1016/schema.json b/src/test/resources/issues/1016/schema.json new file mode 100644 index 000000000..0d8fb1afd --- /dev/null +++ b/src/test/resources/issues/1016/schema.json @@ -0,0 +1,11059 @@ +{ + "$schema" : "http://json-schema.org/draft-07/schema#", + "type" : "object", + "definitions" : { + "var" : { + "type" : "object", + "properties" : { + "@var" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "@var" ], + "defaultSnippets" : [ { + "description" : "Variable reference", + "body" : { + "@var" : "$0" + } + } ] + }, + "comment" : { + "oneOf" : [ { + "type" : "string" + }, { + "type" : "array", + "items" : { + "type" : "string" + } + } ] + }, + "group" : { + "oneOf" : [ { + "oneOf" : [ { + "type" : "string" + }, { + "$ref" : "#/definitions/var" + } ] + }, { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string" + } + }, { + "$ref" : "#/definitions/var" + } ] + } ] + }, + "threads" : { + "oneOf" : [ { + "type" : "string", + "pattern" : "^(\\d+|\\d+-\\d+|[aA][uU][tT][oO])$", + "defaultSnippets" : [ { + "description" : "Let Lingo4G automatically and dynamically adjust the number of threads for this task to minimize latency.", + "body" : "auto" + }, { + "description" : "Let Lingo4G automatically and dynamically adjust the number of threads for this task within the range you provide.", + "body" : "4-8" + }, { + "description" : "Use a fixed number of threads (exactly one).", + "body" : "1" + } ] + }, { + "type" : "integer", + "minimum" : 1 + } ] + }, + "weightAggregation" : { + "type" : "string", + "enum" : [ "MIN", "MAX", "COUNT", "SUM", "PRODUCT", "MEAN", "GEOMETRIC_MEAN" ] + }, + "sortOrder" : { + "type" : "string", + "enum" : [ "ASCENDING", "DESCENDING", "UNSPECIFIED" ] + }, + "fields:simple" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "fields:simple" + }, + "fields" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "fld_double", "fld_double_a", "fld_int", "fld_int_a", "fld_mv", "id", "summary", "summary$phrases", "title", "title$phrases" ] + }, + "examples" : [ [ "fld_double", "fld_double_a", "fld_int", "fld_int_a", "fld_mv", "id", "summary", "summary$phrases", "title", "title$phrases" ] ], + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "examples" : [ { + "type" : "fields:simple", + "fields" : [ "fld_double", "fld_double_a", "fld_int", "fld_int_a", "fld_mv", "id", "summary", "summary$phrases", "title", "title$phrases" ] + } ], + "required" : [ "type", "fields" ], + "default" : { + "type" : "fields:simple", + "fields" : [ ] + } + }, + "fields" : { + "oneOf" : [ { + "$ref" : "#/definitions/fields:reference" + }, { + "$ref" : "#/definitions/fields:simple" + } ] + }, + "featureFields:simple" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureFields:simple" + }, + "fields" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "summary$phrases", "title$phrases" ] + }, + "examples" : [ [ "summary$phrases", "title$phrases" ] ], + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "examples" : [ { + "type" : "featureFields:simple", + "fields" : [ "summary$phrases", "title$phrases" ] + } ], + "required" : [ "type", "fields" ], + "default" : { + "type" : "featureFields:simple", + "fields" : [ ] + } + }, + "featureFields" : { + "oneOf" : [ { + "$ref" : "#/definitions/featureFields:reference" + }, { + "$ref" : "#/definitions/featureFields:simple" + } ] + }, + "contentField" : { + "type" : "object", + "properties" : { + "maxValues" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 3 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxValueLength" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 250 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "truncationMarker" : { + "oneOf" : [ { + "type" : "string", + "default" : "…" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "valueCount" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "highlighting" : { + "type" : "object", + "properties" : { + "enabled" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "startMarker" : { + "oneOf" : [ { + "type" : "string", + "default" : "⁌%s⁍" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "endMarker" : { + "oneOf" : [ { + "type" : "string", + "default" : "⁌\\%s⁍" + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "additionalProperties" : false, + "default" : { + "enabled" : true, + "startMarker" : "⁌%s⁍", + "endMarker" : "⁌\\%s⁍" + } + } + }, + "additionalProperties" : false, + "defaultSnippets" : [ { + "body" : { + "maxValues" : 3, + "maxValueLength" : 160 + } + }, { + "description" : "Field without query highlighting.", + "body" : { + "maxValues" : 3, + "maxValueLength" : 160, + "highlighting" : { + "enabled" : false + } + } + } ], + "default" : { + "maxValues" : 3, + "maxValueLength" : 250, + "truncationMarker" : "…", + "valueCount" : false, + "highlighting" : { + "enabled" : true, + "startMarker" : "⁌%s⁍", + "endMarker" : "⁌\\%s⁍" + } + } + }, + "contentFields:simple" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "contentFields:simple" + }, + "fields" : { + "type" : "object", + "additionalProperties" : false, + "examples" : [ { + "fld_double" : { }, + "fld_double_a" : { }, + "fld_int" : { }, + "fld_int_a" : { }, + "fld_mv" : { }, + "id" : { }, + "summary" : { }, + "title" : { } + } ], + "properties" : { + "fld_double" : { + "$ref" : "#/definitions/contentField" + }, + "fld_double_a" : { + "$ref" : "#/definitions/contentField" + }, + "fld_int" : { + "$ref" : "#/definitions/contentField" + }, + "fld_int_a" : { + "$ref" : "#/definitions/contentField" + }, + "fld_mv" : { + "$ref" : "#/definitions/contentField" + }, + "id" : { + "$ref" : "#/definitions/contentField" + }, + "summary" : { + "$ref" : "#/definitions/contentField" + }, + "title" : { + "$ref" : "#/definitions/contentField" + } + }, + "default" : { } + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "examples" : [ { + "type" : "contentFields:simple", + "fields" : { + "fld_double" : { }, + "fld_double_a" : { }, + "fld_int" : { }, + "fld_int_a" : { }, + "fld_mv" : { }, + "id" : { }, + "summary" : { }, + "title" : { } + } + } ], + "required" : [ "type", "fields" ], + "default" : { + "type" : "contentFields:simple", + "fields" : { } + } + }, + "contentFields:grouped" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "contentFields:grouped" + }, + "groups" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "fields" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "summary", "fld_double_a", "fld_mv", "fld_int", "fld_int_a", "id", "fld_double", "title" ] + } + }, { + "$ref" : "#/definitions/var" + } ] + }, + "config" : { + "$ref" : "#/definitions/contentField" + } + }, + "required" : [ "fields" ] + }, + "examples" : [ [ { + "fields" : [ "summary", "fld_double_a", "fld_mv", "fld_int", "fld_int_a", "id", "fld_double", "title" ], + "config" : { } + } ] ], + "default" : [ ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "examples" : [ { + "type" : "contentFields:grouped", + "groups" : [ ] + } ], + "required" : [ "type", "groups" ], + "default" : { + "type" : "contentFields:grouped", + "groups" : [ ] + } + }, + "contentFields:empty" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "contentFields:empty" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "default" : { + "type" : "contentFields:empty" + } + }, + "contentFields" : { + "oneOf" : [ { + "$ref" : "#/definitions/contentFields:reference" + }, { + "$ref" : "#/definitions/contentFields:simple" + }, { + "$ref" : "#/definitions/contentFields:grouped" + }, { + "$ref" : "#/definitions/contentFields:empty" + } ] + }, + "dictionary:glob" : { + "type" : "object", + "description" : "Word and wildcard-based label filtering dictionary.", + "properties" : { + "type" : { + "const" : "dictionary:glob" + }, + "entries" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "defaultSnippets" : [ { + "description" : "Matches labels equal to 'word', case insensitive.", + "body" : "${1:word}" + }, { + "description" : "Matches labels equal to 'Word', case sensitive.", + "body" : "\"${1:Word}\"" + }, { + "description" : "Matches labels starting with or equal to 'including'.", + "body" : "${1:including} *" + }, { + "description" : "Matches labels ending with or equal to 'respectively'", + "body" : "* ${1:respectively}" + }, { + "description" : "Matches labels containing or equal to 'results'", + "body" : "* ${1:results} *" + } ] + }, + "defaultSnippets" : [ { + "description" : "Matches labels equal to 'project' and labels containing the word 'results'.", + "body" : [ "project", "* results *" ] + } ], + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "entries" ], + "defaultSnippets" : [ { + "description" : "Simple dictionary with an empty list of rules.", + "body" : { + "type" : "dictionary:glob", + "entries" : "^[ $0]" + } + }, { + "description" : "Matches labels equal to 'project' and labels containing the word 'results'.", + "body" : { + "type" : "dictionary:glob", + "entries" : [ "project", "* results *" ] + } + } ], + "default" : { + "type" : "dictionary:glob", + "entries" : [ ] + } + }, + "dictionary:regex" : { + "type" : "object", + "description" : "Regular expression based label filtering dictionary.", + "properties" : { + "type" : { + "const" : "dictionary:regex" + }, + "entries" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "defaultSnippets" : [ { + "description" : "Matches labels containing the word 'low' or 'high', case sensitive.", + "body" : ".*(low|high).*" + }, { + "description" : "Matches labels containing the word 'low' or 'high', case insensitive.", + "body" : "(i).*(low|high).*" + }, { + "description" : "Matches labels starting with one or more digits.", + "body" : "\\d+.*" + } ] + }, + "defaultSnippets" : [ { + "description" : "Regex dictionary with an empty list of rules.", + "body" : "^[ $0]" + }, { + "description" : "Matches labels containing the words 'low' or 'high'.", + "body" : [ ".*(low|high).*" ] + } ], + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "entries" ], + "defaultSnippets" : [ { + "description" : "Regex dictionary with an empty list of rules.", + "body" : { + "type" : "dictionary:regex", + "entries" : "^[ $0]" + } + }, { + "description" : "Matches labels containing the words 'low' or 'high'.", + "body" : { + "type" : "dictionary:regex", + "entries" : [ ".*(low|high).*" ] + } + } ], + "default" : { + "type" : "dictionary:regex", + "entries" : [ ] + } + }, + "dictionary:project" : { + "type" : "object", + "description" : "A reference to a dictionary declared in the project descriptor.", + "properties" : { + "type" : { + "const" : "dictionary:project" + }, + "dictionary" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "defaultSimple", "defaultRegex", "test_dict_1" ], + "default" : null + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "dictionary" ], + "default" : { + "type" : "dictionary:project", + "dictionary" : null + } + }, + "dictionary:all" : { + "type" : "object", + "description" : "All dictionaries declared in the project descriptor, combined.", + "properties" : { + "type" : { + "const" : "dictionary:all" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "default" : { + "type" : "dictionary:all" + } + }, + "dictionary:queryTerms" : { + "type" : "object", + "description" : "A dictionary created dynamically from query terms (if they are available).", + "properties" : { + "type" : { + "const" : "dictionary:queryTerms" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "query" ], + "default" : { + "type" : "dictionary:queryTerms", + "query" : null + } + }, + "dictionary" : { + "oneOf" : [ { + "$ref" : "#/definitions/dictionary:reference" + }, { + "$ref" : "#/definitions/dictionary:glob" + }, { + "$ref" : "#/definitions/dictionary:regex" + }, { + "$ref" : "#/definitions/dictionary:project" + }, { + "$ref" : "#/definitions/dictionary:all" + }, { + "$ref" : "#/definitions/dictionary:queryTerms" + } ] + }, + "labelFilter:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:composite" + }, + "labelFilters" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/labelFilter" + }, + "defaultSnippets" : [ { + "body" : { + "${1:filterName}" : "^$0" + } + } ], + "default" : { } + }, + "operator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "AND", + "description" : "The operator to use to combine label filters.\n\nAND will create a composite conjunction filter, OR will create a disjunction filter." + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labelFilters" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelFilter:composite", + "labelFilters" : { + "${1:filterName}" : "^$0" + }, + "operator" : "AND" + } + } ], + "default" : { + "type" : "labelFilter:composite", + "labelFilters" : { }, + "operator" : "AND" + } + }, + "labelFilter:complement" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:complement" + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labelFilter" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelFilter:complement", + "filter" : "^$0" + } + } ], + "default" : { + "type" : "labelFilter:complement", + "labelFilter" : null + } + }, + "labelFilter:acceptAll" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:acceptAll" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "description" : "Accepts all labels.", + "body" : { + "type" : "labelFilter:acceptAll" + } + } ], + "default" : { + "type" : "labelFilter:acceptAll" + } + }, + "labelFilter:acceptLabels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:acceptLabels" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "description" : "Accepts labels from the provided list.", + "body" : { + "type" : "labelFilter:acceptLabels", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "labelFilter:acceptLabels", + "labels" : { + "type" : "labels:reference", + "auto" : true + } + } + }, + "labelFilter:rejectLabels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:rejectLabels" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "description" : "Rejects labels from the provided list.", + "body" : { + "type" : "labelFilter:rejectLabels", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "labelFilter:rejectLabels", + "labels" : { + "type" : "labels:reference", + "auto" : true + } + } + }, + "labelFilter:autoStopLabels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:autoStopLabels" + }, + "minCoverage" : { + "oneOf" : [ { + "type" : "number", + "defaultSnippets" : [ { + "label" : "1.0: removes only the most popular stop labels", + "description" : "Removes only the stop labels that appeared in 100% of the documents sampled during automatic stop label discovery. Use this setting to avoid removing meaningful labels at the cost of some stop labels passing through the filter.", + "body" : 1.0 + }, { + "label" : "0.8: removes popular stop labels (recommended value)", + "body" : 0.8 + }, { + "label" : "0.6: removes most auto-discovered stop labels", + "body" : 0.8 + }, { + "label" : "0.0: removes all auto-discovered stop labels", + "body" : 0.0 + } ], + "default" : 0.4, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "removalStrength" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.35, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "description" : "Removes the stop labels that appeared in at least 80% of the documents sampled during automatic stop label discovery.\n\nThis is the recommended configuration of this filter.", + "body" : { + "type" : "labelFilter:autoStopLabels", + "minCoverage" : 0.8 + } + }, { + "description" : "Removes only the stop labels that appeared in 100% of the documents sampled during automatic stop label discovery.\n\nUse this setting to avoid removing meaningful labels at the cost of some stop labels passing through the filter.", + "body" : { + "type" : "labelFilter:autoStopLabels", + "minCoverage" : 1.0 + } + } ], + "default" : { + "type" : "labelFilter:autoStopLabels", + "minCoverage" : 0.4, + "removalStrength" : 0.35 + } + }, + "labelFilter:tokenCount" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:tokenCount" + }, + "minTokens" : { + "oneOf" : [ { + "type" : "integer", + "default" : 3, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxTokens" : { + "oneOf" : [ { + "type" : "integer", + "default" : 8, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "description" : "Keeps labels that are between 2 and 5 words long.", + "body" : { + "type" : "labelFilter:tokenCount", + "minTokens" : "^${1:2}", + "maxTokens" : "^${2:5}" + } + }, { + "description" : "Removes one-word labels.", + "body" : { + "type" : "labelFilter:tokenCount", + "minTokens" : 2 + } + }, { + "description" : "Removes labels longer than 4 words.", + "body" : { + "type" : "labelFilter:tokenCount", + "maxTokens" : 4 + } + } ], + "default" : { + "type" : "labelFilter:tokenCount", + "minTokens" : 3, + "maxTokens" : 8 + } + }, + "labelFilter:characterCount" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:characterCount" + }, + "minCharacters" : { + "oneOf" : [ { + "type" : "number", + "default" : 4, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minCharactersAveragePerToken" : { + "oneOf" : [ { + "type" : "number", + "default" : 2.9, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "description" : "Keeps labels that are 4 or more characters long, including spaces.", + "body" : { + "type" : "labelFilter:characterCount", + "minCharacters" : "^${1:4}" + } + }, { + "description" : "Keeps labels whose average word length is 2.8 and larger. You can use this filter to remove labels consisting of very short words.", + "body" : { + "type" : "labelFilter:characterCount", + "minCharactersAveragePerToken" : "^${1:2.8}" + } + } ], + "default" : { + "type" : "labelFilter:characterCount", + "minCharacters" : 4, + "minCharactersAveragePerToken" : 2.9 + } + }, + "labelFilter:surface" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:surface" + }, + "removeCapitalized" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "removeUppercase" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "removeAcronyms" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "description" : "Removes 'Capitalized', 'UPPERCASE' and 'AcRoNYM' labels.", + "body" : { + "type" : "labelFilter:surface", + "removeCapitalized" : "^${1:true}", + "removeUppercase" : "^${2:true}", + "removeAcronyms" : "^${3:true}" + } + } ], + "default" : { + "type" : "labelFilter:surface", + "removeCapitalized" : false, + "removeUppercase" : false, + "removeAcronyms" : false + } + }, + "labelFilter:dictionary" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:dictionary" + }, + "exclude" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/dictionary" + }, + "default" : [ ] + }, + "addAllProjectDictionaries" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "exclude" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelFilter:dictionary", + "exclude" : [ "^$0" ] + } + } ], + "default" : { + "type" : "labelFilter:dictionary", + "exclude" : [ ], + "addAllProjectDictionaries" : true + } + }, + "labelFilter:hasEmbedding" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:hasEmbedding" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelFilter:hasEmbedding" + } + } ], + "default" : { + "type" : "labelFilter:hasEmbedding" + } + }, + "labelFilter" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelFilter:reference" + }, { + "$ref" : "#/definitions/labelFilter:composite" + }, { + "$ref" : "#/definitions/labelFilter:complement" + }, { + "$ref" : "#/definitions/labelFilter:tokenCount" + }, { + "$ref" : "#/definitions/labelFilter:characterCount" + }, { + "$ref" : "#/definitions/labelFilter:surface" + }, { + "$ref" : "#/definitions/labelFilter:autoStopLabels" + }, { + "$ref" : "#/definitions/labelFilter:dictionary" + }, { + "$ref" : "#/definitions/labelFilter:hasEmbedding" + }, { + "$ref" : "#/definitions/labelFilter:acceptAll" + }, { + "$ref" : "#/definitions/labelFilter:acceptLabels" + }, { + "$ref" : "#/definitions/labelFilter:rejectLabels" + } ] + }, + "labelListFilter:acceptAll" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelListFilter:acceptAll" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelListFilter:acceptAll" + } + } ], + "default" : { + "type" : "labelListFilter:acceptAll" + } + }, + "labelListFilter:truncatedPhrases" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelListFilter:truncatedPhrases" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelListFilter:truncatedPhrases" + } + } ], + "default" : { + "type" : "labelListFilter:truncatedPhrases" + } + }, + "labelListFilter" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelListFilter:reference" + }, { + "$ref" : "#/definitions/labelListFilter:acceptAll" + }, { + "$ref" : "#/definitions/labelListFilter:truncatedPhrases" + } ] + }, + "labelScorer:identity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:identity" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:identity" + } + } ], + "default" : { + "type" : "labelScorer:identity" + } + }, + "labelScorer:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:composite" + }, + "scorers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/labelScorer" + }, + "default" : [ ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "scorers" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:composite", + "scorers" : [ "^$0" ] + } + } ], + "default" : { + "type" : "labelScorer:composite", + "scorers" : [ ] + } + }, + "labelScorer:df" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:df" + }, + "scope" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "scope" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:df" + } + } ], + "default" : { + "type" : "labelScorer:df", + "scope" : { + "type" : "documents:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + } + } + }, + "labelScorer:idf" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:idf" + }, + "scope" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "scope" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:idf" + } + } ], + "default" : { + "type" : "labelScorer:idf", + "scope" : { + "type" : "documents:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + } + } + }, + "labelScorer:tf" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:tf" + }, + "scope" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "scope" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:tf" + } + } ], + "default" : { + "type" : "labelScorer:tf", + "scope" : { + "type" : "documents:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + } + } + }, + "labelScorer:probabilityRatio" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelScorer:probabilityRatio" + }, + "baseScope" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "referenceScope" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:sample", + "limit" : 10000, + "query" : { + "type" : "query:all" + }, + "randomSeed" : 0, + "samplingRatio" : 1.0 + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "baseScope", "fields" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelScorer:probabilityRatio" + } + } ], + "default" : { + "type" : "labelScorer:probabilityRatio", + "baseScope" : { + "type" : "documents:reference", + "auto" : true + }, + "referenceScope" : { + "type" : "documents:sample", + "limit" : 10000, + "query" : { + "type" : "query:all" + }, + "randomSeed" : 0, + "samplingRatio" : 1.0 + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + } + } + }, + "labelScorer" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelScorer:identity" + }, { + "$ref" : "#/definitions/labelScorer:composite" + }, { + "$ref" : "#/definitions/labelScorer:df" + }, { + "$ref" : "#/definitions/labelScorer:idf" + }, { + "$ref" : "#/definitions/labelScorer:tf" + }, { + "$ref" : "#/definitions/labelScorer:probabilityRatio" + } ] + }, + "labelCollector:topFromFeatureFields" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelCollector:topFromFeatureFields", + "description" : "Collects labels occurring in the provided documents." + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : { + "type" : "labelFilter:reference", + "auto" : true + } + } ] + }, + "labelListFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelListFilter" + }, { + "default" : { + "type" : "labelListFilter:truncatedPhrases" + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "minTf" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minTfMass" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.0, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "tieResolution" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "TRUNCATE", "EXTEND", "REDUCE", "AUTO" ], + "default" : "AUTO" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "defaultSnippets" : [ { + "body" : { + "type" : "labelCollector:topFromFeatureFields" + } + } ], + "default" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + } + }, + "labelCollector" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelCollector:reference" + }, { + "$ref" : "#/definitions/labelCollector:topFromFeatureFields" + } ] + }, + "labelAggregator:topWeight" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelAggregator:topWeight" + }, + "labelCollector" : { + "allOf" : [ { + "$ref" : "#/definitions/labelCollector" + }, { + "default" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + } + } ] + }, + "maxLabelsPerDocument" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minAbsoluteDf" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minRelativeDf" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.0, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxRelativeDf" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.0, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minWeight" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.0, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "tieResolution" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "TRUNCATE", "EXTEND", "REDUCE", "AUTO" ], + "default" : "AUTO" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "outputWeightFormula" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "TF", "DF" ], + "default" : "TF" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelAggregator:topWeight" + } + } ], + "default" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabelsPerDocument" : 10, + "minAbsoluteDf" : 1, + "minRelativeDf" : 0.0, + "maxRelativeDf" : 1.0, + "minWeight" : 0.0, + "tieResolution" : "AUTO", + "outputWeightFormula" : "TF", + "threads" : "auto" + } + }, + "labelAggregator" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelAggregator:topWeight" + } ] + }, + "labelCount" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelCount:reference" + }, { + "$ref" : "#/definitions/labelCount:fixed" + }, { + "$ref" : "#/definitions/labelCount:unlimited" + }, { + "$ref" : "#/definitions/labelCount:progressive" + } ] + }, + "labelCount:fixed" : { + "type" : "object", + "description" : "An explicit, hard limit of the number of labels that is not document-count sensitive.", + "properties" : { + "type" : { + "const" : "labelCount:fixed" + }, + "value" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelCount:fixed", + "value" : "^$0" + } + } ], + "default" : { + "type" : "labelCount:fixed", + "value" : 10000 + } + }, + "labelCount:unlimited" : { + "type" : "object", + "description" : "No limit on the number of labels.", + "properties" : { + "type" : { + "const" : "labelCount:unlimited" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelCount:unlimited" + } + } ], + "default" : { + "type" : "labelCount:unlimited" + } + }, + "labelCount:progressive" : { + "type" : "object", + "description" : "A limit that scales with the number of documents, progressively.", + "properties" : { + "type" : { + "const" : "labelCount:progressive" + }, + "min" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "max" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "multiplier" : { + "oneOf" : [ { + "type" : "number", + "default" : 2.0, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "exponent" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.75, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelCount:progressive", + "multiplier" : "^$0" + } + } ], + "default" : { + "type" : "labelCount:progressive", + "max" : "unlimited", + "min" : 0, + "multiplier" : 2.0, + "exponent" : 0.75 + } + }, + "query:forFieldValues" : { + "type" : "object", + "description" : "A query that is defined by a set of documents with the provided field values.", + "properties" : { + "type" : { + "const" : "query:forFieldValues" + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/contentFields" + }, { + "default" : { + "type" : "contentFields:reference", + "auto" : true + } + } ] + }, + "values" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string" + }, + "description" : "An array of values to look for in the provided set of fields.", + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields", "values" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:forFieldValues", + "fields" : { + "type" : "contentFields:simple", + "fields" : { } + }, + "values" : [ ] + } + } ], + "default" : { + "type" : "query:forFieldValues", + "fields" : { + "type" : "contentFields:reference", + "auto" : true + }, + "values" : [ ] + } + }, + "query:forLabels" : { + "type" : "object", + "description" : "A query that is defined by a set of labels.", + "properties" : { + "type" : { + "const" : "query:forLabels" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "operator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "OR", + "description" : "The operator to use to combine labels.\n\nOR will select documents containing at least one of the provided labels. AND will select documents containing all of the provided labels." + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "minOrMatches" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1, + "exclusiveMinimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:forLabels", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "operator" : "OR", + "minOrMatches" : 1 + } + }, + "query:all" : { + "type" : "object", + "description" : "A query that matches all documents in the index.", + "properties" : { + "type" : { + "const" : "query:all" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:all" + } + } ], + "default" : { + "type" : "query:all" + } + }, + "query:string" : { + "type" : "object", + "description" : "A query that is defined by a text query string and a query parser that converts it to a Lucene Query.", + "properties" : { + "type" : { + "const" : "query:string" + }, + "query" : { + "oneOf" : [ { + "type" : "string", + "description" : "The query string to use\n\nThe query string must be valid with respect to the provided query parser.", + "default" : "" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "queryParser" : { + "allOf" : [ { + "$ref" : "#/definitions/queryParser" + }, { + "default" : { + "type" : "queryParser:project", + "queryParserKey" : "" + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "query" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:string", + "query" : "$0" + } + } ], + "default" : { + "type" : "query:string", + "queryParser" : { + "type" : "queryParser:project", + "queryParserKey" : "" + }, + "query" : "" + } + }, + "query:filter" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "query:filter" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "filter" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "query", "filter" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:filter", + "query" : "^$1", + "filter" : "^$0" + } + } ], + "default" : { + "type" : "query:filter", + "query" : null, + "filter" : null + } + }, + "query:complement" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "query:complement" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "query" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:complement", + "query" : "^$0" + } + } ], + "default" : { + "type" : "query:complement", + "query" : null + } + }, + "query:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "query:composite" + }, + "queries" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/query" + }, + "default" : [ ] + }, + "operator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "OR" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "queries" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:composite", + "queries" : [ "^$0" ] + } + } ], + "default" : { + "type" : "query:composite", + "queries" : [ ], + "operator" : "OR" + } + }, + "query:fromDocuments" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "query:fromDocuments" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "buildFromDocumentIds" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:fromDocuments", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "buildFromDocumentIds" : false + } + }, + "queryParser" : { + "oneOf" : [ { + "$ref" : "#/definitions/queryParser:reference" + }, { + "$ref" : "#/definitions/queryParser:project" + }, { + "$ref" : "#/definitions/queryParser:enhanced" + } ] + }, + "queryParser:project" : { + "type" : "object", + "description" : "A reference to a query parser declared in the project descriptor's queryParsers block.", + "properties" : { + "type" : { + "const" : "queryParser:project" + }, + "queryParserKey" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "enhanced", "standard", "", "complex" ], + "default" : "" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "default" : { + "type" : "queryParser:project", + "queryParserKey" : "" + } + }, + "queryParser:enhanced" : { + "type" : "object", + "description" : "Query parser parsing Lucene syntax with interval queries and other enhancements.", + "properties" : { + "type" : { + "const" : "queryParser:enhanced" + }, + "sanitizeSpaces" : { + "oneOf" : [ { + "type" : "string", + "default" : "(?U)[\\u001c-\\u001f\\p{IsWhite_Space}\\u2028\\u2029]+" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sanitizeNonSpaces" : { + "oneOf" : [ { + "type" : "string", + "default" : "(?U)[\\u200B-\\u200D]+" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "validateFields" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "defaultFields" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "summary", "fld_double_a", "fld_mv", "fld_int", "fld_int_a", "id", "fld_double", "title" ] + }, + "default" : [ ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "defaultOperator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "AND" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "defaultFields" ], + "default" : { + "type" : "queryParser:enhanced", + "sanitizeSpaces" : "(?U)[\\u001c-\\u001f\\p{IsWhite_Space}\\u2028\\u2029]+", + "sanitizeNonSpaces" : "(?U)[\\u200B-\\u200D]+", + "validateFields" : true, + "defaultFields" : [ ], + "defaultOperator" : "AND" + } + }, + "query" : { + "oneOf" : [ { + "$ref" : "#/definitions/query:reference" + }, { + "$ref" : "#/definitions/query:all" + }, { + "$ref" : "#/definitions/query:string" + }, { + "$ref" : "#/definitions/query:forLabels" + }, { + "$ref" : "#/definitions/query:forFieldValues" + }, { + "$ref" : "#/definitions/query:filter" + }, { + "$ref" : "#/definitions/query:complement" + }, { + "$ref" : "#/definitions/query:composite" + }, { + "$ref" : "#/definitions/query:fromDocuments" + } ] + }, + "limit" : { + "oneOf" : [ { + "const" : "unlimited" + }, { + "type" : "integer", + "minimum" : 0 + } ] + }, + "documents:byId" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:byId", + "description" : "Selects documents by the provided internal document identifiers." + }, + "documents" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "id" : { + "oneOf" : [ { + "type" : "integer" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weight" : { + "oneOf" : [ { + "type" : "number" + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "required" : [ "id" ], + "defaultSnippets" : [ { + "description" : "Document identifier and optional weight.", + "body" : { + "id" : "^$1", + "weight" : "^$0" + } + } ] + }, + "default" : [ ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:byId", + "documents" : [ "^$0" ] + } + } ], + "default" : { + "type" : "documents:byId", + "documents" : [ ] + } + }, + "documents:byQuery" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:byQuery", + "description" : "Selects documents using a Lucene syntax query string." + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "accurateHitCount" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "requireScores" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "query" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:byQuery", + "query" : "^$0" + } + }, { + "description" : "Selects documents using a Lucene syntax query string.", + "body" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "$1" + }, + "limit" : "^${2:1000}" + } + } ], + "default" : { + "type" : "documents:byQuery", + "limit" : 10000, + "query" : null, + "accurateHitCount" : false, + "requireScores" : true + } + }, + "documents:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:composite" + }, + "selectors" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/documents" + }, + "default" : [ ] + }, + "operator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "OR" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weightAggregation" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/weightAggregation" + }, { + "default" : "SUM" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "selectors" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:composite", + "selectors" : [ "^$0" ] + } + } ], + "default" : { + "type" : "documents:composite", + "selectors" : [ ], + "operator" : "OR", + "sortOrder" : "DESCENDING", + "weightAggregation" : "SUM" + } + }, + "documents:fromMatrixColumns" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:fromMatrixColumns" + }, + "matrixRows" : { + "allOf" : [ { + "$ref" : "#/definitions/matrixRows" + }, { + "default" : { + "type" : "matrixRows:reference", + "auto" : true + } + } ] + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weightAggregation" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/weightAggregation" + }, { + "default" : "SUM" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "matrixRows" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:fromMatrixColumns", + "matrixRows" : "^$1", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "documents:fromMatrixColumns", + "limit" : 10000, + "matrixRows" : { + "type" : "matrixRows:reference", + "auto" : true + }, + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "sortOrder" : "DESCENDING", + "weightAggregation" : "SUM" + } + }, + "documents:fromClusterExemplars" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:fromClusterExemplars" + }, + "clusters" : { + "allOf" : [ { + "$ref" : "#/definitions/clusters" + }, { + "default" : { + "type" : "clusters:reference", + "auto" : true + } + } ] + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "clusters" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:fromClusterExemplars", + "clusters" : "^$1", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "documents:fromClusterExemplars", + "limit" : 10000, + "clusters" : { + "type" : "clusters:reference", + "auto" : true + }, + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "sortOrder" : "DESCENDING" + } + }, + "documents:fromClusterMembers" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:fromClusterMembers" + }, + "clusters" : { + "allOf" : [ { + "$ref" : "#/definitions/clusters" + }, { + "default" : { + "type" : "clusters:reference", + "auto" : true + } + } ] + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "clusters" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:fromClusterMembers", + "clusters" : "^$2", + "documents" : "^$1" + } + } ], + "default" : { + "type" : "documents:fromClusterMembers", + "limit" : 10000, + "clusters" : { + "type" : "clusters:reference", + "auto" : true + }, + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "sortOrder" : "DESCENDING" + } + }, + "documents:sample" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:sample" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "samplingRatio" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.0, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "randomSeed" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "query" ], + "defaultSnippets" : [ { + "description" : "Takes a random sample of documents returned by a query. The limit property sets the maximum number of documents to select, regardless of the number of documents that match the query.", + "body" : { + "type" : "documents:sample", + "limit" : "^$1", + "query" : "^$0" + } + }, { + "description" : "Samples documents selected by a string query. The limit property sets the maximum number of documents to select, regardless of the number of documents that match the query.", + "body" : { + "type" : "documents:sample", + "query" : { + "type" : "query:string", + "query" : "$1" + }, + "limit" : "^$2" + } + } ], + "default" : { + "type" : "documents:sample", + "limit" : 10000, + "query" : null, + "randomSeed" : 0, + "samplingRatio" : 1.0 + } + }, + "documents:embeddingNearestNeighbors" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:embeddingNearestNeighbors" + }, + "vector" : { + "allOf" : [ { + "$ref" : "#/definitions/vector" + }, { + "default" : { + "type" : "vector:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 100 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "filterQuery" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : { + "type" : "query:all" + } + } ] + }, + "searcher" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "AUTO", "APPROXIMATE", "COMPLETE" ], + "default" : "AUTO" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "vector" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : "^$0" + } + } ], + "default" : { + "type" : "documents:embeddingNearestNeighbors", + "limit" : 100, + "vector" : { + "type" : "vector:reference", + "auto" : true + }, + "filterQuery" : { + "type" : "query:all" + }, + "failIfEmbeddingsNotAvailable" : true, + "searcher" : "AUTO" + } + }, + "documents:contrastScore" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:contrastScore" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "matrixRows" : { + "allOf" : [ { + "$ref" : "#/definitions/matrixRows" + }, { + "default" : { + "type" : "matrixRows:reference", + "auto" : true + } + } ] + }, + "documentTimestamps" : { + "allOf" : [ { + "$ref" : "#/definitions/values" + }, { + "default" : null + } ] + }, + "contextTimestamps" : { + "allOf" : [ { + "$ref" : "#/definitions/values" + }, { + "default" : null + } ] + }, + "forceSymmetricalContext" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 10000 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minSimilarDocuments" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "matrixRows", "documentTimestamps", "contextTimestamps" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:contrastScore", + "documents" : "^$3", + "matrixRows" : "^$2", + "documentTimestamps" : "^$1", + "contextTimestamps" : "^$0" + } + } ], + "default" : { + "type" : "documents:contrastScore", + "limit" : 10000, + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "matrixRows" : { + "type" : "matrixRows:reference", + "auto" : true + }, + "documentTimestamps" : null, + "contextTimestamps" : null, + "forceSymmetricalContext" : true, + "minSimilarDocuments" : 0, + "sortOrder" : "DESCENDING" + } + }, + "documents:rwmd" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:rwmd" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : { + "type" : "labelFilter:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:rwmd", + "documents" : "^$1", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "documents:rwmd", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "failIfEmbeddingsNotAvailable" : true + } + }, + "documents:fromDocumentPairs" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:fromDocumentPairs" + }, + "documentPairs" : { + "allOf" : [ { + "$ref" : "#/definitions/documentPairs" + }, { + "default" : { + "type" : "documentPairs:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documentPairs" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:fromDocumentPairs", + "documentPairs" : "^$0" + } + } ], + "default" : { + "type" : "documents:fromDocumentPairs", + "documentPairs" : { + "type" : "documentPairs:reference", + "auto" : true + } + } + }, + "documents" : { + "oneOf" : [ { + "$ref" : "#/definitions/documents:reference" + }, { + "$ref" : "#/definitions/documents:byQuery" + }, { + "$ref" : "#/definitions/documents:byId" + }, { + "$ref" : "#/definitions/documents:embeddingNearestNeighbors" + }, { + "$ref" : "#/definitions/documents:composite" + }, { + "$ref" : "#/definitions/documents:fromMatrixColumns" + }, { + "$ref" : "#/definitions/documents:fromClusterExemplars" + }, { + "$ref" : "#/definitions/documents:fromClusterMembers" + }, { + "$ref" : "#/definitions/documents:sample" + }, { + "$ref" : "#/definitions/documents:contrastScore" + }, { + "$ref" : "#/definitions/documents:rwmd" + }, { + "$ref" : "#/definitions/documents:fromDocumentPairs" + } ] + }, + "labels:byPrefix" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:byPrefix", + "description" : "Retrieves labels starting with the provided prefix." + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : { + "type" : "labelFilter:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "type" : "integer", + "default" : 30, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "prefix" : { + "oneOf" : [ { + "type" : "string", + "default" : "" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "prefix" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:byPrefix", + "prefix" : "$1", + "limit" : "^${2:20}" + } + } ], + "default" : { + "type" : "labels:byPrefix", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "prefix" : "", + "limit" : 30 + } + }, + "labels:direct" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:direct", + "description" : "Provides a constant list of labels." + }, + "labels" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "label" : { + "oneOf" : [ { + "type" : "string" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weight" : { + "oneOf" : [ { + "type" : "number" + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "required" : [ "label" ], + "defaultSnippets" : [ { + "body" : { + "label" : "$0" + } + } ] + }, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ], + "default" : [ ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:direct", + "labels" : [ "^$0" ] + } + } ], + "default" : { + "type" : "labels:direct", + "labels" : [ ] + } + }, + "labels:fromDocuments" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:fromDocuments", + "description" : "Collects labels occurring in the provided documents." + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "maxLabels" : { + "allOf" : [ { + "$ref" : "#/definitions/labelCount" + }, { + "default" : { + "type" : "labelCount:fixed", + "value" : 10000 + } + } ] + }, + "labelAggregator" : { + "allOf" : [ { + "$ref" : "#/definitions/labelAggregator" + }, { + "default" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabelsPerDocument" : 10, + "minAbsoluteDf" : 1, + "minRelativeDf" : 0.0, + "maxRelativeDf" : 1.0, + "minWeight" : 0.0, + "tieResolution" : "AUTO", + "outputWeightFormula" : "TF", + "threads" : "auto" + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:fromDocuments", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "maxLabels" : { + "type" : "labelCount:fixed", + "value" : 10000 + }, + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabelsPerDocument" : 10, + "minAbsoluteDf" : 1, + "minRelativeDf" : 0.0, + "maxRelativeDf" : 1.0, + "minWeight" : 0.0, + "tieResolution" : "AUTO", + "outputWeightFormula" : "TF", + "threads" : "auto" + } + } + }, + "labels:fromText" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:fromText" + }, + "text" : { + "oneOf" : [ { + "type" : "string", + "default" : "" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "analyzer" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "english", "keyword", "whitespace", "literal" ], + "default" : "english" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "featureExtractor" : { + "oneOf" : [ { + "type" : "string", + "default" : "" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : { + "type" : "labelFilter:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "text" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:fromText", + "text" : "$0" + } + } ], + "default" : { + "type" : "labels:fromText", + "text" : "", + "analyzer" : "english", + "featureExtractor" : "", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + } + } + }, + "labels:scored" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:scored" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "scorer" : { + "allOf" : [ { + "$ref" : "#/definitions/labelScorer" + }, { + "default" : { + "type" : "labelScorer:identity" + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "scorer", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:scored", + "scorer" : "^$0" + } + } ], + "default" : { + "type" : "labels:scored", + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "scorer" : { + "type" : "labelScorer:identity" + } + } + }, + "labels:embeddingNearestNeighbors" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:embeddingNearestNeighbors" + }, + "vector" : { + "allOf" : [ { + "$ref" : "#/definitions/vector" + }, { + "default" : { + "type" : "vector:reference", + "auto" : true + } + } ] + }, + "labelFilter" : { + "allOf" : [ { + "$ref" : "#/definitions/labelFilter" + }, { + "default" : { + "type" : "labelFilter:acceptAll" + } + } ] + }, + "limit" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "vector" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : "^$0" + } + } ], + "default" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:reference", + "auto" : true + }, + "labelFilter" : { + "type" : "labelFilter:acceptAll" + }, + "limit" : 10, + "failIfEmbeddingsNotAvailable" : true + } + }, + "labels:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:composite" + }, + "sources" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/labels" + }, + "default" : [ ] + }, + "operator" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "OR", "AND" ], + "default" : "OR" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weightAggregation" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/weightAggregation" + }, { + "default" : "SUM" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "examples" : [ { + "type" : "labels:composite", + "sources" : [ ] + } ], + "required" : [ "type", "sources" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:composite", + "sources" : [ "^$0" ] + } + } ], + "default" : { + "type" : "labels:composite", + "sources" : [ ], + "operator" : "OR", + "sortOrder" : "DESCENDING", + "weightAggregation" : "SUM" + } + }, + "labels" : { + "oneOf" : [ { + "$ref" : "#/definitions/labels:reference" + }, { + "$ref" : "#/definitions/labels:byPrefix" + }, { + "$ref" : "#/definitions/labels:direct" + }, { + "$ref" : "#/definitions/labels:fromDocuments" + }, { + "$ref" : "#/definitions/labels:fromText" + }, { + "$ref" : "#/definitions/labels:scored" + }, { + "$ref" : "#/definitions/labels:embeddingNearestNeighbors" + }, { + "$ref" : "#/definitions/labels:composite" + } ] + }, + "matrix:direct" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:direct" + }, + "matrix" : { + "type" : "object", + "properties" : { + "columns" : { + "oneOf" : [ { + "type" : "integer", + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "indices" : { + "type" : "array", + "items" : { + "type" : "array", + "items" : { + "type" : "integer" + }, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ] + }, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ] + }, + "values" : { + "type" : "array", + "items" : { + "type" : "array", + "items" : { + "type" : "number" + }, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ] + }, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ] + } + }, + "additionalProperties" : false, + "required" : [ "columns", "indices", "values" ], + "defaultSnippets" : [ { + "body" : { + "columns" : "^$1", + "indices" : "^$2", + "values" : "^$3" + } + } ], + "default" : { + "columns" : 0, + "indices" : [ ], + "values" : [ ] + } + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrix" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:direct", + "matrix" : { + "columns" : 0, + "indices" : [ [ ] ], + "values" : [ [ ] ] + } + } + } ], + "default" : { + "type" : "matrix:direct", + "matrix" : { + "columns" : 0, + "indices" : [ ], + "values" : [ ] + } + } + }, + "matrix:fromMatrixRows" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:fromMatrixRows" + }, + "matrixRows" : { + "allOf" : [ { + "$ref" : "#/definitions/matrixRows" + }, { + "default" : { + "type" : "matrixRows:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrixRows" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:fromMatrixRows", + "matrixRows" : "^$1" + } + } ], + "default" : { + "type" : "matrix:fromMatrixRows", + "matrixRows" : { + "type" : "matrixRows:reference", + "auto" : true + } + } + }, + "matrix:cooccurrenceLabelSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:cooccurrenceLabelSimilarity" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "cooccurrenceWindowSize" : { + "oneOf" : [ { + "type" : "integer", + "default" : 32, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "similarityWeighting" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "COOCCURRENCES", "RR", "INCLUSION", "LOEVINGER", "BB", "DICE", "YULE", "OCHIAI", "INNER_PRODUCT", "COSINE", "PEARSON" ], + "default" : "INCLUSION" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "normalized" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:cooccurrenceLabelSimilarity", + "documents" : "^$1", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "matrix:cooccurrenceLabelSimilarity", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "cooccurrenceWindowSize" : 32, + "similarityWeighting" : "INCLUSION", + "normalized" : true, + "threads" : "auto" + } + }, + "matrix:keywordDocumentSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:keywordDocumentSimilarity" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "labelCollector" : { + "allOf" : [ { + "$ref" : "#/definitions/labelCollector" + }, { + "default" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "maxNeighbors" : { + "oneOf" : [ { + "type" : "integer", + "default" : 8, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minQueryLabelsPerDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxQueryLabelsPerDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 4, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minQueryLabelsRequiredInSimilarDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxDocumentsForSubIndex" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.3, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxInMemorySubIndexSize" : { + "oneOf" : [ { + "type" : "integer", + "default" : 8000000, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "normalized" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:keywordDocumentSimilarity", + "documents" : "^$1" + } + } ], + "default" : { + "type" : "matrix:keywordDocumentSimilarity", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxNeighbors" : 8, + "minQueryLabelsPerDocument" : 1, + "maxQueryLabelsPerDocument" : 4, + "minQueryLabelsRequiredInSimilarDocument" : 1, + "maxDocumentsForSubIndex" : 0.3, + "maxInMemorySubIndexSize" : 8000000, + "normalized" : true, + "threads" : "auto" + } + }, + "matrix:knn2dDistanceSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:knn2dDistanceSimilarity" + }, + "embedding2d" : { + "allOf" : [ { + "$ref" : "#/definitions/embedding2d" + }, { + "default" : { + "type" : "embedding2d:reference", + "auto" : true + } + } ] + }, + "maxNearestPoints" : { + "oneOf" : [ { + "type" : "integer", + "default" : 8, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "embedding2d" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:knn2dDistanceSimilarity", + "embedding2d" : "^$0" + } + } ], + "default" : { + "type" : "matrix:knn2dDistanceSimilarity", + "embedding2d" : { + "type" : "embedding2d:reference", + "auto" : true + }, + "maxNearestPoints" : 8 + } + }, + "matrix:knnVectorsSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:knnVectorsSimilarity" + }, + "vectors" : { + "allOf" : [ { + "$ref" : "#/definitions/vectors" + }, { + "default" : { + "type" : "vectors:reference", + "auto" : true + } + } ] + }, + "maxNeighbors" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "vectors" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : "^$0" + } + } ], + "default" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:reference", + "auto" : true + }, + "maxNeighbors" : 10, + "threads" : "auto" + } + }, + "matrix:elementWiseProduct" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:elementWiseProduct" + }, + "factorA" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : null + } ] + }, + "factorB" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "factorA", "factorB" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:elementWiseProduct", + "factorA" : "^$1", + "factorB" : "^$0" + } + } ], + "default" : { + "type" : "matrix:elementWiseProduct", + "factorA" : null, + "factorB" : null + } + }, + "matrix:keywordLabelDocumentSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:keywordLabelDocumentSimilarity" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "maxSimilarDocumentsPerLabel" : { + "oneOf" : [ { + "type" : "integer", + "default" : 5, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "documents" : "^$1", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "maxSimilarDocumentsPerLabel" : 5, + "threads" : "auto" + } + }, + "matrix" : { + "oneOf" : [ { + "$ref" : "#/definitions/matrix:reference" + }, { + "$ref" : "#/definitions/matrix:direct" + }, { + "$ref" : "#/definitions/matrix:fromMatrixRows" + }, { + "$ref" : "#/definitions/matrix:cooccurrenceLabelSimilarity" + }, { + "$ref" : "#/definitions/matrix:keywordDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrix:knn2dDistanceSimilarity" + }, { + "$ref" : "#/definitions/matrix:knnVectorsSimilarity" + }, { + "$ref" : "#/definitions/matrix:keywordLabelDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrix:elementWiseProduct" + } ] + }, + "matrixRows:keywordDocumentSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrixRows:keywordDocumentSimilarity" + }, + "index" : { + "type" : "object", + "properties" : { + "columns" : { + "type" : "object", + "properties" : { + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + } + }, + "required" : [ "documents" ], + "additionalProperties" : false, + "default" : { + "documents" : null + } + }, + "rows" : { + "type" : "object", + "properties" : { + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + }, + "labelCollector" : { + "allOf" : [ { + "$ref" : "#/definitions/labelCollector" + }, { + "default" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + } + } ] + }, + "minQueryLabelsPerRowDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxQueryLabelsPerRowDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "required" : [ "documents" ], + "additionalProperties" : false, + "default" : { + "documents" : null, + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxQueryLabelsPerRowDocument" : 10, + "minQueryLabelsPerRowDocument" : 0, + "threads" : "auto" + } + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "maxColumnDocumentsForSubIndex" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.3, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxInMemorySubIndexSize" : { + "oneOf" : [ { + "type" : "integer", + "default" : 8000000, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "required" : [ "columns", "rows" ], + "additionalProperties" : false, + "default" : { + "rows" : { + "documents" : null, + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxQueryLabelsPerRowDocument" : 10, + "minQueryLabelsPerRowDocument" : 0, + "threads" : "auto" + }, + "columns" : { + "documents" : null + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "maxColumnDocumentsForSubIndex" : 0.3, + "maxInMemorySubIndexSize" : 8000000, + "threads" : "auto" + } + }, + "maxNeighbors" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minQueryLabelsRequiredInColumnDocument" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1, + "exclusiveMinimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "normalized" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "index" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrixRows:keywordDocumentSimilarity", + "index" : { + "rows" : { + "documents" : "^$1" + }, + "columns" : { + "documents" : "^$2" + } + } + } + } ], + "default" : { + "type" : "matrixRows:keywordDocumentSimilarity", + "index" : { + "rows" : { + "documents" : null, + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxQueryLabelsPerRowDocument" : 10, + "minQueryLabelsPerRowDocument" : 0, + "threads" : "auto" + }, + "columns" : { + "documents" : null + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "maxColumnDocumentsForSubIndex" : 0.3, + "maxInMemorySubIndexSize" : 8000000, + "threads" : "auto" + }, + "maxNeighbors" : 10, + "minQueryLabelsRequiredInColumnDocument" : 1, + "normalized" : false, + "threads" : "auto" + } + }, + "matrixRows:knnVectorsSimilarity" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrixRows:knnVectorsSimilarity" + }, + "vectors" : { + "type" : "object", + "properties" : { + "rows" : { + "allOf" : [ { + "$ref" : "#/definitions/vectors" + }, { + "default" : { + "type" : "vectors:reference", + "auto" : true + } + } ] + }, + "columns" : { + "allOf" : [ { + "$ref" : "#/definitions/vectors" + }, { + "default" : { + "type" : "vectors:reference", + "auto" : true + } + } ] + } + }, + "required" : [ "rows", "columns" ], + "additionalProperties" : false, + "default" : { + "rows" : { + "type" : "vectors:reference", + "auto" : true + }, + "columns" : { + "type" : "vectors:reference", + "auto" : true + } + } + }, + "maxNeighbors" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "vectors" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrixRows:knnVectorsSimilarity", + "vectors" : { + "rows" : "^$1", + "columns" : "^$0" + } + } + } ], + "default" : { + "type" : "matrixRows:knnVectorsSimilarity", + "vectors" : { + "rows" : { + "type" : "vectors:reference", + "auto" : true + }, + "columns" : { + "type" : "vectors:reference", + "auto" : true + } + }, + "maxNeighbors" : 10, + "threads" : "auto" + } + }, + "matrixRows:fromMatrix" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrixRows:fromMatrix" + }, + "matrix" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : { + "type" : "matrix:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrix" ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrixRows:fromMatrix", + "matrix" : "^$1" + } + } ], + "default" : { + "type" : "matrixRows:fromMatrix", + "matrix" : { + "type" : "matrix:reference", + "auto" : true + } + } + }, + "matrixRows" : { + "oneOf" : [ { + "$ref" : "#/definitions/matrixRows:reference" + }, { + "$ref" : "#/definitions/matrixRows:keywordDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrixRows:knnVectorsSimilarity" + }, { + "$ref" : "#/definitions/matrixRows:fromMatrix" + } ] + }, + "vector:labelEmbedding" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vector:labelEmbedding" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vector:labelEmbedding", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "failIfEmbeddingsNotAvailable" : true + } + }, + "vector:documentEmbedding" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vector:documentEmbedding" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vector:documentEmbedding", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "vector:documentEmbedding", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "failIfEmbeddingsNotAvailable" : true + } + }, + "vector:composite" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vector:composite" + }, + "vectors" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/vector" + }, + "default" : [ ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "vectors" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vector:composite", + "vectors" : [ "^$0" ] + } + } ], + "default" : { + "type" : "vector:composite", + "vectors" : [ ] + } + }, + "vector:estimateFromContext" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vector:estimateFromContext" + }, + "contextVector" : { + "allOf" : [ { + "$ref" : "#/definitions/vector" + }, { + "default" : { + "type" : "vector:reference", + "auto" : true + } + } ] + }, + "failIfEmbeddingsNotAvailable" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "contextVector" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vector:estimateFromContext", + "contextVector" : "^$0" + } + } ], + "default" : { + "type" : "vector:estimateFromContext", + "contextVector" : { + "type" : "vector:reference", + "auto" : true + }, + "failIfEmbeddingsNotAvailable" : true + } + }, + "vector" : { + "oneOf" : [ { + "$ref" : "#/definitions/vector:reference" + }, { + "$ref" : "#/definitions/vector:labelEmbedding" + }, { + "$ref" : "#/definitions/vector:documentEmbedding" + }, { + "$ref" : "#/definitions/vector:composite" + }, { + "$ref" : "#/definitions/vector:estimateFromContext" + } ] + }, + "vectors:precomputedDocumentEmbeddings" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vectors:precomputedDocumentEmbeddings" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "maxInMemoryKnnSubIndexSize" : { + "oneOf" : [ { + "type" : "number", + "default" : 50000000 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vectors:precomputedDocumentEmbeddings", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "vectors:precomputedDocumentEmbeddings", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "maxInMemoryKnnSubIndexSize" : 50000000 + } + }, + "vectors:precomputedLabelEmbeddings" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vectors:precomputedLabelEmbeddings" + }, + "labels" : { + "allOf" : [ { + "$ref" : "#/definitions/labels" + }, { + "default" : { + "type" : "labels:reference", + "auto" : true + } + } ] + }, + "maxLabelsForSubIndex" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.05, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "labels" ], + "defaultSnippets" : [ { + "body" : { + "type" : "vectors:precomputedLabelEmbeddings", + "labels" : "^$0" + } + } ], + "default" : { + "type" : "vectors:precomputedLabelEmbeddings", + "labels" : { + "type" : "labels:reference", + "auto" : true + }, + "maxLabelsForSubIndex" : 0.05 + } + }, + "vectors" : { + "oneOf" : [ { + "$ref" : "#/definitions/vectors:reference" + }, { + "$ref" : "#/definitions/vectors:precomputedLabelEmbeddings" + }, { + "$ref" : "#/definitions/vectors:precomputedDocumentEmbeddings" + } ] + }, + "clusters:ap" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "clusters:ap", + "description" : "Performs clustering using the modified Affinity Propagation algorithm." + }, + "matrix" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : { + "type" : "matrix:reference", + "auto" : true + } + } ] + }, + "inputPreference" : { + "oneOf" : [ { + "type" : "number", + "default" : -1000.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "softening" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.2, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "damping" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.9, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minPruningGain" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.3, + "minimum" : 0.0, + "maximum" : 1.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxIterations" : { + "oneOf" : [ { + "type" : "integer", + "default" : 2000, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "minSteadyIterations" : { + "oneOf" : [ { + "type" : "number", + "default" : 100, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrix" ], + "defaultSnippets" : [ { + "body" : { + "type" : "clusters:ap", + "matrix" : "^$0" + } + } ], + "default" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:reference", + "auto" : true + }, + "inputPreference" : -1000.0, + "softening" : 0.2, + "damping" : 0.9, + "minPruningGain" : 0.3, + "maxIterations" : 2000, + "minSteadyIterations" : 100, + "threads" : "auto" + } + }, + "clusters:withRemappedDocuments" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "clusters:withRemappedDocuments" + }, + "clusters" : { + "allOf" : [ { + "$ref" : "#/definitions/clusters" + }, { + "default" : { + "type" : "clusters:reference", + "auto" : true + } + } ] + }, + "exemplarsFrom" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + }, + "exemplarsTo" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + }, + "membersFrom" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + }, + "membersTo" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "clusters", "exemplarsFrom", "exemplarsTo", "membersFrom", "membersTo" ], + "defaultSnippets" : [ { + "body" : { + "type" : "clusters:withRemappedDocuments", + "clusters" : "^$4", + "exemplarsFrom" : "^$3", + "exemplarsTo" : "^$2", + "membersFrom" : "^$1", + "membersTo" : "^$0" + } + } ], + "default" : { + "type" : "clusters:withRemappedDocuments", + "clusters" : { + "type" : "clusters:reference", + "auto" : true + }, + "membersFrom" : null, + "membersTo" : null, + "exemplarsFrom" : null, + "exemplarsTo" : null + } + }, + "clusters:fromMatrixColumns" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "clusters:fromMatrixColumns" + }, + "matrixRows" : { + "allOf" : [ { + "$ref" : "#/definitions/matrixRows" + }, { + "default" : { + "type" : "matrixRows:reference", + "auto" : true + } + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : 100 + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "sortOrder" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/sortOrder" + }, { + "default" : "DESCENDING" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "weightAggregation" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/weightAggregation" + }, { + "default" : "SUM" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrixRows" ], + "defaultSnippets" : [ { + "body" : { + "type" : "clusters:fromMatrixColumns", + "matrixRows" : "^$1" + } + } ], + "default" : { + "type" : "clusters:fromMatrixColumns", + "matrixRows" : { + "type" : "matrixRows:reference", + "auto" : true + }, + "limit" : 100, + "sortOrder" : "DESCENDING", + "weightAggregation" : "SUM" + } + }, + "clusters:byValues" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "clusters:byValues" + }, + "values" : { + "allOf" : [ { + "$ref" : "#/definitions/values" + }, { + "default" : { + "type" : "values:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "values" ], + "defaultSnippets" : [ { + "body" : { + "type" : "clusters:byValues", + "values" : "^$1" + } + } ], + "default" : { + "type" : "clusters:byValues", + "values" : { + "type" : "values:reference", + "auto" : true + } + } + }, + "clusters" : { + "oneOf" : [ { + "$ref" : "#/definitions/clusters:reference" + }, { + "$ref" : "#/definitions/clusters:ap" + }, { + "$ref" : "#/definitions/clusters:fromMatrixColumns" + }, { + "$ref" : "#/definitions/clusters:withRemappedDocuments" + }, { + "$ref" : "#/definitions/clusters:byValues" + } ] + }, + "labelClusters:documentClusterLabels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelClusters:documentClusterLabels" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "clusters" : { + "allOf" : [ { + "$ref" : "#/definitions/clusters" + }, { + "default" : { + "type" : "clusters:reference", + "auto" : true + } + } ] + }, + "maxLabels" : { + "oneOf" : [ { + "type" : "integer", + "default" : 3, + "exclusiveMinimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "labelAggregator" : { + "allOf" : [ { + "$ref" : "#/definitions/labelAggregator" + }, { + "default" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabelsPerDocument" : 10, + "minAbsoluteDf" : 1, + "minRelativeDf" : 0.0, + "maxRelativeDf" : 1.0, + "minWeight" : 0.0, + "tieResolution" : "AUTO", + "outputWeightFormula" : "TF", + "threads" : "auto" + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "clusters" ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelClusters:documentClusterLabels", + "documents" : "^$0", + "clusters" : "^$1" + } + } ], + "default" : { + "type" : "labelClusters:documentClusterLabels", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "clusters" : { + "type" : "clusters:reference", + "auto" : true + }, + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabelsPerDocument" : 10, + "minAbsoluteDf" : 1, + "minRelativeDf" : 0.0, + "maxRelativeDf" : 1.0, + "minWeight" : 0.0, + "tieResolution" : "AUTO", + "outputWeightFormula" : "TF", + "threads" : "auto" + }, + "maxLabels" : 3 + } + }, + "labelClusters" : { + "oneOf" : [ { + "$ref" : "#/definitions/labelClusters:reference" + }, { + "$ref" : "#/definitions/labelClusters:documentClusterLabels" + } ] + }, + "embedding2d:lv" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "embedding2d:lv" + }, + "matrix" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : { + "type" : "matrix:reference", + "auto" : true + } + } ] + }, + "initial" : { + "oneOf" : [ { + "$ref" : "#/definitions/embedding2d" + }, { + "type" : "null" + } ], + "default" : null + }, + "maxIterations" : { + "oneOf" : [ { + "type" : "integer", + "default" : 300, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeCount" : { + "oneOf" : [ { + "type" : "integer", + "default" : 5, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeWeight" : { + "oneOf" : [ { + "type" : "number", + "default" : 2.0, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeDenominator" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.0, + "exclusiveMinimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "initializedLearningRate" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.02, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrix" ], + "defaultSnippets" : [ { + "body" : { + "type" : "embedding2d:lv", + "matrix" : "^$0" + } + } ], + "default" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:reference", + "auto" : true + }, + "initial" : null, + "maxIterations" : 300, + "negativeEdgeCount" : 5, + "negativeEdgeWeight" : 2.0, + "negativeEdgeDenominator" : 1.0, + "initializedLearningRate" : 0.02, + "threads" : "auto" + } + }, + "embedding2d:lvOverlay" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "embedding2d:lvOverlay" + }, + "matrix" : { + "allOf" : [ { + "$ref" : "#/definitions/matrix" + }, { + "default" : { + "type" : "matrix:reference", + "auto" : true + } + } ] + }, + "embedding2d" : { + "allOf" : [ { + "$ref" : "#/definitions/embedding2d" + }, { + "default" : { + "type" : "embedding2d:reference", + "auto" : true + } + } ] + }, + "initial" : { + "oneOf" : [ { + "$ref" : "#/definitions/embedding2d" + }, { + "type" : "null" + } ], + "default" : null + }, + "maxIterations" : { + "oneOf" : [ { + "type" : "integer", + "default" : 300, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeCount" : { + "oneOf" : [ { + "type" : "integer", + "default" : 5, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeWeight" : { + "oneOf" : [ { + "type" : "number", + "default" : 2.0, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "negativeEdgeDenominator" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.0, + "exclusiveMinimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "initializedLearningRate" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.02, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "matrix", "embedding2d" ], + "defaultSnippets" : [ { + "body" : { + "type" : "embedding2d:lvOverlay", + "matrix" : "^$1", + "embedding2d" : "^$0" + } + } ], + "default" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:reference", + "auto" : true + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "auto" : true + }, + "initial" : null, + "maxIterations" : 300, + "negativeEdgeCount" : 5, + "negativeEdgeWeight" : 2.0, + "negativeEdgeDenominator" : 1.0, + "initializedLearningRate" : 0.02, + "threads" : "auto" + } + }, + "embedding2d:transferred" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "embedding2d:transferred" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/entity" + }, { + "default" : null + } ] + }, + "embedding2d" : { + "allOf" : [ { + "$ref" : "#/definitions/embedding2d" + }, { + "default" : null + } ] + }, + "target" : { + "allOf" : [ { + "$ref" : "#/definitions/entity" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source", "embedding2d", "target" ], + "defaultSnippets" : [ { + "body" : { + "type" : "embedding2d:transferred", + "source" : "^$1", + "embedding2d" : "^$2", + "target" : "^$0" + } + } ], + "default" : { + "type" : "embedding2d:transferred", + "source" : null, + "target" : null, + "embedding2d" : null + } + }, + "embedding2d" : { + "oneOf" : [ { + "$ref" : "#/definitions/embedding2d:reference" + }, { + "$ref" : "#/definitions/embedding2d:lv" + }, { + "$ref" : "#/definitions/embedding2d:lvOverlay" + }, { + "$ref" : "#/definitions/embedding2d:transferred" + } ] + }, + "entity" : { + "oneOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "$ref" : "#/definitions/labels" + } ] + }, + "values" : { + "oneOf" : [ { + "$ref" : "#/definitions/values:reference" + }, { + "$ref" : "#/definitions/values:fromDocumentField" + } ] + }, + "values:fromDocumentField" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "values:fromDocumentField" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "fieldName" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "summary", "fld_double_a", "fld_mv", "fld_int", "fld_int_a", "id", "fld_double", "title" ], + "default" : null + }, { + "$ref" : "#/definitions/var" + } ] + }, + "threads" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/threads" + }, { + "default" : "auto" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "multipleValues" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "COLLECT_FIRST", "REQUIRE_EXACTLY_ONE", "COLLECT_ALL" ], + "default" : "REQUIRE_EXACTLY_ONE" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents", "fieldName" ], + "defaultSnippets" : [ { + "body" : { + "type" : "values:fromDocumentField", + "documents" : "^$1", + "fieldName" : "^$0" + } + } ], + "default" : { + "type" : "values:fromDocumentField", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "fieldName" : null, + "multipleValues" : "REQUIRE_EXACTLY_ONE", + "threads" : "auto" + } + }, + "documentContent" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentContent" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/contentFields" + }, { + "default" : { + "type" : "contentFields:reference", + "auto" : true + } + } ] + }, + "start" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "queries" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/query" + }, + "default" : { } + }, + "mode" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "STREAMING", "EAGER" ], + "default" : "STREAMING" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documentContent", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "fields" : { + "type" : "contentFields:reference", + "auto" : true + }, + "queries" : { }, + "start" : 0, + "limit" : "unlimited", + "mode" : "STREAMING" + } + }, + "documentLabels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentLabels" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "labelCollector" : { + "allOf" : [ { + "$ref" : "#/definitions/labelCollector" + }, { + "default" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + } + } ] + }, + "maxLabels" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "start" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "limit" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "documentLabels", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "documentLabels", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:reference", + "auto" : true + }, + "labelListFilter" : { + "type" : "labelListFilter:truncatedPhrases" + }, + "fields" : { + "type" : "featureFields:reference", + "auto" : true + }, + "minTf" : 0, + "minTfMass" : 1.0, + "tieResolution" : "AUTO" + }, + "maxLabels" : 10, + "start" : 0, + "limit" : "unlimited" + } + }, + "stats:documents" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "stats:documents" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "stats:documents", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "stats:documents", + "documents" : { + "type" : "documents:reference", + "auto" : true + } + } + }, + "stats" : { + "oneOf" : [ { + "$ref" : "#/definitions/stats:documents" + } ] + }, + "documentOverlap" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentOverlap" + }, + "documentPairs" : { + "allOf" : [ { + "$ref" : "#/definitions/documentPairs" + }, { + "default" : { + "type" : "documentPairs:reference", + "auto" : true + } + } ] + }, + "pairwiseSimilarity" : { + "allOf" : [ { + "$ref" : "#/definitions/pairwiseSimilarity" + }, { + "default" : { + "type" : "pairwiseSimilarity:documentOverlapRatio", + "fields" : { + "type" : "fields:reference", + "auto" : true + }, + "ngramWindow" : 6, + "crossFieldOverlaps" : true, + "allowedGapRatio" : 0.0, + "computeDifferences" : false + } + } ] + }, + "alignedFragments" : { + "type" : "object", + "properties" : { + "maxFragments" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "contextChars" : { + "oneOf" : [ { + "type" : "integer", + "default" : 160 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/contentFields" + }, { + "default" : { + "type" : "contentFields:empty" + } + } ] + } + }, + "additionalProperties" : false, + "default" : { + "maxFragments" : "unlimited", + "contextChars" : 160, + "fields" : { + "type" : "contentFields:empty" + } + } + }, + "fragmentsInFields" : { + "type" : "object", + "properties" : { + "contextChars" : { + "oneOf" : [ { + "type" : "integer", + "default" : 160 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/contentFields" + }, { + "default" : { + "type" : "contentFields:empty" + } + } ] + } + }, + "additionalProperties" : false, + "default" : { + "fields" : { + "type" : "contentFields:empty" + }, + "contextChars" : 160 + } + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documentPairs", "pairwiseSimilarity" ], + "default" : { + "type" : "documentOverlap", + "documentPairs" : { + "type" : "documentPairs:reference", + "auto" : true + }, + "pairwiseSimilarity" : { + "type" : "pairwiseSimilarity:documentOverlapRatio", + "fields" : { + "type" : "fields:reference", + "auto" : true + }, + "ngramWindow" : 6, + "crossFieldOverlaps" : true, + "allowedGapRatio" : 0.0, + "computeDifferences" : false + }, + "alignedFragments" : { + "maxFragments" : "unlimited", + "contextChars" : 160, + "fields" : { + "type" : "contentFields:empty" + } + }, + "fragmentsInFields" : { + "fields" : { + "type" : "contentFields:empty" + }, + "contextChars" : 160 + } + } + }, + "documentPairs" : { + "oneOf" : [ { + "$ref" : "#/definitions/documentPairs:reference" + }, { + "$ref" : "#/definitions/documentPairs:duplicates" + }, { + "$ref" : "#/definitions/documentPairs:all" + } ] + }, + "documentPairs:all" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentPairs:all" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "documentPairs:all", + "documents" : { + "type" : "documents:reference", + "auto" : true + } + } + }, + "documentPairs:duplicates" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentPairs:duplicates" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "documentPairFilter" : { + "type" : "object", + "properties" : { + "comment" : { + "$ref" : "#/definitions/comment" + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : { + "type" : "query:all" + } + } ] + }, + "countCondition" : { + "oneOf" : [ { + "type" : "string", + "enum" : [ "ZERO", "ONE", "ONE_OR_MORE", "TWO" ], + "default" : "ONE_OR_MORE" + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "additionalProperties" : false, + "required" : [ ], + "default" : { + "query" : { + "type" : "query:all" + }, + "countCondition" : "ONE_OR_MORE" + } + }, + "hashGrouping" : { + "type" : "object", + "properties" : { + "comment" : { + "$ref" : "#/definitions/comment" + }, + "features" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "pairing" : { + "type" : "object", + "properties" : { + "maxHashBitsDifferent" : { + "oneOf" : [ { + "type" : "integer", + "default" : 0, + "minimum" : 0, + "maximum" : 5 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxHashGroupSize" : { + "oneOf" : [ { + "type" : "integer", + "default" : 200, + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "additionalProperties" : false, + "default" : { + "maxHashBitsDifferent" : 0, + "maxHashGroupSize" : 200 + } + } + }, + "additionalProperties" : false, + "required" : [ "features" ], + "default" : { + "features" : { + "type" : "featureSource:reference", + "auto" : true + }, + "pairing" : { + "maxHashBitsDifferent" : 0, + "maxHashGroupSize" : 200 + } + } + }, + "validationFilters" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/validationCriteria" + }, + "default" : [ ] + }, + "validation" : { + "allOf" : [ { + "$ref" : "#/definitions/validationCriteria" + }, { + "default" : { + "min" : 0.0, + "max" : 1.7976931348623157E308, + "debug" : false, + "pairwiseSimilarity" : { + "type" : "pairwiseSimilarity:featureIntersectionToUnionRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + } + } ] + }, + "output" : { + "type" : "object", + "properties" : { + "explanations" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "diagnostics" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "limit" : { + "oneOf" : [ { + "type" : "integer", + "minimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "additionalProperties" : false, + "default" : { + "explanations" : false, + "diagnostics" : true + } + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + }, + "useCache" : { + "type" : "boolean", + "default" : true + } + }, + "additionalProperties" : false, + "required" : [ "type", "query", "hashGrouping", "validation" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "documentPairs:duplicates", + "query" : null, + "documentPairFilter" : { + "query" : { + "type" : "query:all" + }, + "countCondition" : "ONE_OR_MORE" + }, + "hashGrouping" : { + "features" : { + "type" : "featureSource:reference", + "auto" : true + }, + "pairing" : { + "maxHashBitsDifferent" : 0, + "maxHashGroupSize" : 200 + } + }, + "validation" : { + "min" : 0.0, + "max" : 1.7976931348623157E308, + "debug" : false, + "pairwiseSimilarity" : { + "type" : "pairwiseSimilarity:featureIntersectionToUnionRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "validationFilters" : [ ], + "output" : { + "explanations" : false, + "diagnostics" : true + } + } + }, + "pairwiseSimilarity" : { + "oneOf" : [ { + "$ref" : "#/definitions/pairwiseSimilarity:reference" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionMinRatio" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionToUnionRatio" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionSize" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:documentOverlapRatio" + } ] + }, + "pairwiseSimilarity:featureIntersectionToUnionRatio" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "pairwiseSimilarity:featureIntersectionToUnionRatio" + }, + "features" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "features" ], + "default" : { + "type" : "pairwiseSimilarity:featureIntersectionToUnionRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "pairwiseSimilarity:featureIntersectionMinRatio" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "pairwiseSimilarity:featureIntersectionMinRatio" + }, + "features" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "features" ], + "default" : { + "type" : "pairwiseSimilarity:featureIntersectionMinRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "pairwiseSimilarity:featureIntersectionSize" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "pairwiseSimilarity:featureIntersectionSize" + }, + "features" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "features" ], + "default" : { + "type" : "pairwiseSimilarity:featureIntersectionSize", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "pairwiseSimilarity:documentOverlapRatio" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "pairwiseSimilarity:documentOverlapRatio" + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/fields" + }, { + "default" : { + "type" : "fields:reference", + "auto" : true + } + } ] + }, + "ngramWindow" : { + "oneOf" : [ { + "type" : "integer", + "default" : 6, + "exclusiveMinimum" : 0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "crossFieldOverlaps" : { + "oneOf" : [ { + "type" : "boolean", + "default" : true + }, { + "$ref" : "#/definitions/var" + } ] + }, + "allowedGapRatio" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "computeDifferences" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "default" : { + "type" : "pairwiseSimilarity:documentOverlapRatio", + "fields" : { + "type" : "fields:reference", + "auto" : true + }, + "ngramWindow" : 6, + "crossFieldOverlaps" : true, + "allowedGapRatio" : 0.0, + "computeDifferences" : false + } + }, + "featureSource" : { + "oneOf" : [ { + "$ref" : "#/definitions/featureSource:reference" + }, { + "$ref" : "#/definitions/featureSource:sentences" + }, { + "$ref" : "#/definitions/featureSource:chunks" + }, { + "$ref" : "#/definitions/featureSource:values" + }, { + "$ref" : "#/definitions/featureSource:words" + }, { + "$ref" : "#/definitions/featureSource:labels" + }, { + "$ref" : "#/definitions/featureSource:flatten" + }, { + "$ref" : "#/definitions/featureSource:group" + }, { + "$ref" : "#/definitions/featureSource:ngrams" + }, { + "$ref" : "#/definitions/featureSource:count" + }, { + "$ref" : "#/definitions/featureSource:unique" + }, { + "$ref" : "#/definitions/featureSource:minhash" + }, { + "$ref" : "#/definitions/featureSource:simhash" + } ] + }, + "featureSource:sentences" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:sentences" + }, + "minCharacters" : { + "oneOf" : [ { + "type" : "integer", + "default" : 40 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/fields" + }, { + "default" : { + "type" : "fields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:sentences", + "minCharacters" : 40, + "fields" : { + "type" : "fields:reference", + "auto" : true + } + } + }, + "featureSource:chunks" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:chunks" + }, + "minCharacters" : { + "oneOf" : [ { + "type" : "integer", + "default" : 80 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "modulo" : { + "oneOf" : [ { + "type" : "integer", + "default" : 5 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/fields" + }, { + "default" : { + "type" : "fields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:chunks", + "fields" : { + "type" : "fields:reference", + "auto" : true + }, + "modulo" : 5, + "minCharacters" : 80 + } + }, + "featureSource:values" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:values" + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/fields" + }, { + "default" : { + "type" : "fields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:values", + "fields" : { + "type" : "fields:reference", + "auto" : true + } + } + }, + "featureSource:words" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:words" + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/fields" + }, { + "default" : { + "type" : "fields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:words", + "fields" : { + "type" : "fields:reference", + "auto" : true + } + } + }, + "featureSource:labels" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:labels" + }, + "minDocFrequency" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxDocFrequency" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fields" : { + "allOf" : [ { + "$ref" : "#/definitions/featureFields" + }, { + "default" : { + "type" : "featureFields:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "fields" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:labels", + "minDocFrequency" : 1, + "maxDocFrequency" : "unlimited", + "fields" : { + "type" : "featureFields:reference", + "auto" : true + } + } + }, + "featureSource:flatten" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:flatten" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:flatten", + "source" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "featureSource:group" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:group" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:group", + "source" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "featureSource:ngrams" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:ngrams" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "window" : { + "oneOf" : [ { + "type" : "integer", + "default" : 10 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source", "window" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:ngrams", + "source" : { + "type" : "featureSource:reference", + "auto" : true + }, + "window" : 10 + } + }, + "featureSource:count" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:count" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "minFeatureCount" : { + "oneOf" : [ { + "type" : "integer", + "default" : 1 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "maxFeatureCount" : { + "oneOf" : [ { + "allOf" : [ { + "$ref" : "#/definitions/limit" + }, { + "default" : "unlimited" + } ] + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:count", + "source" : { + "type" : "featureSource:reference", + "auto" : true + }, + "minFeatureCount" : 1, + "maxFeatureCount" : "unlimited" + } + }, + "featureSource:unique" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:unique" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:unique", + "source" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "featureSource:minhash" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:minhash" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "functionCount" : { + "oneOf" : [ { + "type" : "integer", + "default" : 128 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:minhash", + "source" : { + "type" : "featureSource:reference", + "auto" : true + }, + "functionCount" : 128 + } + }, + "featureSource:simhash" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:simhash" + }, + "source" : { + "allOf" : [ { + "$ref" : "#/definitions/featureSource" + }, { + "default" : { + "type" : "featureSource:reference", + "auto" : true + } + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "required" : [ "type", "source" ], + "defaultSnippets" : [ ], + "default" : { + "type" : "featureSource:simhash", + "source" : { + "type" : "featureSource:reference", + "auto" : true + } + } + }, + "validationCriteria" : { + "type" : "object", + "properties" : { + "comment" : { + "$ref" : "#/definitions/comment" + }, + "pairwiseSimilarity" : { + "allOf" : [ { + "$ref" : "#/definitions/pairwiseSimilarity" + }, { + "default" : { + "type" : "pairwiseSimilarity:featureIntersectionToUnionRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + } ] + }, + "min" : { + "oneOf" : [ { + "type" : "number", + "default" : 0.0, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "max" : { + "oneOf" : [ { + "type" : "number", + "default" : 1.7976931348623157E308, + "minimum" : 0.0 + }, { + "$ref" : "#/definitions/var" + } ] + }, + "debug" : { + "oneOf" : [ { + "type" : "boolean", + "default" : false + }, { + "$ref" : "#/definitions/var" + } ] + } + }, + "additionalProperties" : false, + "required" : [ "pairwiseSimilarity", "min" ], + "default" : { + "min" : 0.0, + "max" : 1.7976931348623157E308, + "debug" : false, + "pairwiseSimilarity" : { + "type" : "pairwiseSimilarity:featureIntersectionToUnionRatio", + "features" : { + "type" : "featureSource:reference", + "auto" : true + } + } + } + }, + "taskSpec" : { + "type" : "object", + "properties" : { + "name" : { + "oneOf" : [ { + "type" : "string" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "durationMs" : { + "oneOf" : [ { + "type" : "integer" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "progressAvailable" : { + "oneOf" : [ { + "type" : "boolean" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "fail" : { + "oneOf" : [ { + "type" : "boolean" + }, { + "$ref" : "#/definitions/var" + } ] + }, + "tasks" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/taskSpec" + } + } + }, + "additionalProperties" : false, + "required" : [ ], + "defaultSnippets" : [ { + "body" : { + "name" : "${1:Task}", + "durationMs" : "^${2:2000}" + } + } ] + }, + "debug:progress" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "debug:progress" + }, + "tasks" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/taskSpec" + }, + "default" : [ { + "name" : "Task", + "durationMs" : 2000, + "progressAvailable" : true, + "tasks" : [ ], + "fail" : false + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type" ], + "defaultSnippets" : [ { + "body" : { + "type" : "debug:progress", + "tasks" : [ "^$0" ] + } + } ], + "default" : { + "type" : "debug:progress", + "tasks" : [ { + "name" : "Task", + "durationMs" : 2000, + "progressAvailable" : true, + "tasks" : [ ], + "fail" : false + } ] + } + }, + "debug:explain" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "debug:explain" + }, + "documents" : { + "allOf" : [ { + "$ref" : "#/definitions/documents" + }, { + "default" : { + "type" : "documents:reference", + "auto" : true + } + } ] + }, + "query" : { + "allOf" : [ { + "$ref" : "#/definitions/query" + }, { + "default" : null + } ] + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "name" : { + "type" : "string" + } + }, + "additionalProperties" : false, + "required" : [ "type", "documents" ], + "defaultSnippets" : [ { + "body" : { + "type" : "debug:explain", + "documents" : "^$0" + } + } ], + "default" : { + "type" : "debug:explain", + "documents" : { + "type" : "documents:reference", + "auto" : true + }, + "query" : null + } + }, + "component" : { + "oneOf" : [ { + "$ref" : "#/definitions/query:all" + }, { + "$ref" : "#/definitions/query:string" + }, { + "$ref" : "#/definitions/query:forLabels" + }, { + "$ref" : "#/definitions/query:forFieldValues" + }, { + "$ref" : "#/definitions/query:filter" + }, { + "$ref" : "#/definitions/query:complement" + }, { + "$ref" : "#/definitions/query:composite" + }, { + "$ref" : "#/definitions/query:fromDocuments" + }, { + "$ref" : "#/definitions/queryParser:project" + }, { + "$ref" : "#/definitions/queryParser:enhanced" + }, { + "$ref" : "#/definitions/labelFilter:composite" + }, { + "$ref" : "#/definitions/labelFilter:complement" + }, { + "$ref" : "#/definitions/labelFilter:tokenCount" + }, { + "$ref" : "#/definitions/labelFilter:characterCount" + }, { + "$ref" : "#/definitions/labelFilter:surface" + }, { + "$ref" : "#/definitions/labelFilter:autoStopLabels" + }, { + "$ref" : "#/definitions/labelFilter:dictionary" + }, { + "$ref" : "#/definitions/labelFilter:hasEmbedding" + }, { + "$ref" : "#/definitions/labelFilter:acceptAll" + }, { + "$ref" : "#/definitions/labelFilter:acceptLabels" + }, { + "$ref" : "#/definitions/labelFilter:rejectLabels" + }, { + "$ref" : "#/definitions/labelListFilter:acceptAll" + }, { + "$ref" : "#/definitions/labelListFilter:truncatedPhrases" + }, { + "$ref" : "#/definitions/labelCount:fixed" + }, { + "$ref" : "#/definitions/labelCount:unlimited" + }, { + "$ref" : "#/definitions/labelCount:progressive" + }, { + "$ref" : "#/definitions/labelScorer:identity" + }, { + "$ref" : "#/definitions/labelScorer:composite" + }, { + "$ref" : "#/definitions/labelScorer:df" + }, { + "$ref" : "#/definitions/labelScorer:idf" + }, { + "$ref" : "#/definitions/labelScorer:tf" + }, { + "$ref" : "#/definitions/labelScorer:probabilityRatio" + }, { + "$ref" : "#/definitions/labelCollector:topFromFeatureFields" + }, { + "$ref" : "#/definitions/labelAggregator:topWeight" + }, { + "$ref" : "#/definitions/fields:simple" + }, { + "$ref" : "#/definitions/featureFields:simple" + }, { + "$ref" : "#/definitions/contentFields:simple" + }, { + "$ref" : "#/definitions/contentFields:grouped" + }, { + "$ref" : "#/definitions/contentFields:empty" + }, { + "$ref" : "#/definitions/featureSource:sentences" + }, { + "$ref" : "#/definitions/featureSource:chunks" + }, { + "$ref" : "#/definitions/featureSource:values" + }, { + "$ref" : "#/definitions/featureSource:words" + }, { + "$ref" : "#/definitions/featureSource:labels" + }, { + "$ref" : "#/definitions/featureSource:flatten" + }, { + "$ref" : "#/definitions/featureSource:group" + }, { + "$ref" : "#/definitions/featureSource:ngrams" + }, { + "$ref" : "#/definitions/featureSource:count" + }, { + "$ref" : "#/definitions/featureSource:unique" + }, { + "$ref" : "#/definitions/featureSource:minhash" + }, { + "$ref" : "#/definitions/featureSource:simhash" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionMinRatio" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionToUnionRatio" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:featureIntersectionSize" + }, { + "$ref" : "#/definitions/pairwiseSimilarity:documentOverlapRatio" + }, { + "$ref" : "#/definitions/matrixRows:keywordDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrixRows:knnVectorsSimilarity" + }, { + "$ref" : "#/definitions/matrixRows:fromMatrix" + }, { + "$ref" : "#/definitions/dictionary:glob" + }, { + "$ref" : "#/definitions/dictionary:regex" + }, { + "$ref" : "#/definitions/dictionary:project" + }, { + "$ref" : "#/definitions/dictionary:all" + }, { + "$ref" : "#/definitions/dictionary:queryTerms" + } ] + }, + "stage" : { + "oneOf" : [ { + "$ref" : "#/definitions/documents:byQuery" + }, { + "$ref" : "#/definitions/documents:byId" + }, { + "$ref" : "#/definitions/documents:embeddingNearestNeighbors" + }, { + "$ref" : "#/definitions/documents:composite" + }, { + "$ref" : "#/definitions/documents:fromMatrixColumns" + }, { + "$ref" : "#/definitions/documents:fromClusterExemplars" + }, { + "$ref" : "#/definitions/documents:fromClusterMembers" + }, { + "$ref" : "#/definitions/documents:sample" + }, { + "$ref" : "#/definitions/documents:contrastScore" + }, { + "$ref" : "#/definitions/documents:rwmd" + }, { + "$ref" : "#/definitions/documents:fromDocumentPairs" + }, { + "$ref" : "#/definitions/documentPairs:duplicates" + }, { + "$ref" : "#/definitions/documentPairs:all" + }, { + "$ref" : "#/definitions/labels:byPrefix" + }, { + "$ref" : "#/definitions/labels:direct" + }, { + "$ref" : "#/definitions/labels:fromDocuments" + }, { + "$ref" : "#/definitions/labels:fromText" + }, { + "$ref" : "#/definitions/labels:scored" + }, { + "$ref" : "#/definitions/labels:embeddingNearestNeighbors" + }, { + "$ref" : "#/definitions/labels:composite" + }, { + "$ref" : "#/definitions/matrix:direct" + }, { + "$ref" : "#/definitions/matrix:fromMatrixRows" + }, { + "$ref" : "#/definitions/matrix:cooccurrenceLabelSimilarity" + }, { + "$ref" : "#/definitions/matrix:keywordDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrix:knn2dDistanceSimilarity" + }, { + "$ref" : "#/definitions/matrix:knnVectorsSimilarity" + }, { + "$ref" : "#/definitions/matrix:keywordLabelDocumentSimilarity" + }, { + "$ref" : "#/definitions/matrix:elementWiseProduct" + }, { + "$ref" : "#/definitions/vector:labelEmbedding" + }, { + "$ref" : "#/definitions/vector:documentEmbedding" + }, { + "$ref" : "#/definitions/vector:composite" + }, { + "$ref" : "#/definitions/vector:estimateFromContext" + }, { + "$ref" : "#/definitions/clusters:ap" + }, { + "$ref" : "#/definitions/clusters:fromMatrixColumns" + }, { + "$ref" : "#/definitions/clusters:withRemappedDocuments" + }, { + "$ref" : "#/definitions/clusters:byValues" + }, { + "$ref" : "#/definitions/labelClusters:documentClusterLabels" + }, { + "$ref" : "#/definitions/embedding2d:lv" + }, { + "$ref" : "#/definitions/embedding2d:lvOverlay" + }, { + "$ref" : "#/definitions/embedding2d:transferred" + }, { + "$ref" : "#/definitions/vectors:precomputedLabelEmbeddings" + }, { + "$ref" : "#/definitions/vectors:precomputedDocumentEmbeddings" + }, { + "$ref" : "#/definitions/documentContent" + }, { + "$ref" : "#/definitions/documentLabels" + }, { + "$ref" : "#/definitions/values:fromDocumentField" + }, { + "$ref" : "#/definitions/documentOverlap" + }, { + "$ref" : "#/definitions/stats:documents" + }, { + "$ref" : "#/definitions/debug:explain" + }, { + "$ref" : "#/definitions/debug:progress" + } ] + }, + "variableValue" : { + "anyOf" : [ { + "type" : "integer" + }, { + "type" : "number" + }, { + "type" : "boolean" + }, { + "type" : "string" + }, { + "type" : "null" + }, { + "type" : "array", + "items" : { + "type" : "string" + } + } ] + }, + "variable" : { + "type" : "object", + "properties" : { + "value" : { + "$ref" : "#/definitions/variableValue" + }, + "name" : { + "type" : "string" + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "meta" : { + "type" : "object", + "properties" : { + "ui" : { + "type" : "string", + "enum" : [ "textarea", "tags" ] + } + } + } + }, + "required" : [ "value" ], + "additionalProperties" : false, + "defaultSnippets" : [ { + "description" : "Variable with documentation", + "body" : { + "name" : "${1:Variable name}", + "comment" : "${2:Variable description}", + "value" : "^$0" + } + }, { + "description" : "Variable without documentation", + "body" : { + "value" : "^$0" + } + } ] + }, + "variableGroup" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "comment" : { + "$ref" : "#/definitions/comment" + }, + "variables" : { + "$ref" : "#/definitions/variableContainer" + } + }, + "required" : [ "variables" ], + "additionalProperties" : false, + "defaultSnippets" : [ { + "description" : "Group of variables", + "body" : { + "name" : "${1:Group name}", + "comment" : "${2:Group description}", + "variables" : { + "${3:variable1}" : "^$0" + } + } + } ] + }, + "variableGroupOrVariable" : { + "oneOf" : [ { + "$ref" : "#/definitions/variable" + }, { + "$ref" : "#/definitions/variableGroup" + } ] + }, + "variableContainer" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/variableGroupOrVariable" + } + }, + "labelFilter:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelFilter:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelFilter:reference", + "use" : "$0" + } + } ] + }, + "labelListFilter:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelListFilter:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelListFilter:reference", + "use" : "$0" + } + } ] + }, + "labelCollector:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelCollector:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelCollector:reference", + "use" : "$0" + } + } ] + }, + "query:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "query:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "query:reference", + "use" : "$0" + } + } ] + }, + "fields:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "fields:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "fields:reference", + "use" : "$0" + } + } ] + }, + "featureFields:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureFields:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "featureFields:reference", + "use" : "$0" + } + } ] + }, + "contentFields:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "contentFields:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "contentFields:reference", + "use" : "$0" + } + } ] + }, + "matrixRows:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrixRows:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrixRows:reference", + "use" : "$0" + } + } ] + }, + "featureSource:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "featureSource:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "featureSource:reference", + "use" : "$0" + } + } ] + }, + "pairwiseSimilarity:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "pairwiseSimilarity:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "pairwiseSimilarity:reference", + "use" : "$0" + } + } ] + }, + "dictionary:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "dictionary:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "dictionary:reference", + "use" : "$0" + } + } ] + }, + "labelCount:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelCount:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelCount:reference", + "use" : "$0" + } + } ] + }, + "queryParser:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "queryParser:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "queryParser:reference", + "use" : "$0" + } + } ] + }, + "documents:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documents:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "documents:reference", + "use" : "$0" + } + } ] + }, + "labels:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labels:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labels:reference", + "use" : "$0" + } + } ] + }, + "vector:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vector:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "vector:reference", + "use" : "$0" + } + } ] + }, + "matrix:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "matrix:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "matrix:reference", + "use" : "$0" + } + } ] + }, + "clusters:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "clusters:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "clusters:reference", + "use" : "$0" + } + } ] + }, + "labelClusters:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "labelClusters:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "labelClusters:reference", + "use" : "$0" + } + } ] + }, + "embedding2d:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "embedding2d:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "embedding2d:reference", + "use" : "$0" + } + } ] + }, + "vectors:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "vectors:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "vectors:reference", + "use" : "$0" + } + } ] + }, + "values:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "values:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "values:reference", + "use" : "$0" + } + } ] + }, + "documentPairs:reference" : { + "type" : "object", + "properties" : { + "type" : { + "const" : "documentPairs:reference" + }, + "use" : { + "type" : "string" + }, + "auto" : { + "type" : "boolean" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "use" ] + }, { + "properties" : { + "auto" : { + "const" : true + } + }, + "required" : [ "auto" ] + } ], + "defaultSnippets" : [ { + "body" : { + "type" : "documentPairs:reference", + "use" : "$0" + } + } ] + } + }, + "properties" : { + "variables" : { + "$ref" : "#/definitions/variableContainer" + }, + "components" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/component" + }, + "defaultSnippets" : [ { + "body" : { } + } ] + }, + "stages" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/stage" + } + }, + "output" : { + "type" : "object", + "properties" : { + "progress" : { + "type" : "boolean" + }, + "request" : { + "type" : "boolean" + }, + "stages" : { + "type" : "array", + "items" : { + "type" : "string" + }, + "uniqueItems" : true, + "defaultSnippets" : [ { + "body" : [ "^$0" ] + } ] + } + } + }, + "useCache" : { + "type" : "boolean", + "default" : true + }, + "tags" : { + "description" : "One or more tags that describe this request. Used by the API sandbox to label requests.", + "type" : "array", + "items" : { + "type" : "string" + } + }, + "name" : { + "type" : "string" + }, + "comment" : { + "$ref" : "#/definitions/comment" + } + }, + "additionalProperties" : false, + "defaultSnippets" : [ { + "body" : { + "name" : "Documents by query", + "comment" : "Retrieves identifiers of documents that match the provided search query. This request is useful as a building block for larger requests, such as 'Documents by query with contents' or 'Labels from documents'.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents by query", + "description" : "Retrieves identifiers of documents that match the provided search query. This request is useful as a building block for larger requests, such as 'Documents by query with contents' or 'Labels from documents'." + }, { + "body" : { + "name" : "Documents by query statistics", + "comment" : "Computes the number of documents matching the provided search query. This request is useful to get the number of documents matching a query without downloading the, potentially large, list of document identifiers.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000, + "accurateHitCount" : true + }, + "documentStats" : { + "type" : "stats:documents" + } + }, + "output" : { + "stages" : [ "documentStats" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents by query statistics", + "description" : "Computes the number of documents matching the provided search query. This request is useful to get the number of documents matching a query without downloading the, potentially large, list of document identifiers." + }, { + "body" : { + "name" : "Documents by query with contents", + "comment" : "Retrieves identifiers and content of documents that match the provided query. Use this request for display purposes.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 10 + }, + "documentContent" : { + "type" : "documentContent" + } + }, + "output" : { + "stages" : [ "documentContent", "documents" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents by query with contents", + "description" : "Retrieves identifiers and content of documents that match the provided query. Use this request for display purposes." + }, { + "body" : { + "name" : "Documents sample by query", + "comment" : "Selects a uniform random sample of documents from the pool of all documents matching the provided query. This request is a useful building block when processing the whole pool of documents may be costly while processing a small sample the may also provide reasonable results.", + "stages" : { + "documents" : { + "type" : "documents:sample", + "limit" : 100, + "query" : { + "type" : "query:string", + "query" : "" + } + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents sample by query", + "description" : "Selects a uniform random sample of documents from the pool of all documents matching the provided query. This request is a useful building block when processing the whole pool of documents may be costly while processing a small sample the may also provide reasonable results." + }, { + "body" : { + "name" : "Documents with content and label occurrences", + "comment" : "Retrieves the contents and labels occurring in each document matching the query. Use this request to get labels occurring in individual documents for display purposes. To collect an aggregated list of labels from multiple documents, see the 'Labels from documents' request.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "photon" + }, + "limit" : 10 + }, + "documentContent" : { + "type" : "documentContent" + }, + "documentLabels" : { + "type" : "documentLabels" + } + }, + "output" : { + "stages" : [ "documents", "documentContent", "documentLabels" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents with content and label occurrences", + "description" : "Retrieves the contents and labels occurring in each document matching the query. Use this request to get labels occurring in individual documents for display purposes. To collect an aggregated list of labels from multiple documents, see the 'Labels from documents' request." + }, { + "body" : { + "name" : "Documents with content and filtered label occurrences", + "comment" : "Retrieves the contents and labels occurring in each document matching the query, narrowing down the list of labels. Use this request to get labels occurring in individual documents for display purposes. To collect an aggregated list of labels from multiple documents with label filtering applied, see the 'Labels from documents with filtering' request.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "photon" + }, + "limit" : 10 + }, + "labels" : { + "type" : "labels:fromDocuments" + }, + "documentContent" : { + "type" : "documentContent" + }, + "documentLabels" : { + "type" : "documentLabels", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:acceptLabels" + } + } + } + }, + "output" : { + "stages" : [ "documents", "labels", "documentContent", "documentLabels" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Documents with content and filtered label occurrences", + "description" : "Retrieves the contents and labels occurring in each document matching the query, narrowing down the list of labels. Use this request to get labels occurring in individual documents for display purposes. To collect an aggregated list of labels from multiple documents with label filtering applied, see the 'Labels from documents with filtering' request." + }, { + "body" : { + "name" : "Labels by prefix", + "comment" : "Retrieves labels starting with the provided prefix. This request is useful for building a label selection user interface with a prefix-based suggestion list.", + "stages" : { + "labels" : { + "type" : "labels:byPrefix", + "prefix" : "test", + "limit" : 20 + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels by prefix", + "description" : "Retrieves labels starting with the provided prefix. This request is useful for building a label selection user interface with a prefix-based suggestion list." + }, { + "body" : { + "name" : "Labels by label embedding vector", + "comment" : "Retrieves labels that are semantically similar to the provided list of labels.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + } + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels by label embedding vector", + "description" : "Retrieves labels that are semantically similar to the provided list of labels." + }, { + "body" : { + "name" : "Labels by label embedding vector with filtering", + "comment" : "Retrieves labels that are semantically similar to the provided list of labels while filtering out some of the results. The request finds labels similar to 'photon' but excludes from results all labels that actually contain the word 'photon'.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + }, + "labelFilter" : { + "type" : "labelFilter:dictionary", + "exclude" : [ { + "type" : "dictionary:regex", + "entries" : [ ".*photon.*" ] + } ] + } + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels by label embedding vector with filtering", + "description" : "Retrieves labels that are semantically similar to the provided list of labels while filtering out some of the results. The request finds labels similar to 'photon' but excludes from results all labels that actually contain the word 'photon'." + }, { + "body" : { + "name" : "Labels from text", + "comment" : "Extracts labels occurring in the provided raw text. This request is a useful building block when processing text that is not present in the index. For example, this request can be a starting point for making a More-Like-This search where the seed document is an arbitrary piece of text, not present in the index.", + "stages" : { + "labels" : { + "type" : "labels:fromText", + "text" : "Text to extract labels from." + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels from text", + "description" : "Extracts labels occurring in the provided raw text. This request is a useful building block when processing text that is not present in the index. For example, this request can be a starting point for making a More-Like-This search where the seed document is an arbitrary piece of text, not present in the index." + }, { + "body" : { + "name" : "Labels from documents", + "comment" : "Retrieves labels that occur most frequently in the provided set of documents.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "photon" + }, + "limit" : 1000 + }, + "labels" : { + "type" : "labels:fromDocuments" + } + }, + "output" : { + "stages" : [ "labels" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels from documents", + "description" : "Retrieves labels that occur most frequently in the provided set of documents." + }, { + "body" : { + "name" : "Labels from documents with filtering", + "comment" : "Retrieves labels that occur most frequently in the provided documents, with additionally filtering applied.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + }, + "labels" : { + "type" : "labels:fromDocuments", + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "labelFilter" : { + "type" : "labelFilter:composite", + "labelFilters" : { + "dictionary" : { + "type" : "labelFilter:dictionary", + "exclude" : [ { + "type" : "dictionary:glob", + "entries" : [ "* project *" ] + } ] + }, + "default" : { + "type" : "labelFilter:reference", + "use" : "labelFilter" + } + } + } + } + } + } + }, + "output" : { + "stages" : [ "labels" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels from documents with filtering", + "description" : "Retrieves labels that occur most frequently in the provided documents, with additionally filtering applied." + }, { + "body" : { + "name" : "Labels from text with filtering", + "comment" : "Extracts labels occurring in the provided raw text. This request is a useful building block when processing text that is not present in the index. For example, this request can be a starting point for making a More-Like-This search where the seed document is an arbitrary piece of text, not present in the index.", + "variables" : { + "inputText" : { + "name" : "Input text", + "comment" : "Text from which to extract labels.", + "value" : "Using the effective Lagrangian for the low energy photon−photon interaction the lowest order photon self energy at finite temperature and in non−equilibrium is calculated within the real time formalism. The Debye mass, the dispersion relation, the dielectric tensor, and the velocity of light following from the photon self energy are discussed. As an application we consider the interaction of photons with the cosmic microwave background radiation.", + "meta" : { + "ui" : "textarea" + } + }, + "minLabelWords" : { + "name" : "Min label words", + "comment" : "Remove labels with fewer words than specified.", + "value" : 2 + }, + "removeCapitalized" : { + "name" : "Remove capitalized", + "value" : false + }, + "removeUppercase" : { + "name" : "Remove uppercase", + "value" : false + }, + "removeAcronyms" : { + "name" : "Remove acronyms", + "value" : false + }, + "exclusionsDictionary" : { + "name" : "Exclusions", + "comment" : "Remove labels matching any of the exclusions dictionary entries.", + "value" : [ "* preliminary *" ] + } + }, + "stages" : { + "labels" : { + "type" : "labels:fromText", + "text" : { + "@var" : "inputText" + }, + "labelFilter" : { + "type" : "labelFilter:composite", + "labelFilters" : { + "stopLabels" : { + "type" : "labelFilter:autoStopLabels" + }, + "surface" : { + "type" : "labelFilter:surface", + "removeCapitalized" : { + "@var" : "removeCapitalized" + }, + "removeUppercase" : { + "@var" : "removeUppercase" + }, + "removeAcronyms" : { + "@var" : "removeAcronyms" + } + }, + "tokens" : { + "type" : "labelFilter:tokenCount", + "minTokens" : { + "@var" : "minLabelWords" + }, + "maxTokens" : 5 + }, + "filter" : { + "type" : "labelFilter:dictionary", + "addAllProjectDictionaries" : true, + "exclude" : [ { + "type" : "dictionary:glob", + "entries" : { + "@var" : "exclusionsDictionary" + } + } ] + } + } + } + } + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels from text with filtering", + "description" : "Extracts labels occurring in the provided raw text. This request is a useful building block when processing text that is not present in the index. For example, this request can be a starting point for making a More-Like-This search where the seed document is an arbitrary piece of text, not present in the index." + }, { + "body" : { + "name" : "Labels from documents with tf-idf and probability ratio scores", + "comment" : "Computes TF-IDF and probability ratio scores for a list of labels collected from a set of documents. This request is useful to get additional statistics for a list of labels.", + "stages" : { + "documents" : { + "type" : "documents:sample", + "query" : { + "type" : "query:string", + "query" : "photon" + }, + "limit" : 10000 + }, + "labels" : { + "type" : "labels:fromDocuments" + }, + "tfidf" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:composite", + "scorers" : [ { + "type" : "labelScorer:tf" + }, { + "type" : "labelScorer:idf" + } ] + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "probabilityRatios" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:probabilityRatio" + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + } + }, + "output" : { + "stages" : [ "labels", "tfidf", "probabilityRatios" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Labels from documents with tf-idf and probability ratio scores", + "description" : "Computes TF-IDF and probability ratio scores for a list of labels collected from a set of documents. This request is useful to get additional statistics for a list of labels." + }, { + "body" : { + "name" : "Label frequency estimates for a list of labels", + "comment" : "Estimates the occurrence frequencies for a list of labels based on a sample of all documents in the index. Computing frequencies based on the whole index may be costly. An estimate is much faster to compute while retaining reasonable quality.", + "stages" : { + "scope" : { + "type" : "documents:sample", + "limit" : 10000, + "query" : { + "type" : "query:all" + } + }, + "labels" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + }, { + "label" : "electron" + }, { + "label" : "proton" + } ] + }, + "df" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:df" + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + } + }, + "output" : { + "stages" : [ "labels", "df" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Label frequency estimates for a list of labels", + "description" : "Estimates the occurrence frequencies for a list of labels based on a sample of all documents in the index. Computing frequencies based on the whole index may be costly. An estimate is much faster to compute while retaining reasonable quality." + }, { + "body" : { + "name" : "Label frequencies across a time series", + "comment" : "Computes occurrence frequencies for a list of labels across a set of time ranges. This request may be useful to create a chart showing the popularity of selected labels over time.", + "components" : { + "rangeQuery0" : { + "type" : "query:string", + "query" : "created:[2014-01-01 TO 2014-12-31]" + }, + "rangeQuery1" : { + "type" : "query:string", + "query" : "created:[2016-01-01 TO 2016-12-31]" + }, + "rangeQuery2" : { + "type" : "query:string", + "query" : "created:[2018-01-01 TO 2018-12-31]" + } + }, + "variables" : { + "limit" : { + "value" : 10000 + } + }, + "stages" : { + "labels" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + }, { + "label" : "electron" + }, { + "label" : "proton" + } ] + }, + "df0" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:df", + "scope" : { + "type" : "documents:sample", + "limit" : { + "@var" : "limit" + }, + "query" : { + "type" : "query:reference", + "use" : "rangeQuery0" + } + } + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "df1" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:df", + "scope" : { + "type" : "documents:sample", + "limit" : { + "@var" : "limit" + }, + "query" : { + "type" : "query:reference", + "use" : "rangeQuery1" + } + } + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "df2" : { + "type" : "labels:scored", + "scorer" : { + "type" : "labelScorer:df", + "scope" : { + "type" : "documents:sample", + "limit" : { + "@var" : "limit" + }, + "query" : { + "type" : "query:reference", + "use" : "rangeQuery2" + } + } + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + } + }, + "output" : { + "stages" : [ "labels", "df0", "df1", "df2" ] + }, + "tags" : [ "Documents and Labels" ] + }, + "label" : "Label frequencies across a time series", + "description" : "Computes occurrence frequencies for a list of labels across a set of time ranges. This request may be useful to create a chart showing the popularity of selected labels over time." + }, { + "body" : { + "name" : "Document clusters by keyword similarity", + "comment" : "Clusters a set of documents based on the common labels the documents share. Attempts to describe the clusters by top-frequency labels from cluster's documents. Fetches the content of documents being clustered.", + "variables" : { + "documents" : { + "name" : "Documents to cluster", + "variables" : { + "query" : { + "name" : "Documents query", + "comment" : "Defines the set of documents to cluster.", + "value" : "clustering" + }, + "limit" : { + "name" : "Max documents", + "comment" : "The maximum number of documents matching the query to select for clustering.", + "value" : 1000 + } + } + }, + "clustering" : { + "name" : "Clustering", + "variables" : { + "clusterCreationPreference" : { + "name" : "Cluster creation preference", + "comment" : "How many clusters to create. The more negative the preference, the fewer clusters. The closer the preference to 0, the more clusters.", + "value" : -1000 + }, + "clusterLinkingPreference" : { + "name" : "Cluster linking preference", + "comment" : "How many links to create between clusters. Softening of 0 creates unlinked, flat structure of clusters. Softening of 1.0 creates a highly-linked structure of clusters.", + "value" : 0 + }, + "maxSimilarDocuments" : { + "name" : "Max similar documents", + "comment" : "How many similar documents to find for each document in the similarity matrix. The larger the number of similar documents, the larger and more general the clusters and the longer clustering time.", + "value" : 10 + }, + "maxClusterLabels" : { + "name" : "Max cluster labels", + "comment" : "How many labels to use to label each cluster.", + "value" : 3 + } + } + } + }, + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "query" + } + }, + "limit" : { + "@var" : "limit" + } + }, + "content" : { + "type" : "documentContent", + "limit" : { + "@var" : "limit" + } + }, + "clusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:keywordDocumentSimilarity", + "maxNeighbors" : { + "@var" : "maxSimilarDocuments" + } + }, + "inputPreference" : { + "@var" : "clusterCreationPreference" + }, + "softening" : { + "@var" : "clusterLinkingPreference" + } + }, + "labelClusters" : { + "type" : "labelClusters:documentClusterLabels", + "maxLabels" : { + "@var" : "maxClusterLabels" + } + } + }, + "output" : { + "stages" : [ "documents", "content", "clusters", "labelClusters" ] + }, + "tags" : [ "Clustering" ] + }, + "label" : "Document clusters by keyword similarity", + "description" : "Clusters a set of documents based on the common labels the documents share. Attempts to describe the clusters by top-frequency labels from cluster's documents. Fetches the content of documents being clustered." + }, { + "body" : { + "name" : "Document clusters by embedding similarity", + "comment" : "Clusters a set of documents based on the document embedding vector similarity. Attempts to describe the clusters by top-frequency labels from cluster's documents. Fetches the content of documents being clustered.", + "variables" : { + "documents" : { + "name" : "Documents to cluster", + "variables" : { + "query" : { + "name" : "Documents query", + "comment" : "Defines the set of documents to cluster.", + "value" : "clustering" + }, + "limit" : { + "name" : "Max documents", + "comment" : "The maximum number of documents matching the query to select for clustering.", + "value" : 10000 + } + } + }, + "clustering" : { + "name" : "Clustering", + "variables" : { + "clusterCreationPreference" : { + "name" : "Cluster creation preference", + "comment" : "How many clusters to create. The more negative the preference, the fewer clusters. The closer the preference to 0, the more clusters.", + "value" : -1000 + }, + "clusterLinkingPreference" : { + "name" : "Cluster linking preference", + "comment" : "How many links to create between clusters. Softening of 0 creates unlinked, flat structure of clusters. Softening of 1.0 creates a highly-linked structure of clusters.", + "value" : 0 + }, + "maxSimilarDocuments" : { + "name" : "Max similar documents", + "comment" : "How many similar documents to find for each document in the similarity matrix. The larger the number of similar documents, the larger and more general the clusters and the longer clustering time.", + "value" : 10 + }, + "maxClusterLabels" : { + "name" : "Max cluster labels", + "comment" : "How many labels to use to label each cluster.", + "value" : 3 + } + } + } + }, + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "query" + } + }, + "limit" : { + "@var" : "limit" + } + }, + "content" : { + "type" : "documentContent", + "limit" : { + "@var" : "limit" + } + }, + "clusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings" + }, + "maxNeighbors" : { + "@var" : "maxSimilarDocuments" + } + }, + "inputPreference" : { + "@var" : "clusterCreationPreference" + }, + "softening" : { + "@var" : "clusterLinkingPreference" + } + }, + "labelClusters" : { + "type" : "labelClusters:documentClusterLabels", + "maxLabels" : { + "@var" : "maxClusterLabels" + } + } + }, + "output" : { + "stages" : [ "documents", "content", "clusters", "labelClusters" ] + }, + "tags" : [ "Clustering" ] + }, + "label" : "Document clusters by embedding similarity", + "description" : "Clusters a set of documents based on the document embedding vector similarity. Attempts to describe the clusters by top-frequency labels from cluster's documents. Fetches the content of documents being clustered." + }, { + "body" : { + "name" : "Label clusters by embedding similarity", + "comment" : "Groups semantically similar labels into clusters based on the embedding vector similarity.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + }, + "limit" : 100 + }, + "labelClusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedLabelEmbeddings", + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + } + } + }, + "output" : { + "stages" : [ "similarLabels", "labelClusters" ] + }, + "tags" : [ "Clustering" ] + }, + "label" : "Label clusters by embedding similarity", + "description" : "Groups semantically similar labels into clusters based on the embedding vector similarity." + }, { + "body" : { + "name" : "Label clusters by label co-occurrence similarity", + "comment" : "Groups labels into clusters based on how frequently they co-occur in documents in the index.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + }, + "limit" : 100 + }, + "labelClusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:cooccurrenceLabelSimilarity", + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + }, + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + } + }, + "output" : { + "stages" : [ "similarLabels", "labelClusters" ] + }, + "tags" : [ "Clustering" ] + }, + "label" : "Label clusters by label co-occurrence similarity", + "description" : "Groups labels into clusters based on how frequently they co-occur in documents in the index." + }, { + "body" : { + "name" : "Documents by document embedding vector", + "comment" : "Retrieves identifiers of documents that are most similar to the query document, based on multidimensional document embedding similarity. Use this request to find a list of documents that are semantically similar to one or more input documents.", + "stages" : { + "queryDocuments" : { + "type" : "documents:byId", + "documents" : [ { + "id" : 0 + } ] + }, + "similarDocuments" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:documentEmbedding", + "documents" : { + "type" : "documents:reference", + "use" : "queryDocuments" + } + }, + "limit" : 10 + } + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "Documents by document embedding vector", + "description" : "Retrieves identifiers of documents that are most similar to the query document, based on multidimensional document embedding similarity. Use this request to find a list of documents that are semantically similar to one or more input documents." + }, { + "body" : { + "name" : "Documents by document embedding vector with contents", + "comment" : "Retrieves the content of documents that are most similar to the query document, based on multidimensional document embedding similarity. Use this request to find a list of documents that are semantically similar to one or more input documents.", + "stages" : { + "queryDocuments" : { + "type" : "documents:byId", + "documents" : [ { + "id" : 0 + } ] + }, + "similarDocuments" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:documentEmbedding", + "documents" : { + "type" : "documents:reference", + "use" : "queryDocuments" + } + }, + "limit" : 10 + }, + "queryDocumentsContents" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "queryDocuments" + } + }, + "similarDocumentsContents" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "similarDocuments" + } + } + }, + "output" : { + "stages" : [ "queryDocumentsContents", "similarDocumentsContents", "queryDocuments", "similarDocuments" ] + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "Documents by document embedding vector with contents", + "description" : "Retrieves the content of documents that are most similar to the query document, based on multidimensional document embedding similarity. Use this request to find a list of documents that are semantically similar to one or more input documents." + }, { + "body" : { + "name" : "Documents by label embedding vector", + "comment" : "Retrieves identifiers of documents that are most similar to the provided labels, based on multidimensional embeddings of labels and documents. Use this request to find documents that are semantically similar to one or more labels. Compared to a traditional keyword search, the embedding similarity search may return documents that don't contain any of the input labels, but do contain a synonym of the input labels.", + "stages" : { + "queryLabels" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "documents" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding" + }, + "limit" : 10 + } + }, + "output" : { + "stages" : [ "documents" ] + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "Documents by label embedding vector", + "description" : "Retrieves identifiers of documents that are most similar to the provided labels, based on multidimensional embeddings of labels and documents. Use this request to find documents that are semantically similar to one or more labels. Compared to a traditional keyword search, the embedding similarity search may return documents that don't contain any of the input labels, but do contain a synonym of the input labels." + }, { + "body" : { + "name" : "Documents by label embedding vector with content", + "comment" : "Retrieves content of documents that are most similar to the provided labels, based on multidimensional embeddings of labels and documents. Use this request to find documents that are semantically similar to one or more labels. Compared to a traditional keyword search, the embedding similarity search may return documents that don't contain any of the input labels, but do contain a synonym of the input labels.", + "stages" : { + "queryLabels" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "documents" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding" + }, + "limit" : 10 + }, + "content" : { + "type" : "documentContent" + } + }, + "output" : { + "stages" : [ "content", "documents" ] + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "Documents by label embedding vector with content", + "description" : "Retrieves content of documents that are most similar to the provided labels, based on multidimensional embeddings of labels and documents. Use this request to find documents that are semantically similar to one or more labels. Compared to a traditional keyword search, the embedding similarity search may return documents that don't contain any of the input labels, but do contain a synonym of the input labels." + }, { + "body" : { + "name" : "More-Like-This document, keyword-based", + "comment" : "Finds documents that are similar to the provided 'seed' document. The keyword-based method extracts top-frequency labels from the seed document and retrieves other documents containing those labels.", + "variables" : { + "seedQuery" : { + "name" : "Seed query", + "comment" : "Defines the set of documents for which to find other similar documents. In most cases, the query should return one or a small number of documents. A by-document-id query is usually appropriate here.", + "value" : "id:1703.01028" + }, + "maxSeedDocuments" : { + "name" : "Max seed documents", + "comment" : "Maximum number of documents matching 'seedQuery' to use for the similarity search.", + "value" : 1 + }, + "maxSimilarDocuments" : { + "name" : "Max similar documents", + "comment" : "Maximum number of similar documents to find.", + "value" : 20 + } + }, + "stages" : { + "seedDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "seedQuery" + } + }, + "limit" : { + "@var" : "maxSeedDocuments" + } + }, + "seedLabels" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + }, + "maxLabels" : { + "type" : "labelCount:progressive", + "multiplier" : 1000 + }, + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "tieResolution" : "EXTEND", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "tieResolution" : "EXTEND" + } + } + }, + "mltDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "use" : "seedLabels" + } + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + }, + "seedDocumentsContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + }, + "queries" : { + "s" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + }, + "k" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "mltDocuments" + } + } + } + }, + "mltDocumentContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "mltDocuments" + }, + "queries" : { + "s" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + }, + "k" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "mltDocuments" + } + } + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + } + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "More-Like-This document, keyword-based", + "description" : "Finds documents that are similar to the provided 'seed' document. The keyword-based method extracts top-frequency labels from the seed document and retrieves other documents containing those labels." + }, { + "body" : { + "name" : "More-Like-This document, keyword-based with label and results filtering", + "comment" : "Finds documents that are similar to the provided 'seed' document. Compared to the simpler 'More-Like-This document, keyword-based' request, this one adds the possibility to exclude some of the seed labels and also filter the MLT document list.", + "variables" : { + "seedQuery" : { + "name" : "Seed query", + "comment" : "Defines the set of documents for which to find other similar documents. In most cases, the query should return one or a small number of documents. A by-document-id query is usually appropriate here.", + "value" : "id:1703.01028" + }, + "maxSeedDocuments" : { + "name" : "Max seed documents", + "comment" : "Maximum number of documents matching 'seedQuery' to use for the similarity search.", + "value" : 1 + }, + "maxSimilarDocuments" : { + "name" : "Max similar documents", + "comment" : "Maximum number of similar documents to find.", + "value" : 20 + }, + "similarDocumentsFilterQuery" : { + "name" : "Similar documents filter query", + "comment" : "Additional condition all similar documents must meet. Use the query to narrow down the results to, for example, documents from a specific category.", + "value" : "set:math*" + }, + "seedLabelExclusionDictionaryEntries" : { + "name" : "Seed label exclusion dictionary entries", + "comment" : "Exclusion dictionary entries to apply to filter labels collected from seed documents. Use the entries to remove unwanted seed labels.", + "value" : [ "* paradox *" ] + } + }, + "stages" : { + "seedDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "seedQuery" + } + }, + "limit" : { + "@var" : "maxSeedDocuments" + } + }, + "seedLabels" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + }, + "maxLabels" : { + "type" : "labelCount:progressive", + "multiplier" : 1000 + }, + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "tieResolution" : "EXTEND", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "tieResolution" : "EXTEND", + "labelFilter" : { + "type" : "labelFilter:composite", + "labelFilters" : { + "autoStopLabels" : { + "type" : "labelFilter:autoStopLabels" + }, + "dictionary" : { + "type" : "labelFilter:dictionary", + "exclude" : [ { + "type" : "dictionary:glob", + "entries" : { + "@var" : "seedLabelExclusionDictionaryEntries" + } + } ] + } + } + } + } + } + }, + "mltDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:filter", + "query" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "use" : "seedLabels" + } + }, + "filter" : { + "type" : "query:string", + "query" : { + "@var" : "similarDocumentsFilterQuery" + } + } + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + }, + "seedDocumentsContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + }, + "mltDocumentContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "mltDocuments" + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + } + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "More-Like-This document, keyword-based with label and results filtering", + "description" : "Finds documents that are similar to the provided 'seed' document. Compared to the simpler 'More-Like-This document, keyword-based' request, this one adds the possibility to exclude some of the seed labels and also filter the MLT document list." + }, { + "body" : { + "name" : "More-Like-This document, embedding-based", + "comment" : "Finds documents that are similar to the provided 'seed' document. The embedding-based method finds documents whose embedding vectors are similar to the vector of the seed document.", + "variables" : { + "seedQuery" : { + "name" : "Seed query", + "comment" : "Defines the set of documents for which to find other similar documents. In most cases, the query should return one or a small number of documents. A by-document-id query is usually appropriate here.", + "value" : "id:1703.01028" + }, + "maxSeedDocuments" : { + "name" : "Max seed documents", + "comment" : "Maximum number of documents matching 'seedQuery' to use for the similarity search.", + "value" : 1 + }, + "maxSimilarDocuments" : { + "name" : "Max similar documents", + "comment" : "Maximum number of similar documents to find.", + "value" : 20 + } + }, + "stages" : { + "seedDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "seedQuery" + } + }, + "limit" : { + "@var" : "maxSeedDocuments" + } + }, + "mltDocuments" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:documentEmbedding", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + }, + "seedDocumentsContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + }, + "mltDocumentContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "mltDocuments" + }, + "limit" : { + "@var" : "maxSimilarDocuments" + } + } + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "More-Like-This document, embedding-based", + "description" : "Finds documents that are similar to the provided 'seed' document. The embedding-based method finds documents whose embedding vectors are similar to the vector of the seed document." + }, { + "body" : { + "name" : "More-Like-This document, keyword- and embedding-based with content, label and results filtering", + "comment" : "Finds documents that are similar to the provided 'seed' document. This request combines results from the keyword- and embedding-based methods. Additionally, it allows filtering and providing additional seed labels as well as filtering of MLT results.", + "variables" : { + "seeds" : { + "name" : "Search seed definition", + "comment" : "Defines the 'seed' to use for the similar documents search.", + "variables" : { + "seedQuery" : { + "name" : "Seed query", + "comment" : "Defines the set of documents for which to find other similar documents. In most cases, the query should return one or a small number of documents. A by-document-id query is usually appropriate here.", + "value" : "id:1703.01028" + }, + "maxSeedDocuments" : { + "name" : "Max seed documents", + "comment" : "Maximum number of documents matching 'seedQuery' to use for the similarity search.", + "value" : 1 + }, + "seedLabelExclusionDictionaryEntries" : { + "name" : "Seed label exclusion dictionary entries", + "comment" : "Exclusion dictionary entries to apply to filter labels collected from seed documents. Use the entries to remove unwanted seed labels.", + "value" : [ "* paradox *" ] + } + } + }, + "results" : { + "name" : "Similar documents", + "comment" : "Configures the similarity search results", + "variables" : { + "maxKeywordSimilarDocuments" : { + "name" : "Max keyword-similar documents", + "comment" : "Maximum number of similar documents to find using the keyword method.", + "value" : 10 + }, + "maxEmbeddingSimilarDocuments" : { + "name" : "Max embedding-similar documents", + "comment" : "Maximum number of similar documents to find using the embedding method.", + "value" : 10 + }, + "similarDocumentsFilterQuery" : { + "name" : "Similar documents filter query", + "comment" : "Additional condition all similar documents must meet. Use the query to narrow down the results to, for example, documents from a specific category.", + "value" : "set:math*" + }, + "compositionOperator" : { + "name" : "Composition operator", + "comment" : "Determines how to combine the keyword- and embedding-based similar documents. Use 'OR' to take a union of the two lists. Use 'AND' to include only the documents returned by both methods.", + "value" : "OR" + } + } + } + }, + "components" : { + "filterQuery" : { + "type" : "query:string", + "query" : { + "@var" : "similarDocumentsFilterQuery" + } + }, + "labelFilter" : { + "type" : "labelFilter:composite", + "labelFilters" : { + "dictionary" : { + "type" : "labelFilter:dictionary", + "exclude" : [ { + "type" : "dictionary:glob", + "entries" : { + "@var" : "seedLabelExclusionDictionaryEntries" + } + } ] + }, + "autoStopLabels" : { + "type" : "labelFilter:autoStopLabels" + } + } + } + }, + "stages" : { + "seedDocuments" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "seedQuery" + } + }, + "limit" : { + "@var" : "maxSeedDocuments" + } + }, + "seedLabels" : { + "type" : "labels:composite", + "sources" : [ { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + }, + "maxLabels" : { + "type" : "labelCount:progressive", + "multiplier" : 1000 + }, + "labelAggregator" : { + "type" : "labelAggregator:topWeight", + "tieResolution" : "EXTEND", + "labelCollector" : { + "type" : "labelCollector:topFromFeatureFields", + "tieResolution" : "EXTEND", + "labelFilter" : { + "type" : "labelFilter:reference", + "use" : "labelFilter" + } + } + } + }, { + "type" : "labels:direct", + "labels" : [ { + "label" : "observable" + } ] + } ] + }, + "keywordMlt" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:filter", + "query" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "use" : "seedLabels" + } + }, + "filter" : { + "type" : "query:reference", + "use" : "filterQuery" + } + }, + "limit" : { + "@var" : "maxKeywordSimilarDocuments" + } + }, + "embeddingMlt" : { + "type" : "documents:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:composite", + "vectors" : [ { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "seedLabels" + } + }, { + "type" : "vector:documentEmbedding", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + } + } ] + }, + "filterQuery" : { + "type" : "query:reference", + "use" : "filterQuery" + }, + "limit" : { + "@var" : "maxEmbeddingSimilarDocuments" + } + }, + "combinedRwmd" : { + "type" : "documents:composite", + "selectors" : [ { + "type" : "documents:reference", + "use" : "keywordMlt" + }, { + "type" : "documents:reference", + "use" : "embeddingMlt" + } ], + "operator" : { + "@var" : "compositionOperator" + }, + "weightAggregation" : "SUM" + }, + "seedContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "seedDocuments" + }, + "queries" : { + "k" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "keywordMlt" + } + }, + "e" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "mlt" + } + } + } + }, + "keywordMltContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "keywordMlt" + }, + "queries" : { + "k" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "keywordMlt" + } + } + }, + "limit" : { + "@var" : "maxKeywordSimilarDocuments" + } + }, + "embeddingMltContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "embeddingMlt" + }, + "queries" : { + "e" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "mlt" + } + } + }, + "limit" : { + "@var" : "maxEmbeddingSimilarDocuments" + } + }, + "mlt" : { + "type" : "documents:rwmd", + "documents" : { + "type" : "documents:reference", + "use" : "combinedRwmd" + }, + "labels" : { + "type" : "labels:reference", + "use" : "seedLabels" + } + }, + "mltContent" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:reference", + "use" : "mlt" + }, + "queries" : { + "k" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "keywordMlt" + } + }, + "e" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "combinedRwmd" + } + } + }, + "limit" : "unlimited" + } + }, + "output" : { + "stages" : [ "embeddingMlt", "embeddingMltContent", "keywordMlt", "keywordMltContent", "seedDocuments", "seedContent", "seedLabels", "mlt", "mltContent" ] + }, + "tags" : [ "Similar Documents (MLT)" ] + }, + "label" : "More-Like-This document, keyword- and embedding-based with content, label and results filtering", + "description" : "Finds documents that are similar to the provided 'seed' document. This request combines results from the keyword- and embedding-based methods. Additionally, it allows filtering and providing additional seed labels as well as filtering of MLT results." + }, { + "body" : { + "name" : "Document 2d embedding by keyword similarity", + "comment" : "Puts documents on a 2d map based on the common labels they share. Documents containing similar labels will be close to each other on the map, while documents not sharing any labels will be far apart on the map.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:keywordDocumentSimilarity" + } + } + }, + "output" : { + "stages" : [ "documents", "documents2dEmbedding" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding by keyword similarity", + "description" : "Puts documents on a 2d map based on the common labels they share. Documents containing similar labels will be close to each other on the map, while documents not sharing any labels will be far apart on the map." + }, { + "body" : { + "name" : "Document 2d embedding by document embedding similarity", + "comment" : "Puts documents on a 2d map based on the embedding vector similarity. Similar documents will be close to each other on the map, while dissimilar documents should be far apart on the map.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings" + } + } + } + }, + "output" : { + "stages" : [ "documents", "documents2dEmbedding" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding by document embedding similarity", + "description" : "Puts documents on a 2d map based on the embedding vector similarity. Similar documents will be close to each other on the map, while dissimilar documents should be far apart on the map." + }, { + "body" : { + "name" : "Document 2d embedding with labels by keyword similarity", + "comment" : "Puts documents on a 2d map based on the common labels they share. Additionally, the request computes 2d coordinates of labels to describe specific areas of the map.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + }, + "labels" : { + "type" : "labels:fromDocuments" + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:keywordDocumentSimilarity" + } + }, + "documents2dEmbeddingLabels" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + } + } + }, + "output" : { + "stages" : [ "documents", "labels", "documents2dEmbedding", "documents2dEmbeddingLabels" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding with labels by keyword similarity", + "description" : "Puts documents on a 2d map based on the common labels they share. Additionally, the request computes 2d coordinates of labels to describe specific areas of the map." + }, { + "body" : { + "name" : "Document 2d embedding with labels by document embedding similarity", + "comment" : "Puts documents on a 2d map based on the embedding vector similarity. Additionally, the request computes 2d coordinates of labels to describe specific areas of the map.", + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : "" + }, + "limit" : 1000 + }, + "labels" : { + "type" : "labels:fromDocuments" + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings" + } + } + }, + "documents2dEmbeddingLabels" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + } + } + }, + "output" : { + "stages" : [ "documents", "labels", "documents2dEmbedding", "documents2dEmbeddingLabels" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding with labels by document embedding similarity", + "description" : "Puts documents on a 2d map based on the embedding vector similarity. Additionally, the request computes 2d coordinates of labels to describe specific areas of the map." + }, { + "body" : { + "name" : "Document 2d embedding with labels and clusters by keyword similarity", + "comment" : "Puts documents on a 2d map based on the keyword document similarity. Additionally, computes 2d coordinates of labels to describe specific areas of the map and clusters documents based on their 2d embedding distance.", + "variables" : { + "query" : { + "name" : "Query", + "comment" : "Selects documents to arrange into a 2d map.", + "value" : "\"dark matter\"" + }, + "maxDocuments" : { + "name" : "Max documents", + "comment" : "Maximum number of documents to include in the 2d map.", + "value" : 5000 + }, + "maxLabels" : { + "name" : "Max labels", + "comment" : "Maximum number of labels to put on the 2d map.", + "value" : 200 + } + }, + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "query" + } + }, + "limit" : { + "@var" : "maxDocuments" + } + }, + "labels" : { + "type" : "labels:fromDocuments", + "maxLabels" : { + "type" : "labelCount:fixed", + "value" : { + "@var" : "maxLabels" + } + } + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:keywordDocumentSimilarity" + } + }, + "documents2dEmbeddingLabels" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + } + }, + "docClusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:knn2dDistanceSimilarity", + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + }, + "maxNearestPoints" : 32 + }, + "softening" : 0.2, + "inputPreference" : -10000 + } + }, + "output" : { + "stages" : [ "documents", "labels", "documents2dEmbedding", "documents2dEmbeddingLabels", "docClusters" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding with labels and clusters by keyword similarity", + "description" : "Puts documents on a 2d map based on the keyword document similarity. Additionally, computes 2d coordinates of labels to describe specific areas of the map and clusters documents based on their 2d embedding distance." + }, { + "body" : { + "name" : "Document 2d embedding with labels and clusters by embedding similarity", + "comment" : "Puts documents on a 2d map based on the document embedding similarity. Additionally, computes 2d coordinates of labels to describe specific areas of the map and clusters documents based on their 2d embedding distance.", + "variables" : { + "query" : { + "name" : "Query", + "comment" : "Selects documents to arrange into a 2d map.", + "value" : "\"dark matter\"" + }, + "maxDocuments" : { + "name" : "Max documents", + "comment" : "Maximum number of documents to include in the 2d map.", + "value" : 5000 + }, + "maxLabels" : { + "name" : "Max labels", + "comment" : "Maximum number of labels to put on the 2d map.", + "value" : 200 + } + }, + "stages" : { + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "query" + } + }, + "limit" : { + "@var" : "maxDocuments" + } + }, + "labels" : { + "type" : "labels:fromDocuments", + "maxLabels" : { + "type" : "labelCount:fixed", + "value" : { + "@var" : "maxLabels" + } + } + }, + "documents2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings" + } + } + }, + "documents2dEmbeddingLabels" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "labels" : { + "type" : "labels:reference", + "use" : "labels" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + } + }, + "docClusters" : { + "type" : "clusters:ap", + "matrix" : { + "type" : "matrix:knn2dDistanceSimilarity", + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "documents2dEmbedding" + }, + "maxNearestPoints" : 32 + }, + "softening" : 0.2, + "inputPreference" : -10000 + } + }, + "output" : { + "stages" : [ "documents", "labels", "documents2dEmbedding", "documents2dEmbeddingLabels", "docClusters" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document 2d embedding with labels and clusters by embedding similarity", + "description" : "Puts documents on a 2d map based on the document embedding similarity. Additionally, computes 2d coordinates of labels to describe specific areas of the map and clusters documents based on their 2d embedding distance." + }, { + "body" : { + "name" : "Label 2d embedding by embedding similarity", + "comment" : "Puts labels on a 2d map based on their embedding vector similarity. Semantically similar labels will be close to each other on the map, dissimilar labels should be far apart on the map.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + }, + "limit" : 100 + }, + "label2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedLabelEmbeddings", + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + } + } + }, + "output" : { + "stages" : [ "label2dEmbedding", "similarLabels" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Label 2d embedding by embedding similarity", + "description" : "Puts labels on a 2d map based on their embedding vector similarity. Semantically similar labels will be close to each other on the map, dissimilar labels should be far apart on the map." + }, { + "body" : { + "name" : "Label 2d embedding by label co-occurrence similarity", + "comment" : "Puts labels on a 2d map based on how frequently they co-occur in the documents. Frequently co-occurring labels will be close to each other on the map.", + "stages" : { + "queryLabel" : { + "type" : "labels:direct", + "labels" : [ { + "label" : "photon" + } ] + }, + "similarLabels" : { + "type" : "labels:embeddingNearestNeighbors", + "vector" : { + "type" : "vector:labelEmbedding", + "labels" : { + "type" : "labels:reference", + "use" : "queryLabel" + } + }, + "limit" : 100 + }, + "label2dEmbedding" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:cooccurrenceLabelSimilarity", + "documents" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:forLabels", + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + }, + "labels" : { + "type" : "labels:reference", + "use" : "similarLabels" + } + } + } + }, + "output" : { + "stages" : [ "label2dEmbedding", "similarLabels" ] + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Label 2d embedding by label co-occurrence similarity", + "description" : "Puts labels on a 2d map based on how frequently they co-occur in the documents. Frequently co-occurring labels will be close to each other on the map." + }, { + "body" : { + "name" : "Document embedding with labels time series", + "comment" : "Computes a series of 2d maps for documents from overlapping periods of time. You can use this request to show how the thematic structure of documents evolves over time.", + "components" : { + "baseQuery" : { + "type" : "query:string", + "query" : "photon" + }, + "rangeQuery0" : { + "type" : "query:string", + "query" : "created:[2015-01-01 TO 2017-01-01]" + }, + "rangeQuery1" : { + "type" : "query:string", + "query" : "created:[2016-01-01 TO 2018-01-01]" + }, + "rangeQuery2" : { + "type" : "query:string", + "query" : "created:[2017-01-01 TO 2019-01-01]" + }, + "baseAndRangesQuery" : { + "type" : "query:composite", + "operator" : "AND", + "queries" : [ { + "type" : "query:reference", + "use" : "baseQuery" + }, { + "type" : "query:composite", + "operator" : "OR", + "queries" : [ { + "type" : "query:reference", + "use" : "rangeQuery0" + }, { + "type" : "query:reference", + "use" : "rangeQuery1" + }, { + "type" : "query:reference", + "use" : "rangeQuery2" + } ] + } ] + } + }, + "stages" : { + "sample" : { + "type" : "documents:sample", + "limit" : 10000, + "query" : { + "type" : "query:reference", + "use" : "baseAndRangesQuery" + } + }, + "documents0" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:filter", + "query" : { + "type" : "query:reference", + "use" : "rangeQuery0" + }, + "filter" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "sample" + }, + "buildFromDocumentIds" : true + } + } + }, + "documents1" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:filter", + "query" : { + "type" : "query:reference", + "use" : "rangeQuery1" + }, + "filter" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "sample" + }, + "buildFromDocumentIds" : true + } + } + }, + "documents2" : { + "type" : "documents:byQuery", + "query" : { + "type" : "query:filter", + "query" : { + "type" : "query:reference", + "use" : "rangeQuery2" + }, + "filter" : { + "type" : "query:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "sample" + }, + "buildFromDocumentIds" : true + } + } + }, + "labels0" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "documents0" + } + }, + "labels1" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "documents1" + } + }, + "labels2" : { + "type" : "labels:fromDocuments", + "documents" : { + "type" : "documents:reference", + "use" : "documents2" + } + }, + "docEmbedding0" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings", + "documents" : { + "type" : "documents:reference", + "use" : "documents0" + } + } + } + }, + "labelEmbedding0" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "documents" : { + "type" : "documents:reference", + "use" : "documents0" + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels0" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "docEmbedding0" + } + }, + "docEmbedding1" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings", + "documents" : { + "type" : "documents:reference", + "use" : "documents1" + } + } + }, + "initial" : { + "type" : "embedding2d:transferred", + "source" : { + "type" : "documents:reference", + "use" : "documents0" + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "docEmbedding0" + }, + "target" : { + "type" : "documents:reference", + "use" : "documents1" + } + } + }, + "labelEmbedding1" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "documents" : { + "type" : "documents:reference", + "use" : "documents1" + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels1" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "docEmbedding1" + }, + "initial" : { + "type" : "embedding2d:transferred", + "source" : { + "type" : "labels:reference", + "use" : "labels0" + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "labelEmbedding0" + }, + "target" : { + "type" : "labels:reference", + "use" : "labels1" + } + } + }, + "docEmbedding2" : { + "type" : "embedding2d:lv", + "matrix" : { + "type" : "matrix:knnVectorsSimilarity", + "vectors" : { + "type" : "vectors:precomputedDocumentEmbeddings", + "documents" : { + "type" : "documents:reference", + "use" : "documents2" + } + } + }, + "initial" : { + "type" : "embedding2d:transferred", + "source" : { + "type" : "documents:reference", + "use" : "documents1" + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "docEmbedding1" + }, + "target" : { + "type" : "documents:reference", + "use" : "documents2" + } + } + }, + "labelEmbedding2" : { + "type" : "embedding2d:lvOverlay", + "matrix" : { + "type" : "matrix:keywordLabelDocumentSimilarity", + "documents" : { + "type" : "documents:reference", + "use" : "documents2" + }, + "labels" : { + "type" : "labels:reference", + "use" : "labels2" + } + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "docEmbedding2" + }, + "initial" : { + "type" : "embedding2d:transferred", + "source" : { + "type" : "labels:reference", + "use" : "labels1" + }, + "embedding2d" : { + "type" : "embedding2d:reference", + "use" : "labelEmbedding1" + }, + "target" : { + "type" : "labels:reference", + "use" : "labels2" + } + } + } + }, + "tags" : [ "2D Embeddings" ] + }, + "label" : "Document embedding with labels time series", + "description" : "Computes a series of 2d maps for documents from overlapping periods of time. You can use this request to show how the thematic structure of documents evolves over time." + }, { + "body" : { + "name" : "Pairs of similar but not identical documents", + "comment" : "Finds pairs of documents with similar but not identical content in the 'abstract' field. You can use requests of this type to identify potentially plagiarized content, where large parts of text are identical or nearly identical.", + "variables" : { + "scopeQuery" : { + "name" : "Duplicate search scope query", + "comment" : "Determines the set of documents to search for duplicated content.", + "value" : "*:*" + }, + "scopeMaxDocuments" : { + "name" : "Duplicate search scope size", + "comment" : "Determines how many of the in-scope documents to search for duplicated content.", + "value" : 1000000 + }, + "fieldsToCompare" : { + "name" : "Fields to compare", + "comment" : "Fields to check for duplicated content.", + "value" : [ "abstract" ] + } + }, + "components" : { + "fields" : { + "type" : "fields:simple", + "fields" : { + "@var" : "fieldsToCompare" + } + } + }, + "stages" : { + "duplicates" : { + "type" : "documentPairs:duplicates", + "query" : { + "type" : "query:string", + "query" : { + "@var" : "scopeQuery" + } + }, + "hashGrouping" : { + "pairing" : { + "maxHashBitsDifferent" : 3, + "maxHashGroupSize" : 500 + }, + "features" : { + "type" : "featureSource:simhash", + "source" : { + "type" : "featureSource:flatten", + "source" : { + "type" : "featureSource:words" + } + } + } + }, + "validation" : { + "pairwiseSimilarity" : { + "type" : "pairwiseSimilarity:featureIntersectionMinRatio", + "features" : { + "type" : "featureSource:count", + "minFeatureCount" : 20, + "source" : { + "type" : "featureSource:flatten", + "source" : { + "type" : "featureSource:words" + } + } + } + }, + "min" : 0.8, + "max" : 0.99 + }, + "output" : { + "explanations" : true, + "limit" : 10 + } + }, + "content" : { + "type" : "documentContent", + "documents" : { + "type" : "documents:fromDocumentPairs" + }, + "fields" : { + "type" : "contentFields:grouped", + "groups" : [ { + "fields" : { + "@var" : "fieldsToCompare" + }, + "config" : { + "maxValues" : 1, + "maxValueLength" : 160 + } + } ] + } + } + }, + "tags" : [ "Duplicate Detection" ] + }, + "label" : "Pairs of similar but not identical documents", + "description" : "Finds pairs of documents with similar but not identical content in the 'abstract' field. You can use requests of this type to identify potentially plagiarized content, where large parts of text are identical or nearly identical." + } ] +} \ No newline at end of file diff --git a/src/test/resources/issues/662/emptyObject.json b/src/test/resources/issues/662/emptyObject.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/test/resources/issues/662/emptyObject.json @@ -0,0 +1 @@ +{} diff --git a/src/test/resources/issues/662/objectInvalidValue.json b/src/test/resources/issues/662/objectInvalidValue.json new file mode 100644 index 000000000..2245430f4 --- /dev/null +++ b/src/test/resources/issues/662/objectInvalidValue.json @@ -0,0 +1,5 @@ +{ + "optionalObject": { + "value": "invalid" + } +} diff --git a/src/test/resources/issues/662/schema.json b/src/test/resources/issues/662/schema.json new file mode 100644 index 000000000..3155e0fcf --- /dev/null +++ b/src/test/resources/issues/662/schema.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "optionalObject": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "value": { + "enum": [ + "one", + "two" + ] + } + }, + "required": [ + "value" + ] + } + ] + } + } +} diff --git a/src/test/resources/issues/662/validObject.json b/src/test/resources/issues/662/validObject.json new file mode 100644 index 000000000..ce72d612a --- /dev/null +++ b/src/test/resources/issues/662/validObject.json @@ -0,0 +1,5 @@ +{ + "optionalObject": { + "value": "one" + } +} diff --git a/src/test/resources/jsv-messages-override.properties b/src/test/resources/jsv-messages-override.properties new file mode 100644 index 000000000..a9794e6ef --- /dev/null +++ b/src/test/resources/jsv-messages-override.properties @@ -0,0 +1 @@ +allOf = {0}: overridden message {1} diff --git a/src/test/resources/oas/3.1/dialect/base b/src/test/resources/oas/3.1/dialect/base new file mode 100644 index 000000000..eae8386e8 --- /dev/null +++ b/src/test/resources/oas/3.1/dialect/base @@ -0,0 +1,25 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/dialect/base", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "title": "OpenAPI 3.1 Schema Object Dialect", + "description": "A JSON Schema dialect describing schemas found in OpenAPI documents", + + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true, + "https://spec.openapis.org/oas/3.1/vocab/base": false + }, + + "$dynamicAnchor": "meta", + + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + { "$ref": "https://spec.openapis.org/oas/3.1/meta/base" } + ] +} diff --git a/src/test/resources/oas/3.1/meta/base b/src/test/resources/oas/3.1/meta/base new file mode 100644 index 000000000..a7a59f1c7 --- /dev/null +++ b/src/test/resources/oas/3.1/meta/base @@ -0,0 +1,87 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/meta/base", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "title": "OAS Base vocabulary", + "description": "A JSON Schema Vocabulary used in the OpenAPI Schema Dialect", + + "$vocabulary": { + "https://spec.openapis.org/oas/3.1/vocab/base": true + }, + + "$dynamicAnchor": "meta", + + "type": ["object", "boolean"], + "properties": { + "example": true, + "discriminator": { "$ref": "#/$defs/discriminator" }, + "externalDocs": { "$ref": "#/$defs/external-docs" }, + "xml": { "$ref": "#/$defs/xml" } + }, + + "$defs": { + "extensible": { + "patternProperties": { + "^x-": true + } + }, + + "discriminator": { + "$ref": "#/$defs/extensible", + "type": "object", + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["propertyName"], + "unevaluatedProperties": false + }, + + "external-docs": { + "$ref": "#/$defs/extensible", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "required": ["url"], + "unevaluatedProperties": false + }, + + "xml": { + "$ref": "#/$defs/extensible", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean" + }, + "wrapped": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + } + } +} diff --git a/src/test/resources/oas/3.1/schema-base/2022-10-07 b/src/test/resources/oas/3.1/schema-base/2022-10-07 new file mode 100644 index 000000000..752e98be4 --- /dev/null +++ b/src/test/resources/oas/3.1/schema-base/2022-10-07 @@ -0,0 +1,23 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema-base/2022-10-07", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "description": "The description of OpenAPI v3.1.x documents using the OpenAPI JSON Schema dialect, as defined by https://spec.openapis.org/oas/v3.1.0", + + "$ref": "https://spec.openapis.org/oas/3.1/schema/2022-10-07", + "properties": { + "jsonSchemaDialect": { "$ref": "#/$defs/dialect" } + }, + + "$defs": { + "dialect": { "const": "https://spec.openapis.org/oas/3.1/dialect/base" }, + + "schema": { + "$dynamicAnchor": "meta", + "$ref": "https://spec.openapis.org/oas/3.1/dialect/base", + "properties": { + "$schema": { "$ref": "#/$defs/dialect" } + } + } + } +} diff --git a/src/test/resources/oas/3.1/schema/2022-10-07 b/src/test/resources/oas/3.1/schema/2022-10-07 new file mode 100644 index 000000000..468bc7e5f --- /dev/null +++ b/src/test/resources/oas/3.1/schema/2022-10-07 @@ -0,0 +1,1440 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0", + "type": "object", + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + }, + "default": [ + { + "url": "/" + } + ] + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "info": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri" + }, + "contact": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "contact": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "dependentSchemas": { + "identifier": { + "not": { + "required": [ + "url" + ] + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "type": "object", + "properties": { + "schemas": { + "type": "object", + "additionalProperties": { + "$dynamicRef": "#meta" + } + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "patternProperties": { + "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { + "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", + "propertyNames": { + "pattern": "^[a-zA-Z0-9._-]+$" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "paths": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "get": { + "$ref": "#/$defs/operation" + }, + "put": { + "$ref": "#/$defs/operation" + }, + "post": { + "$ref": "#/$defs/operation" + }, + "delete": { + "$ref": "#/$defs/operation" + }, + "options": { + "$ref": "#/$defs/operation" + }, + "head": { + "$ref": "#/$defs/operation" + }, + "patch": { + "$ref": "#/$defs/operation" + }, + "trace": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "external-documentation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + } + }, + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/examples" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form" + } + ], + "$defs": { + "styles-for-path": { + "if": { + "properties": { + "in": { + "const": "path" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "name": { + "pattern": "[^/#?]+$" + }, + "style": { + "default": "simple", + "enum": [ + "matrix", + "label", + "simple" + ] + }, + "required": { + "const": true + } + }, + "required": [ + "required" + ] + } + }, + "styles-for-header": { + "if": { + "properties": { + "in": { + "const": "header" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + } + } + } + }, + "styles-for-query": { + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + } + } + }, + "styles-for-cookie": { + "if": { + "properties": { + "in": { + "const": "cookie" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "const": "form" + } + } + } + }, + "styles-for-form": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "$ref": "#/$defs/content" + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "request-body-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "type": "object", + "properties": { + "contentType": { + "type": "string", + "format": "media-range" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/encoding/$defs/explode-default" + } + ], + "unevaluatedProperties": false, + "$defs": { + "explode-default": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } + }, + "responses": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": { + "$ref": "#/$defs/response-or-reference" + } + }, + "minProperties": 1, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "$ref": "#/$defs/content" + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri" + } + }, + "not": { + "required": [ + "value", + "externalValue" + ] + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "link-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + }, + "explode": { + "default": false, + "type": "boolean" + } + }, + "$ref": "#/$defs/examples" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "$dynamicAnchor": "meta", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "unevaluatedProperties": false, + "$defs": { + "type-apikey": { + "if": { + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "type-oauth2": { + "if": { + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + } + } + }, + "security-requirement": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/src/test/resources/openapi3/AdditionalPropertiesOneOfFailsTest.json b/src/test/resources/openapi3/AdditionalPropertiesOneOfFailsTest.json deleted file mode 100644 index 14424f30f..000000000 --- a/src/test/resources/openapi3/AdditionalPropertiesOneOfFailsTest.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "type": "object", - "properties": { - "locationName": { - "type": "string" - }, - "activities": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "required": [ - "activityType", - "weight", - "height" - ], - "additionalProperties": false, - "properties": { - "activityType": { - "enum": [ - "machine" - ] - }, - "weight": { - "type": "integer" - }, - "height": { - "type": "integer" - } - } - }, - { - "type": "object", - "required": [ - "activityType", - "chemicalCharacteristic" - ], - "additionalProperties": false, - "properties": { - "activityType": { - "enum": [ - "chemical" - ] - }, - "chemicalCharacteristic": { - "oneOf": [ - { - "type": "object", - "required": [ - "chemicalName" - ], - "additionalProperties": false, - "properties": { - "commonName": { - "type": "string" - }, - "chemicalName": { - "type": "string" - } - } - }, - { - "type": "object", - "required": [ - "chemicalName" - ], - "additionalProperties": false, - "properties": { - "categoryName": { - "type": "string" - }, - "chemicalName": { - "type": "string" - } - } - } - ] - } - } - } - ] - } - } - } -} diff --git a/src/test/resources/openapi3/discriminator.json b/src/test/resources/openapi3/discriminator.json index 6d55ece6c..f09912f8d 100644 --- a/src/test/resources/openapi3/discriminator.json +++ b/src/test/resources/openapi3/discriminator.json @@ -232,27 +232,27 @@ "valid": true }, { - "description": "mapped to Bedroom with missing number of beds", + "description": "mapped to Bedroom with missing number of beds but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "bed" }, - "valid": false + "valid": true }, { - "description": "mapped to KidsBedroom with missing number of beds", + "description": "mapped to KidsBedroom with missing number of beds but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "KidsBedRoom", "isTidy": true }, - "valid": false + "valid": true }, { - "description": "mapped to KidsBedroom with missing tidiness", + "description": "mapped to KidsBedroom with missing tidiness but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "KidsBedRoom", "numberOfBeds": 1 }, - "valid": false + "valid": true }, { "description": "mapped to GuestRoom with correct @type (mapping override on BedRoom)", @@ -264,12 +264,12 @@ "valid": true }, { - "description": "mapped to GuestRoom with incorrect @type (mapping override on BedRoom)", + "description": "mapped to GuestRoom with incorrect @type (mapping override on BedRoom) but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "GuestRoom", "guest": "Steve" }, - "valid": false + "valid": true }, { "description": "mapped to invalid Room", @@ -409,7 +409,7 @@ "valid": true }, { - "description": "schema with discriminator and recursion with invalid BedRoom", + "description": "schema with discriminator and recursion with invalid BedRoom but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "can be ignored - discriminator not in use on root schema", "numberOfBeds": 42, @@ -418,7 +418,7 @@ "floor": 1 } }, - "valid": false + "valid": true } ] }, @@ -506,7 +506,7 @@ "valid": true }, { - "description": "schema with discriminator and recursion with invalid BedRoom", + "description": "schema with discriminator and recursion with invalid BedRoom but #/components/schemas/Room matches and discriminator cannot change the validation result", "data": { "@type": "can be ignored - discriminator not in use on root schema", "numberOfBeds": 42, @@ -515,7 +515,7 @@ "floor": 1 } }, - "valid": false + "valid": true } ] }, diff --git a/src/test/resources/prefixItemsException/prefixItemsException.json b/src/test/resources/prefixItemsException/prefixItemsException.json new file mode 100644 index 000000000..cc9c1f079 --- /dev/null +++ b/src/test/resources/prefixItemsException/prefixItemsException.json @@ -0,0 +1,16 @@ +[ + { + "description": "no prefix items, exception testing", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [] + }, + "tests": [ + { + "description": "zero elements", + "data": [], + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/recursiveRefException/invalidRecursiveReference.json b/src/test/resources/recursiveRefException/invalidRecursiveReference.json new file mode 100644 index 000000000..6941769ff --- /dev/null +++ b/src/test/resources/recursiveRefException/invalidRecursiveReference.json @@ -0,0 +1,50 @@ +[ + { + "description": "invalid recursive reference exception testing", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "subordinates": { + "type": "array", + "items": { + "$recursiveRef": "#%" + } + } + } + }, + "tests": [ + { + "description": "no test cases invalid schema exception", + "data": { + "name": "John Doe", + "title": "CEO", + "subordinates": [ + { + "name": "Alice Smith", + "title": "CTO", + "subordinates": [ + { + "name": "Bob Johnson", + "title": "Lead Engineer", + "subordinates": [] + } + ] + }, + { + "name": "Eva Brown", + "title": "CFO", + "subordinates": [] + } + ] + }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/remotes/integer.json b/src/test/resources/remotes/integer.json deleted file mode 100644 index 782f20030..000000000 --- a/src/test/resources/remotes/integer.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "integer" -} diff --git a/src/test/resources/remotes/name-defs.json b/src/test/resources/remotes/name-defs.json deleted file mode 100644 index 6b5479d3b..000000000 --- a/src/test/resources/remotes/name-defs.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$defs": { - "orNull": { - "anyOf": [ - { - "type": "null" - }, - { - "$ref": "#" - } - ] - } - }, - "type": "string" -} diff --git a/src/test/resources/remotes/name.json b/src/test/resources/remotes/name.json deleted file mode 100644 index 746a27e72..000000000 --- a/src/test/resources/remotes/name.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "definitions": { - "orNull": { - "anyOf": [ - { - "type": "null" - }, - { - "$ref": "#" - } - ] - } - }, - "type": "string" -} diff --git a/src/test/resources/remotes/subSchemas.json b/src/test/resources/remotes/subSchemas.json deleted file mode 100644 index f5577e771..000000000 --- a/src/test/resources/remotes/subSchemas.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "integer": { - "type": "integer" - }, - "refToInteger": { - "$ref": "#/integer" - } -} diff --git a/src/test/resources/schema/OverwritingCustomMessageBug.json b/src/test/resources/schema/OverwritingCustomMessageBug.json new file mode 100644 index 000000000..8eecb3bfb --- /dev/null +++ b/src/test/resources/schema/OverwritingCustomMessageBug.json @@ -0,0 +1,31 @@ +{ + "type": "object", + "properties": { + "toplevel": { + "type": "array", + "items": { + "type": "object", + "properties": { + "foos": { + "type": "string", + "pattern": "(foo)+", + "message": { + "pattern": "Must be a string with the a shape foofoofoofoo... with at least one foo" + } + }, + "Nope": { + "type": "string" + }, + "bars": { + "type": "string", + "pattern": "(bar)+", + "message": { + "pattern": "Must be a string with the a shape barbarbar... with at least one bar" + } + } + } + }, + "minItems": 1 + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/common/child.json b/src/test/resources/schema/common/child.json new file mode 100644 index 000000000..9fa5f2699 --- /dev/null +++ b/src/test/resources/schema/common/child.json @@ -0,0 +1,9 @@ +{ + "title": "child schema", + "type": "object", + "properties": { + "value": { + "$ref": "./child2.json" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/common/child2.json b/src/test/resources/schema/common/child2.json new file mode 100644 index 000000000..6115beb10 --- /dev/null +++ b/src/test/resources/schema/common/child2.json @@ -0,0 +1,10 @@ +{ + "title": "child2 schema", + "type": "object", + "properties": { + "value": { + "type": "string", + "$comment": "child value." + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/contains/issue769/max-contains-v7.json b/src/test/resources/schema/contains/issue769/max-contains-v7.json new file mode 100644 index 000000000..7a316428e --- /dev/null +++ b/src/test/resources/schema/contains/issue769/max-contains-v7.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "myArray": { + "type": "array", + "maxContains": 1, + "contains": {"properties": {"itemType": {"const": "type A"}} + }, + "items": {"properties": {"itemType": {"type": "string"}}} + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/contains/issue769/max-contains.json b/src/test/resources/schema/contains/issue769/max-contains.json new file mode 100644 index 000000000..bda4eb423 --- /dev/null +++ b/src/test/resources/schema/contains/issue769/max-contains.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "myArray": { + "type": "array", + "maxContains": 1, + "contains": {"properties": {"itemType": {"const": "type A"}} + }, + "items": {"properties": {"itemType": {"type": "string"}}} + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/contains/issue769/min-contains-v7.json b/src/test/resources/schema/contains/issue769/min-contains-v7.json new file mode 100644 index 000000000..e93542508 --- /dev/null +++ b/src/test/resources/schema/contains/issue769/min-contains-v7.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "myArray": { + "type": "array", + "minContains": 2, + "contains": {"properties": {"itemType": {"const": "type A"}} + }, + "items": {"properties": {"itemType": {"type": "string"}}} + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/contains/issue769/min-contains.json b/src/test/resources/schema/contains/issue769/min-contains.json new file mode 100644 index 000000000..f8a4971e9 --- /dev/null +++ b/src/test/resources/schema/contains/issue769/min-contains.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "myArray": { + "type": "array", + "minContains": 2, + "contains": {"properties": {"itemType": {"const": "type A"}} + }, + "items": {"properties": {"itemType": {"type": "string"}}} + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/customMessageTests/custom-message-disabled-tests.json b/src/test/resources/schema/customMessageTests/custom-message-disabled-tests.json new file mode 100644 index 000000000..9464fb320 --- /dev/null +++ b/src/test/resources/schema/customMessageTests/custom-message-disabled-tests.json @@ -0,0 +1,124 @@ +[ + { + "description": "Tests that validate custom message functionality doesn't work for keywords at any level, if validator config has isCustomMessageSupported set as false", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "array", + "maxItems": 3, + "items" : { + "type" : "number" + }, + "message" : { + "maxItems" : "Custom Message: Maximum 3 numbers can be given in 'foo'", + "type" : "Custom Message: Only numbers are supported in 'foo'" + } + }, + "bar": { + "type": "string" + } + }, + "message": { + "type" : "Custom Message: Invalid type provided" + } + }, + "tests": [ + { + "description": "Basic Success Test 1", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [], + "bar": "Bar 1" + }, + "valid": true + }, + { + "description": "Basic Success Test 2", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2], + "bar": "Bar 1" + }, + "valid": true + }, + { + "description": "Custom error message for keyword failing at top level - type keyword", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2, 3], + "bar": 123 + }, + "valid": false, + "errors": [ + "/bar: integer found, string expected" + ] + }, + { + "description": "Custom error message for keyword failing at nested property level - type keyword", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2, "text"], + "bar": "Bar 1" + }, + "valid": false, + "errors": [ + "/foo/2: string found, number expected" + ] + }, + { + "description": "Custom error message for keyword failing at nested property level - maxItems keyword", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2, 3, 4], + "bar": "Bar 1" + }, + "valid": false, + "errors": [ + "/foo: must have at most 3 items but found 4" + ] + }, + { + "description": "Custom error message for keyword failing at both top level and nested property level - type keyword", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2, "text"], + "bar": 123 + }, + "valid": false, + "errors": [ + "/foo/2: string found, number expected", + "/bar: integer found, string expected" + ] + }, + { + "description": "Custom error message for keyword failing at both top level and nested property level - type keyword", + "config": { + "isCustomMessageSupported": false + }, + "data": { + "foo": [1, 2, "text", 3], + "bar": 123 + }, + "valid": false, + "errors": [ + "/foo: must have at most 3 items but found 4", + "/foo/2: string found, number expected", + "/bar: integer found, string expected" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/schema/customMessageTests/custom-message-tests.json b/src/test/resources/schema/customMessageTests/custom-message-tests.json new file mode 100644 index 000000000..1dd846514 --- /dev/null +++ b/src/test/resources/schema/customMessageTests/custom-message-tests.json @@ -0,0 +1,223 @@ +[ + { + "description": "Tests that validate custom message functionality where overrides can be provided at schema level for individual keywords", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "array", + "maxItems": 3, + "items" : { + "type" : "number" + }, + "message" : { + "maxItems" : "Custom Message: Maximum 3 numbers can be given in 'foo'", + "type" : "Custom Message: Only numbers are supported in 'foo'" + } + }, + "bar": { + "type": "string" + } + }, + "message": { + "type" : "Custom Message: Invalid type provided" + } + }, + "tests": [ + { + "description": "Basic Success Test 1", + "data": { + "foo": [], + "bar": "Bar 1" + }, + "valid": true + }, + { + "description": "Basic Success Test 2", + "data": { + "foo": [1, 2], + "bar": "Bar 1" + }, + "valid": true + }, + { + "description": "Custom error message for keyword failing at top level - type keyword", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": [1, 2, 3], + "bar": 123 + }, + "valid": false, + "errors": [ + "/bar: Custom Message: Invalid type provided" + ] + }, + { + "description": "Custom error message for keyword failing at nested property level - type keyword", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": [1, 2, "text"], + "bar": "Bar 1" + }, + "valid": false, + "errors": [ + "/foo/2: Custom Message: Only numbers are supported in 'foo'" + ] + }, + { + "description": "Custom error message for keyword failing at nested property level - maxItems keyword", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": [1, 2, 3, 4], + "bar": "Bar 1" + }, + "valid": false, + "errors": [ + "/foo: Custom Message: Maximum 3 numbers can be given in 'foo'" + ] + }, + { + "description": "Custom error message for keyword failing at both top level and nested property level - type keyword", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": [1, 2, "text"], + "bar": 123 + }, + "valid": false, + "errors": [ + "/bar: Custom Message: Invalid type provided", + "/foo/2: Custom Message: Only numbers are supported in 'foo'" + ] + }, + { + "description": "Custom error message for keyword failing at both top level and nested property level - type keyword", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": [1, 2, "text", 3], + "bar": 123 + }, + "valid": false, + "errors": [ + "/bar: Custom Message: Invalid type provided", + "/foo/2: Custom Message: Only numbers are supported in 'foo'", + "/foo: Custom Message: Maximum 3 numbers can be given in 'foo'" + ] + } + ] + }, + { + "description": "messages for keywords for different properties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "number" + }, + "bar": { + "type": "string" + } + }, + "required": ["foo", "bar"], + "message": { + "type" : "should be an object", + "required": { + "foo" : "'foo' is required", + "bar" : "'bar' is required" + } + } + }, + "tests": [ + { + "description": "bar is required", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "foo": 1 + }, + "valid": false, + "errors": [ + ": 'bar' is required" + ] + }, + { + "description": "foo is required", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "bar": "bar" + }, + "valid": false, + "errors": [ + ": 'foo' is required" + ] + }, + { + "description": "both foo and bar are required", + "config": { + "isCustomMessageSupported": true + }, + "data": { + }, + "valid": false, + "errors": [ + ": 'foo' is required", + ": 'bar' is required" + ] + } + ] + }, + { + "description": "messages for keywords with arguments", + "schema": { + "type": "object", + "properties": { + "requestedItems": { + "type": "array", + "minItems": 1, + "items": { + "properties": { + "item": { + "type": "string", + "minLength": 1, + "message": { + "minLength": "Item should not be empty" + } + } + } + } + } + } + }, + "tests": [ + { + "description": "should not be empty", + "config": { + "isCustomMessageSupported": true + }, + "data": { + "requestedItems": [ + { + "item": "" + } + ] + }, + "valid": false, + "errors": [ + "/requestedItems/0/item: Item should not be empty" + ] + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/schema/example-escaped.yaml b/src/test/resources/schema/example-escaped.yaml new file mode 100644 index 000000000..273e35732 --- /dev/null +++ b/src/test/resources/schema/example-escaped.yaml @@ -0,0 +1,9 @@ +paths: + /users: + post: + requestBody: + application/json: + schema: + anyOf: + - type: object + - type: string \ No newline at end of file diff --git a/src/test/resources/schema/example-main.json b/src/test/resources/schema/example-main.json new file mode 100644 index 000000000..05a84361f --- /dev/null +++ b/src/test/resources/schema/example-main.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required" : ["DriverProperties"], + "properties": { + "DriverProperties": { + "type": "object", + "properties": { + "CommonProperties": { + "$ref": "example-ref.json#/definitions/DriverProperties" + } + }, + "required": ["CommonProperties"], + "additionalProperties": false + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/src/test/resources/schema/example-ref.json b/src/test/resources/schema/example-ref.json new file mode 100644 index 000000000..39dfc0b17 --- /dev/null +++ b/src/test/resources/schema/example-ref.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "additionalProperties": false, + "definitions": { + "DriverProperties": { + "type": "object", + "properties": { + "field1": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "field2": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": ["field1"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/id-relative.json b/src/test/resources/schema/id-relative.json new file mode 100644 index 000000000..d159f7ba7 --- /dev/null +++ b/src/test/resources/schema/id-relative.json @@ -0,0 +1,4 @@ +{ + "$id": "0", + "$schema": "https://json-schema.org/draft/2020-12/schema" +} \ No newline at end of file diff --git a/src/test/resources/schema/issue1091.json b/src/test/resources/schema/issue1091.json new file mode 100644 index 000000000..532125783 --- /dev/null +++ b/src/test/resources/schema/issue1091.json @@ -0,0 +1,2529 @@ +{ + "$ref": "#/definitions/doc_node", + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "blockCard_node": { + "type": "object", + "properties": { + "type": { + "enum": ["blockCard"] + }, + "attrs": { + "anyOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "datasource": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "parameters": {}, + "views": { + "items": { + "additionalProperties": false, + "properties": { + "properties": {}, + "type": { + "type": "string" + } + }, + "required": ["type"], + "type": "object" + }, + "minItems": 1, + "type": "array" + } + }, + "required": ["id", "parameters", "views"] + }, + "width": { + "type": "number" + }, + "layout": { + "enum": [ + "wide", + "full-width", + "center", + "wrap-right", + "wrap-left", + "align-end", + "align-start" + ] + } + }, + "required": ["datasource"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": ["url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "data": {} + }, + "required": ["data"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "text_node": { + "type": "object", + "properties": { + "type": { + "enum": ["text"] + }, + "marks": { + "type": "array" + }, + "text": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "required": ["type", "text"] + }, + "codeBlock_node": { + "type": "object", + "properties": { + "type": { + "enum": ["codeBlock"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "language": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/definitions/text_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + } + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "codeBlock_with_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/codeBlock_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/breakout_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "breakout_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["breakout"] + }, + "attrs": { + "type": "object", + "properties": { + "mode": { + "enum": ["wide", "full-width"] + } + }, + "required": ["mode"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "codeBlock_with_no_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/codeBlock_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + }, + "mediaSingle_node": { + "type": "object", + "properties": { + "type": { + "enum": ["mediaSingle"] + }, + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/link_mark" + } + }, + "attrs": { + "anyOf": [ + { + "type": "object", + "properties": { + "width": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "layout": { + "enum": [ + "wide", + "full-width", + "center", + "wrap-right", + "wrap-left", + "align-end", + "align-start" + ] + }, + "widthType": { + "enum": ["percentage"] + } + }, + "required": ["layout"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "width": { + "type": "number", + "minimum": 0 + }, + "widthType": { + "enum": ["pixel"] + }, + "layout": { + "enum": [ + "wide", + "full-width", + "center", + "wrap-right", + "wrap-left", + "align-end", + "align-start" + ] + } + }, + "required": ["width", "widthType", "layout"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": true, + "required": ["type"] + }, + "link_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["link"] + }, + "attrs": { + "type": "object", + "properties": { + "href": { + "type": "string" + }, + "title": { + "type": "string" + }, + "id": { + "type": "string" + }, + "collection": { + "type": "string" + }, + "occurrenceKey": { + "type": "string" + } + }, + "required": ["href"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "media_node": { + "type": "object", + "properties": { + "type": { + "enum": ["media"] + }, + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/link_mark" + }, + { + "$ref": "#/definitions/annotation_mark" + }, + { + "$ref": "#/definitions/border_mark" + } + ] + } + }, + "attrs": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "enum": ["link", "file"] + }, + "id": { + "minLength": 1, + "type": "string" + }, + "alt": { + "type": "string" + }, + "collection": { + "type": "string" + }, + "height": { + "type": "number" + }, + "occurrenceKey": { + "minLength": 1, + "type": "string" + }, + "width": { + "type": "number" + } + }, + "required": ["type", "id", "collection"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "enum": ["external"] + }, + "alt": { + "type": "string" + }, + "height": { + "type": "number" + }, + "width": { + "type": "number" + }, + "url": { + "type": "string" + } + }, + "required": ["type", "url"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "annotation_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["annotation"] + }, + "attrs": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "annotationType": { + "enum": ["inlineComment"] + } + }, + "required": ["id", "annotationType"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "border_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["border"] + }, + "attrs": { + "type": "object", + "properties": { + "size": { + "type": "number", + "minimum": 1, + "maximum": 3 + }, + "color": { + "pattern": "^#[0-9a-fA-F]{8}$|^#[0-9a-fA-F]{6}$", + "type": "string" + } + }, + "required": ["size", "color"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "hardBreak_node": { + "type": "object", + "properties": { + "type": { + "enum": ["hardBreak"] + }, + "attrs": { + "type": "object", + "properties": { + "text": { + "enum": ["\n"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "mention_node": { + "type": "object", + "properties": { + "type": { + "enum": ["mention"] + }, + "attrs": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "localId": { + "type": "string" + }, + "text": { + "type": "string" + }, + "accessLevel": { + "type": "string" + }, + "userType": { + "enum": ["DEFAULT", "SPECIAL", "APP"] + } + }, + "required": ["id"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "emoji_node": { + "type": "object", + "properties": { + "type": { + "enum": ["emoji"] + }, + "attrs": { + "type": "object", + "properties": { + "shortName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "required": ["shortName"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "date_node": { + "type": "object", + "properties": { + "type": { + "enum": ["date"] + }, + "attrs": { + "type": "object", + "properties": { + "timestamp": { + "minLength": 1, + "type": "string" + } + }, + "required": ["timestamp"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "placeholder_node": { + "type": "object", + "properties": { + "type": { + "enum": ["placeholder"] + }, + "attrs": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "inlineCard_node": { + "type": "object", + "properties": { + "type": { + "enum": ["inlineCard"] + }, + "attrs": { + "anyOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": ["url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "data": {} + }, + "required": ["data"], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "status_node": { + "type": "object", + "properties": { + "type": { + "enum": ["status"] + }, + "attrs": { + "type": "object", + "properties": { + "text": { + "minLength": 1, + "type": "string" + }, + "color": { + "enum": ["neutral", "purple", "blue", "red", "yellow", "green"] + }, + "localId": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "required": ["text", "color"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "formatted_text_inline_node": { + "allOf": [ + { + "$ref": "#/definitions/text_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/link_mark" + }, + { + "$ref": "#/definitions/em_mark" + }, + { + "$ref": "#/definitions/strong_mark" + }, + { + "$ref": "#/definitions/strike_mark" + }, + { + "$ref": "#/definitions/subsup_mark" + }, + { + "$ref": "#/definitions/underline_mark" + }, + { + "$ref": "#/definitions/textColor_mark" + }, + { + "$ref": "#/definitions/annotation_mark" + }, + { + "$ref": "#/definitions/backgroundColor_mark" + } + ] + } + } + }, + "additionalProperties": true + } + ] + }, + "em_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["em"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + "strong_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["strong"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + "strike_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["strike"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + "subsup_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["subsup"] + }, + "attrs": { + "type": "object", + "properties": { + "type": { + "enum": ["sub", "sup"] + } + }, + "required": ["type"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "underline_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["underline"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + "textColor_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["textColor"] + }, + "attrs": { + "type": "object", + "properties": { + "color": { + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + } + }, + "required": ["color"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "backgroundColor_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["backgroundColor"] + }, + "attrs": { + "type": "object", + "properties": { + "color": { + "pattern": "^#[0-9a-fA-F]{6}$", + "type": "string" + } + }, + "required": ["color"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "code_inline_node": { + "allOf": [ + { + "$ref": "#/definitions/text_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/code_mark" + }, + { + "$ref": "#/definitions/link_mark" + }, + { + "$ref": "#/definitions/annotation_mark" + } + ] + } + } + }, + "additionalProperties": true + } + ] + }, + "code_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["code"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + "caption_node": { + "type": "object", + "properties": { + "type": { + "enum": ["caption"] + }, + "content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/hardBreak_node" + }, + { + "$ref": "#/definitions/mention_node" + }, + { + "$ref": "#/definitions/emoji_node" + }, + { + "$ref": "#/definitions/date_node" + }, + { + "$ref": "#/definitions/placeholder_node" + }, + { + "$ref": "#/definitions/inlineCard_node" + }, + { + "$ref": "#/definitions/status_node" + }, + { + "$ref": "#/definitions/formatted_text_inline_node" + }, + { + "$ref": "#/definitions/code_inline_node" + } + ] + }, + "minItems": 0 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "mediaSingle_caption_node": { + "allOf": [ + { + "$ref": "#/definitions/mediaSingle_node" + }, + { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/media_node" + }, + { + "$ref": "#/definitions/caption_node" + } + ], + "minItems": 1, + "maxItems": 2 + } + }, + "required": ["content"], + "additionalProperties": true + } + ] + }, + "mediaSingle_full_node": { + "allOf": [ + { + "$ref": "#/definitions/mediaSingle_node" + }, + { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/media_node" + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": ["content"], + "additionalProperties": true + } + ] + }, + "inlineExtension_node": { + "type": "object", + "properties": { + "type": { + "enum": ["inlineExtension"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "extensionKey": { + "minLength": 1, + "type": "string" + }, + "extensionType": { + "minLength": 1, + "type": "string" + }, + "parameters": {}, + "text": { + "type": "string" + }, + "localId": { + "minLength": 1, + "type": "string" + } + }, + "required": ["extensionKey", "extensionType"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "inlineExtension_with_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/inlineExtension_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/dataConsumer_mark" + }, + { + "$ref": "#/definitions/fragment_mark" + } + ] + } + } + }, + "additionalProperties": true + } + ] + }, + "dataConsumer_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["dataConsumer"] + }, + "attrs": { + "type": "object", + "properties": { + "sources": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": ["sources"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "fragment_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["fragment"] + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "minLength": 1, + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["localId"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "mediaInline_node": { + "type": "object", + "properties": { + "type": { + "enum": ["mediaInline"] + }, + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/link_mark" + }, + { + "$ref": "#/definitions/annotation_mark" + }, + { + "$ref": "#/definitions/border_mark" + } + ] + } + }, + "attrs": { + "type": "object", + "properties": { + "type": { + "enum": ["link", "file", "image"] + }, + "id": { + "minLength": 1, + "type": "string" + }, + "alt": { + "type": "string" + }, + "collection": { + "type": "string" + }, + "occurrenceKey": { + "minLength": 1, + "type": "string" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "data": {} + }, + "required": ["id", "collection"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "inline_node": { + "anyOf": [ + { + "$ref": "#/definitions/formatted_text_inline_node" + }, + { + "$ref": "#/definitions/code_inline_node" + }, + { + "$ref": "#/definitions/date_node" + }, + { + "$ref": "#/definitions/emoji_node" + }, + { + "$ref": "#/definitions/hardBreak_node" + }, + { + "$ref": "#/definitions/inlineCard_node" + }, + { + "$ref": "#/definitions/mention_node" + }, + { + "$ref": "#/definitions/placeholder_node" + }, + { + "$ref": "#/definitions/status_node" + }, + { + "$ref": "#/definitions/inlineExtension_with_marks_node" + }, + { + "$ref": "#/definitions/mediaInline_node" + } + ] + }, + "paragraph_node": { + "type": "object", + "properties": { + "type": { + "enum": ["paragraph"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/inline_node" + } + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "paragraph_with_alignment_node": { + "allOf": [ + { + "$ref": "#/definitions/paragraph_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/alignment_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "alignment_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["alignment"] + }, + "attrs": { + "type": "object", + "properties": { + "align": { + "enum": ["center", "end"] + } + }, + "required": ["align"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "paragraph_with_indentation_node": { + "allOf": [ + { + "$ref": "#/definitions/paragraph_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/indentation_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "indentation_mark": { + "type": "object", + "properties": { + "type": { + "enum": ["indentation"] + }, + "attrs": { + "type": "object", + "properties": { + "level": { + "type": "number", + "minimum": 1, + "maximum": 6 + } + }, + "required": ["level"], + "additionalProperties": false + } + }, + "required": ["type", "attrs"], + "additionalProperties": false + }, + "paragraph_with_no_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/paragraph_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + }, + "taskItem_node": { + "type": "object", + "properties": { + "type": { + "enum": ["taskItem"] + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "type": "string" + }, + "state": { + "enum": ["TODO", "DONE"] + } + }, + "required": ["localId", "state"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/inline_node" + } + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "taskList_node": { + "type": "object", + "properties": { + "type": { + "enum": ["taskList"] + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "type": "string" + } + }, + "required": ["localId"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/taskItem_node" + }, + { + "anyOf": [ + { + "$ref": "#/definitions/taskItem_node" + }, + { + "$ref": "#/definitions/taskList_node" + } + ] + } + ], + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "attrs", "content"] + }, + "bulletList_node": { + "type": "object", + "properties": { + "type": { + "enum": ["bulletList"] + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/listItem_node" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "listItem_node": { + "type": "object", + "properties": { + "type": { + "enum": ["listItem"] + }, + "content": { + "type": "array", + "items": [ + { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + } + ] + }, + { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + } + ] + } + ], + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "orderedList_node": { + "type": "object", + "properties": { + "type": { + "enum": ["orderedList"] + }, + "attrs": { + "type": "object", + "properties": { + "order": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/listItem_node" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "blockquote_node": { + "type": "object", + "properties": { + "type": { + "enum": ["blockquote"] + }, + "content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/bulletList_node" + } + ] + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "decisionItem_node": { + "type": "object", + "properties": { + "type": { + "enum": ["decisionItem"] + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "required": ["localId", "state"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/inline_node" + } + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "decisionList_node": { + "type": "object", + "properties": { + "type": { + "enum": ["decisionList"] + }, + "attrs": { + "type": "object", + "properties": { + "localId": { + "type": "string" + } + }, + "required": ["localId"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/decisionItem_node" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "attrs", "content"] + }, + "embedCard_node": { + "type": "object", + "properties": { + "type": { + "enum": ["embedCard"] + }, + "attrs": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "layout": { + "enum": [ + "wide", + "full-width", + "center", + "wrap-right", + "wrap-left", + "align-end", + "align-start" + ] + }, + "width": { + "type": "number", + "maximum": 100, + "minimum": 0 + }, + "originalHeight": { + "type": "number" + }, + "originalWidth": { + "type": "number" + } + }, + "required": ["url", "layout"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "extension_node": { + "type": "object", + "properties": { + "type": { + "enum": ["extension"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "extensionKey": { + "minLength": 1, + "type": "string" + }, + "extensionType": { + "minLength": 1, + "type": "string" + }, + "parameters": {}, + "text": { + "type": "string" + }, + "layout": { + "enum": ["wide", "full-width", "default"] + }, + "localId": { + "minLength": 1, + "type": "string" + } + }, + "required": ["extensionKey", "extensionType"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "extension_with_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/extension_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/dataConsumer_mark" + }, + { + "$ref": "#/definitions/fragment_mark" + } + ] + } + } + }, + "additionalProperties": true + } + ] + }, + "heading_node": { + "type": "object", + "properties": { + "type": { + "enum": ["heading"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "level": { + "type": "number", + "minimum": 1, + "maximum": 6 + }, + "localId": { + "type": "string" + } + }, + "required": ["level"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/inline_node" + } + } + }, + "additionalProperties": false, + "required": ["type", "attrs"] + }, + "heading_with_indentation_node": { + "allOf": [ + { + "$ref": "#/definitions/heading_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/indentation_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "heading_with_no_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/heading_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + }, + "heading_with_alignment_node": { + "allOf": [ + { + "$ref": "#/definitions/heading_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/alignment_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "mediaGroup_node": { + "type": "object", + "properties": { + "type": { + "enum": ["mediaGroup"] + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/media_node" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "rule_node": { + "type": "object", + "properties": { + "type": { + "enum": ["rule"] + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "panel_node": { + "type": "object", + "properties": { + "type": { + "enum": ["panel"] + }, + "attrs": { + "type": "object", + "properties": { + "panelType": { + "enum": [ + "info", + "note", + "tip", + "warning", + "error", + "success", + "custom" + ] + }, + "panelIcon": { + "type": "string" + }, + "panelIconId": { + "type": "string" + }, + "panelIconText": { + "type": "string" + }, + "panelColor": { + "type": "string" + } + }, + "required": ["panelType"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/blockCard_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/decisionList_node" + } + ] + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "attrs", "content"] + }, + "nestedExpand_content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/decisionList_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/panel_node" + }, + { + "$ref": "#/definitions/blockquote_node" + } + ] + }, + "minItems": 1 + }, + "nestedExpand_with_no_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/nestedExpand_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + }, + "table_cell_node": { + "type": "object", + "properties": { + "type": { + "enum": ["tableCell"] + }, + "attrs": { + "type": "object", + "properties": { + "colspan": { + "type": "number" + }, + "rowspan": { + "type": "number" + }, + "colwidth": { + "type": "array", + "items": { + "type": "number" + } + }, + "background": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "$ref": "#/definitions/table_cell_content" + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "nestedExpand_node": { + "type": "object", + "properties": { + "type": { + "enum": ["nestedExpand"] + }, + "attrs": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "$ref": "#/definitions/nestedExpand_content" + } + }, + "additionalProperties": false, + "required": ["type", "content", "attrs"] + }, + "table_header_node": { + "type": "object", + "properties": { + "type": { + "enum": ["tableHeader"] + }, + "attrs": { + "type": "object", + "properties": { + "colspan": { + "type": "number" + }, + "rowspan": { + "type": "number" + }, + "colwidth": { + "type": "array", + "items": { + "type": "number" + } + }, + "background": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "$ref": "#/definitions/table_cell_content" + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "table_cell_content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/paragraph_with_alignment_node" + }, + { + "$ref": "#/definitions/panel_node" + }, + { + "$ref": "#/definitions/blockquote_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/heading_with_alignment_node" + }, + { + "$ref": "#/definitions/heading_with_indentation_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/decisionList_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/blockCard_node" + }, + { + "$ref": "#/definitions/embedCard_node" + }, + { + "$ref": "#/definitions/extension_with_marks_node" + }, + { + "$ref": "#/definitions/nestedExpand_with_no_marks_node" + } + ] + }, + "minItems": 1 + }, + "table_row_node": { + "type": "object", + "properties": { + "type": { + "enum": ["tableRow"] + }, + "content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/table_cell_node" + }, + { + "$ref": "#/definitions/table_header_node" + } + ] + } + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "table_node": { + "type": "object", + "properties": { + "type": { + "enum": ["table"] + }, + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/fragment_mark" + } + }, + "attrs": { + "type": "object", + "properties": { + "displayMode": { + "enum": ["default", "fixed"] + }, + "isNumberColumnEnabled": { + "type": "boolean" + }, + "layout": { + "enum": [ + "wide", + "full-width", + "center", + "align-end", + "align-start", + "default" + ] + }, + "localId": { + "type": "string", + "minLength": 1 + }, + "width": { + "type": "number" + } + }, + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/table_row_node" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "non_nestable_block_content": { + "anyOf": [ + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/panel_node" + }, + { + "$ref": "#/definitions/blockquote_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/decisionList_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/table_node" + }, + { + "$ref": "#/definitions/blockCard_node" + }, + { + "$ref": "#/definitions/embedCard_node" + }, + { + "$ref": "#/definitions/extension_with_marks_node" + } + ] + }, + "bodiedExtension_node": { + "type": "object", + "properties": { + "type": { + "enum": ["bodiedExtension"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "extensionKey": { + "minLength": 1, + "type": "string" + }, + "extensionType": { + "minLength": 1, + "type": "string" + }, + "parameters": {}, + "text": { + "type": "string" + }, + "layout": { + "enum": ["wide", "full-width", "default"] + }, + "localId": { + "minLength": 1, + "type": "string" + } + }, + "required": ["extensionKey", "extensionType"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/non_nestable_block_content" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "attrs", "content"] + }, + "bodiedExtension_with_marks_node": { + "allOf": [ + { + "$ref": "#/definitions/bodiedExtension_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/dataConsumer_mark" + }, + { + "$ref": "#/definitions/fragment_mark" + } + ] + } + } + }, + "additionalProperties": true + } + ] + }, + "expand_node": { + "type": "object", + "properties": { + "type": { + "enum": ["expand"] + }, + "marks": { + "type": "array" + }, + "attrs": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/non_nestable_block_content" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "content", "attrs"] + }, + "expand_with_no_mark_node": { + "allOf": [ + { + "$ref": "#/definitions/expand_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "maxItems": 0 + } + }, + "additionalProperties": true + } + ] + }, + "expand_with_breakout_mark_node": { + "allOf": [ + { + "$ref": "#/definitions/expand_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/breakout_mark" + } + } + }, + "additionalProperties": true + } + ] + }, + "block_content": { + "anyOf": [ + { + "$ref": "#/definitions/blockCard_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/paragraph_with_alignment_node" + }, + { + "$ref": "#/definitions/paragraph_with_indentation_node" + }, + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/blockquote_node" + }, + { + "$ref": "#/definitions/decisionList_node" + }, + { + "$ref": "#/definitions/embedCard_node" + }, + { + "$ref": "#/definitions/extension_with_marks_node" + }, + { + "$ref": "#/definitions/heading_with_indentation_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/heading_with_alignment_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/panel_node" + }, + { + "$ref": "#/definitions/table_node" + }, + { + "$ref": "#/definitions/bodiedExtension_with_marks_node" + }, + { + "$ref": "#/definitions/expand_with_no_mark_node" + } + ] + }, + "layoutColumn_node": { + "type": "object", + "properties": { + "type": { + "enum": ["layoutColumn"] + }, + "attrs": { + "type": "object", + "properties": { + "width": { + "type": "number", + "minimum": 0, + "maximum": 100 + } + }, + "required": ["width"], + "additionalProperties": false + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/block_content" + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "required": ["type", "attrs", "content"] + }, + "layoutSection_node": { + "type": "object", + "properties": { + "type": { + "enum": ["layoutSection"] + }, + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/breakout_mark" + } + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/layoutColumn_node" + } + } + }, + "additionalProperties": false, + "required": ["type", "content"] + }, + "layoutSection_full_node": { + "allOf": [ + { + "$ref": "#/definitions/layoutSection_node" + }, + { + "type": "object", + "properties": { + "marks": { + "type": "array", + "items": { + "$ref": "#/definitions/breakout_mark" + } + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/layoutColumn_node" + }, + "minItems": 2, + "maxItems": 3 + }, + "type": { + "enum": ["layoutSection"] + } + }, + "required": ["type", "content"], + "additionalProperties": false + } + ] + }, + "doc_node": { + "type": "object", + "properties": { + "type": { + "enum": ["doc"] + }, + "content": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/blockCard_node" + }, + { + "$ref": "#/definitions/codeBlock_with_marks_node" + }, + { + "$ref": "#/definitions/codeBlock_with_no_marks_node" + }, + { + "$ref": "#/definitions/mediaSingle_caption_node" + }, + { + "$ref": "#/definitions/mediaSingle_full_node" + }, + { + "$ref": "#/definitions/paragraph_with_alignment_node" + }, + { + "$ref": "#/definitions/paragraph_with_indentation_node" + }, + { + "$ref": "#/definitions/paragraph_with_no_marks_node" + }, + { + "$ref": "#/definitions/taskList_node" + }, + { + "$ref": "#/definitions/orderedList_node" + }, + { + "$ref": "#/definitions/bulletList_node" + }, + { + "$ref": "#/definitions/blockquote_node" + }, + { + "$ref": "#/definitions/decisionList_node" + }, + { + "$ref": "#/definitions/embedCard_node" + }, + { + "$ref": "#/definitions/extension_with_marks_node" + }, + { + "$ref": "#/definitions/heading_with_indentation_node" + }, + { + "$ref": "#/definitions/heading_with_no_marks_node" + }, + { + "$ref": "#/definitions/heading_with_alignment_node" + }, + { + "$ref": "#/definitions/mediaGroup_node" + }, + { + "$ref": "#/definitions/rule_node" + }, + { + "$ref": "#/definitions/panel_node" + }, + { + "$ref": "#/definitions/table_node" + }, + { + "$ref": "#/definitions/bodiedExtension_with_marks_node" + }, + { + "$ref": "#/definitions/expand_with_no_mark_node" + }, + { + "$ref": "#/definitions/expand_with_breakout_mark_node" + }, + { + "$ref": "#/definitions/layoutSection_full_node" + } + ] + } + }, + "version": { + "enum": [1] + } + }, + "additionalProperties": false, + "required": ["version", "type", "content"] + } + } +} diff --git a/src/test/resources/schema/issue386-v7.json b/src/test/resources/schema/issue386-v7.json deleted file mode 100644 index a63d0b649..000000000 --- a/src/test/resources/schema/issue386-v7.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "type": "object", - "properties": { - "street_address": { - "type": "string" - }, - "country": { - "default": "United States of America", - "enum": ["United States of America", "Canada", "Netherlands"] - } - }, - "allOf": [ - { - "if": { - "properties": { "country": { "const": "United States of America" } } - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Canada" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } - } - }, - { - "if": { - "properties": { "country": { "const": "Netherlands" } }, - "required": ["country"] - }, - "then": { - "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } - } - } - ] -} \ No newline at end of file diff --git a/src/test/resources/schema/issue396-v7.json b/src/test/resources/schema/issue396-v7.json index 710518414..cfb723341 100644 --- a/src/test/resources/schema/issue396-v7.json +++ b/src/test/resources/schema/issue396-v7.json @@ -27,7 +27,7 @@ "definitions": { "props": { "minLength": 2, - "pattern": "[A-Z]" + "pattern": "[A-Z]{2,}" }, "xyz": { "enum": [ diff --git a/src/test/resources/schema/issue467.json b/src/test/resources/schema/issue467.json new file mode 100644 index 000000000..257ea1654 --- /dev/null +++ b/src/test/resources/schema/issue467.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "default_schema", + "description": "Default Description", + "properties": { + "tags": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "value" : { + "type": "string" + }, + "category" : { + "type": "string" + } + }, + "required": [ "value" ] + } + ] + } + }, + "required": [ "tags" ] +} \ No newline at end of file diff --git a/src/test/resources/schema/issue470-v7.json b/src/test/resources/schema/issue470-v7.json deleted file mode 100644 index 63fa12424..000000000 --- a/src/test/resources/schema/issue470-v7.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/issue-470.json", - "title": "OneOf validation message", - "description": "Test description", - "type": "object", - "properties": { - "search": { - "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "byName": { - "type": "object", - "properties": { - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "byAge": { - "type": "object", - "properties": { - "age": { - "type": "integer", - "maximum": 150, - "minimum": 1 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - } - }, - "additionalProperties": false -} diff --git a/src/test/resources/schema/issue491-v7.json b/src/test/resources/schema/issue491-v7.json deleted file mode 100644 index 75c6292b2..000000000 --- a/src/test/resources/schema/issue491-v7.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/issue-470.json", - "title": "OneOf validation message", - "description": "Test description", - "type": "object", - "properties": { - "search": { - "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "searchAge": { - "type": "object", - "properties": { - "age": { - "type": "integer", - "maximum": 150, - "minimum": 1 - } - }, - "required": [ - "age" - ] - } - }, - "required": [ - "searchAge" - ] - }, - { - "type": "object", - "properties": { - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - } - }, - "required": [ - "name" - ] - } - ] - } - }, - "additionalProperties": false -} diff --git a/src/test/resources/schema/issue491_2-v7.json b/src/test/resources/schema/issue491_2-v7.json deleted file mode 100644 index a4e495180..000000000 --- a/src/test/resources/schema/issue491_2-v7.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/issue-470.json", - "title": "OneOf validation message", - "description": "Test description", - "type": "object", - "properties": { - "search": { - "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "byAge": { - "type": "object", - "properties": { - "age": { - "type": "integer", - "maximum": 150, - "minimum": 1 - } - }, - "required": [ - "age" - ] - } - }, - "required": [ - "byAge" - ] - }, - { - "type": "object", - "properties": { - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - } - }, - "required": [ - "name" - ] - } - ] - } - }, - "additionalProperties": false -} diff --git a/src/test/resources/schema/issue491_3-v7.json b/src/test/resources/schema/issue491_3-v7.json deleted file mode 100644 index 0d462d44b..000000000 --- a/src/test/resources/schema/issue491_3-v7.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/issue-470.json", - "title": "OneOf validation message", - "description": "Test description", - "type": "object", - "properties": { - "search": { - "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "age": { - "type": "integer", - "maximum": 150, - "minimum": 1 - } - }, - "required": [ - "age" - ] - }, - { - "type": "object", - "properties": { - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - } - }, - "required": [ - "name" - ] - } - ] - } - }, - "additionalProperties": false -} diff --git a/src/test/resources/schema/issue500_1-v7.json b/src/test/resources/schema/issue500_1-v7.json new file mode 100644 index 000000000..c88c2947b --- /dev/null +++ b/src/test/resources/schema/issue500_1-v7.json @@ -0,0 +1,18 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "type": "integer", + "minimum": 0 + } + } +} diff --git a/src/test/resources/schema/issue500_2-v7.json b/src/test/resources/schema/issue500_2-v7.json new file mode 100644 index 000000000..934c606fa --- /dev/null +++ b/src/test/resources/schema/issue500_2-v7.json @@ -0,0 +1,35 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "type": "integer", + "minimum": 0 + } + }, + "oneOf": [ + { + "properties": { + "firstName": { + "const": "John" + } + } + }, + { + "properties": { + "lastName": { + "const": "Doe" + } + } + } + ] +} + diff --git a/src/test/resources/schema/issue575-2019-09.json b/src/test/resources/schema/issue575-2019-09.json new file mode 100644 index 000000000..7d984cfbd --- /dev/null +++ b/src/test/resources/schema/issue575-2019-09.json @@ -0,0 +1,14 @@ +{ + "$schema" : "https://json-schema.org/draft/2019-09/schema", + "title": "Test Time Zone Schema (for testing time zones with negative offsets)", + "type": "object", + "properties": { + "testDateTime": { + "description": "A date-time value.", + "type" : "string", + "minLength" : 1, + "maxLength" : 32, + "format" : "date-time" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue606-v7.json b/src/test/resources/schema/issue606-v7.json new file mode 100644 index 000000000..f0269072b --- /dev/null +++ b/src/test/resources/schema/issue606-v7.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "V": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "X": { + "type": "string" + } + }, + "required": [ + "X" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "A": { + "type": "string" + }, + "B": { + "type": "string" + }, + "C": { + "type": "string" + } + }, + "anyOf": [ + { + "properties": { + "origin": { + "const": "not-present" + } + }, + "required": [ + "A", + "C" + ] + }, + { + "properties": { + "origin": { + "const": "not-present-either" + } + }, + "required": [ + "A", + "B" + ] + } + ], + "additionalProperties": false, + "required": [ + "A" + ], + "not": { + "type": "object", + "required": [ + "X" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue619.json b/src/test/resources/schema/issue619.json new file mode 100644 index 000000000..69696bf5a --- /dev/null +++ b/src/test/resources/schema/issue619.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "resource:schema/issue619.json", + "oneOf": [ + { + "$ref": "#/definitions/one" + }, + { + "$ref": "#/definitions/two" + } + ], + "definitions": { + "one": { + "type": "integer", + "enum": [1] + }, + "two": { + "type": "integer", + "enum": [2] + }, + "refToOne": { + "$ref": "#/definitions/one" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue627-v7.json b/src/test/resources/schema/issue627-v7.json new file mode 100644 index 000000000..df4091edf --- /dev/null +++ b/src/test/resources/schema/issue627-v7.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "dateTime": { + "type": "string", + "format": "date", + "message": { + "format": "Keep date format yyyy-mm-dd" + } + }, + "uuid": { + "type": "string", + "format": "uuid", + "message": { + "format": "Input should be uuid" + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue664-v7.json b/src/test/resources/schema/issue664-v7.json new file mode 100644 index 000000000..a6a2c5298 --- /dev/null +++ b/src/test/resources/schema/issue664-v7.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "type": "object", + "required": [ + "country", + "postal_code" + ], + "properties": { + "country": { + "type": "string" + }, + "postal_code": { + "type": "string" + } + }, + "allOf": [ + { + "anyOf": [ + { + "oneOf": [ + { + "not": { + "properties": { + "country": { + "const": "United Kingdom" + } + } + } + } + ] + } + ] + }, + { + "if": { + "properties": { + "country": { + "const": "United States of America" + } + } + }, + "then": { + "properties": { + "postal_code": { + "pattern": "[0-9]{5}(-[0-9]{4})?" + } + } + }, + "else": { + "properties": { + "postal_code": { + "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue668-sub1.yml b/src/test/resources/schema/issue668-sub1.yml new file mode 100644 index 000000000..d73ddd150 --- /dev/null +++ b/src/test/resources/schema/issue668-sub1.yml @@ -0,0 +1 @@ +type: object \ No newline at end of file diff --git a/src/test/resources/schema/issue668-sub2.yaml b/src/test/resources/schema/issue668-sub2.yaml new file mode 100644 index 000000000..d73ddd150 --- /dev/null +++ b/src/test/resources/schema/issue668-sub2.yaml @@ -0,0 +1 @@ +type: object \ No newline at end of file diff --git a/src/test/resources/schema/issue668-sub3 b/src/test/resources/schema/issue668-sub3 new file mode 100644 index 000000000..61ef6e4e6 --- /dev/null +++ b/src/test/resources/schema/issue668-sub3 @@ -0,0 +1 @@ +{"type": "object"} \ No newline at end of file diff --git a/src/test/resources/schema/issue668.yml b/src/test/resources/schema/issue668.yml new file mode 100644 index 000000000..e69f5142b --- /dev/null +++ b/src/test/resources/schema/issue668.yml @@ -0,0 +1,9 @@ +$id: resource:/schema/issue668 +type: object +properties: + sub1: + $ref: issue668-sub1.yml + sub2: + $ref: issue668-sub2.yaml + sub3: + $ref: issue668-sub3 \ No newline at end of file diff --git a/src/test/resources/schema/issue687.json b/src/test/resources/schema/issue687.json new file mode 100644 index 000000000..5e2480329 --- /dev/null +++ b/src/test/resources/schema/issue687.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" }, + "b.ar": { "type": "string" }, + "children" : { + "type": "array", + "items": { + "type": "object", + "properties": { + "childFoo": { "type": "string" }, + "c/hildBar": { "type": "string" } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue832-v7.json b/src/test/resources/schema/issue832-v7.json new file mode 100644 index 000000000..94044736b --- /dev/null +++ b/src/test/resources/schema/issue832-v7.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "foo": { + "type": "string", + "format": "no_match" + }, + "contact": { + "type": "string", + "format": "email" + } + }, + "required": ["foo", "contact"], + "type": "object" +} diff --git a/src/test/resources/schema/issue898.json b/src/test/resources/schema/issue898.json new file mode 100644 index 000000000..ab4137c7c --- /dev/null +++ b/src/test/resources/schema/issue898.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { + "type": "string", + "enum": [ + "foo1", + "foo2" + ] + }, + "bar": { + "type": "string", + "pattern": "(bar)+" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/issue928-v07.json b/src/test/resources/schema/issue928-v07.json new file mode 100644 index 000000000..63dd7db8a --- /dev/null +++ b/src/test/resources/schema/issue928-v07.json @@ -0,0 +1,14 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "example": { + "$id": "#example", + "type": "string", + "enum": [ + "A", "B" + ] + } + } +} diff --git a/src/test/resources/schema/issue928-v2019-09.json b/src/test/resources/schema/issue928-v2019-09.json new file mode 100644 index 000000000..ef7d4f397 --- /dev/null +++ b/src/test/resources/schema/issue928-v2019-09.json @@ -0,0 +1,14 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "type": "object", + "definitions": { + "example": { + "$anchor": "example", + "type": "string", + "enum": [ + "A", "B" + ] + } + } +} diff --git a/src/test/resources/schema/issue928-v2020-12.json b/src/test/resources/schema/issue928-v2020-12.json new file mode 100644 index 000000000..483c6d0d4 --- /dev/null +++ b/src/test/resources/schema/issue928-v2020-12.json @@ -0,0 +1,14 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema#", + "type": "object", + "definitions": { + "example": { + "$anchor": "example", + "type": "string", + "enum": [ + "A", "B" + ] + } + } +} diff --git a/src/test/resources/schema/issue936.json b/src/test/resources/schema/issue936.json new file mode 100644 index 000000000..d159f7ba7 --- /dev/null +++ b/src/test/resources/schema/issue936.json @@ -0,0 +1,4 @@ +{ + "$id": "0", + "$schema": "https://json-schema.org/draft/2020-12/schema" +} \ No newline at end of file diff --git a/src/test/resources/schema/main/main.json b/src/test/resources/schema/main/main.json new file mode 100644 index 000000000..eab0e21d8 --- /dev/null +++ b/src/test/resources/schema/main/main.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "main schema", + "type": "object", + "properties": { + "fields": { + "type": "object", + "properties": { + "ids": { + "$ref": "../common/child.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/notAllowedValidation/notAllowedJson.json b/src/test/resources/schema/notAllowedValidation/notAllowedJson.json new file mode 100644 index 000000000..e97b01409 --- /dev/null +++ b/src/test/resources/schema/notAllowedValidation/notAllowedJson.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "field1": {}, + "field2": {} + }, + "additionalProperties": false, + "notAllowed": ["field3"] +} \ No newline at end of file diff --git a/src/test/resources/schema/oas/3.0/petstore.yaml b/src/test/resources/schema/oas/3.0/petstore.yaml new file mode 100644 index 000000000..a03f3f8db --- /dev/null +++ b/src/test/resources/schema/oas/3.0/petstore.yaml @@ -0,0 +1,135 @@ +openapi: 3.0.2 +info: + title: Petstore - OpenAPI 3.0 +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetRequest' + application/xml: + schema: + $ref: '#/components/schemas/PetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PetRequest' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/PetResponse' + application/json: + schema: + $ref: '#/components/schemas/PetResponse' + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pets + summary: Find pet by ID + description: Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate + API error conditions + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + description: param ID of pet that needs to be fetched + exclusiveMaximum: 10 + exclusiveMinimum: 1 + responses: + default: + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/PetResponse' + description: A Pet in JSON format + application/xml: + schema: + $ref: '#/components/schemas/PetResponse' + description: A Pet in XML format + "400": + description: Invalid ID supplied + "404": + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + - api_key: [] +components: + schemas: + PetRequest: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Lizard' + PetResponse: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Lizard' + Pet: + type: object + required: + - petType + properties: + petType: + type: string + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + lizard: '#/components/schemas/Lizard' + Cat: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Cat` + required: + - name + properties: + name: + type: string + Dog: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Dog` + required: + - bark + properties: + bark: + type: string + Lizard: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Lizard` + required: + - lovesRocks + properties: + lovesRocks: + type: boolean \ No newline at end of file diff --git a/src/test/resources/schema/oas/3.1/petstore.json b/src/test/resources/schema/oas/3.1/petstore.json new file mode 100644 index 000000000..adb86a62b --- /dev/null +++ b/src/test/resources/schema/oas/3.1/petstore.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Swagger Petstore - OpenAPI 3.1", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.1 specification. You can find out more about\nSwagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.11" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "https://petstore3.swagger.io/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "delete a pet", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/oas/3.1/petstore.yaml b/src/test/resources/schema/oas/3.1/petstore.yaml new file mode 100644 index 000000000..670e7d8dc --- /dev/null +++ b/src/test/resources/schema/oas/3.1/petstore.yaml @@ -0,0 +1,105 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 +paths: + /pet/{petId}: + get: + tags: + - pets + summary: Find pet by ID + description: Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate + API error conditions + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + description: param ID of pet that needs to be fetched + exclusiveMaximum: 10 + exclusiveMinimum: 1 + responses: + default: + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/PetResponse' + description: A Pet in JSON format + application/xml: + schema: + $ref: '#/components/schemas/PetResponse' + description: A Pet in XML format + "400": + description: Invalid ID supplied + "404": + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + - api_key: [] +components: + schemas: + PetRequest: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Lizard' + PetResponse: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - $ref: '#/components/schemas/Lizard' + Pet: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + type: object + required: + - petType + properties: + petType: + type: string + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + lizard: '#/components/schemas/Lizard' + Cat: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Cat` + required: + - name + properties: + name: + type: string + Dog: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Dog` + required: + - bark + properties: + bark: + type: string + Lizard: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Lizard` + required: + - lovesRocks + properties: + lovesRocks: + type: boolean \ No newline at end of file diff --git a/src/test/resources/schema/output-format-schema.json b/src/test/resources/schema/output-format-schema.json new file mode 100644 index 000000000..10d395785 --- /dev/null +++ b/src/test/resources/schema/output-format-schema.json @@ -0,0 +1,18 @@ +{ + "$id": "https://example.com/polygon", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "point": { + "type": "object", + "properties": { + "x": { "type": "number" }, + "y": { "type": "number" } + }, + "additionalProperties": false, + "required": [ "x", "y" ] + } + }, + "type": "array", + "items": { "$ref": "#/$defs/point" }, + "minItems": 3 +} \ No newline at end of file diff --git a/src/test/resources/schema/read-only-schema.json b/src/test/resources/schema/read-only-schema.json new file mode 100644 index 000000000..4b1e3cf09 --- /dev/null +++ b/src/test/resources/schema/read-only-schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Read Only Schema", + "description": "Testing Read Only Schema Validation", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "readOnly": true + }, + "lastName": { + "type": "string" + } + } +} diff --git a/src/test/resources/schema/ref-main-schema-resource.json b/src/test/resources/schema/ref-main-schema-resource.json new file mode 100644 index 000000000..c42fecde4 --- /dev/null +++ b/src/test/resources/schema/ref-main-schema-resource.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://www.example.org/driver", + "type": "object", + "required": [ + "DriverProperties" + ], + "properties": { + "DriverProperties": { + "type": "object", + "properties": { + "CommonProperties": { + "$ref": "common#/definitions/DriverProperties" + } + }, + "required": [ + "CommonProperties" + ], + "additionalProperties": false + } + }, + "additionalProperties": false, + "definitions": { + "common": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://www.example.org/common", + "type": "object", + "additionalProperties": false, + "definitions": { + "DriverProperties": { + "type": "object", + "properties": { + "field1": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "field2": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "field1" + ], + "additionalProperties": false + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/ref-main.json b/src/test/resources/schema/ref-main.json new file mode 100644 index 000000000..c219fec5f --- /dev/null +++ b/src/test/resources/schema/ref-main.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required" : ["DriverProperties"], + "properties": { + "DriverProperties": { + "type": "object", + "properties": { + "CommonProperties": { + "$ref": "ref-ref.json#/definitions/DriverProperties" + } + }, + "required": ["CommonProperties"], + "additionalProperties": false + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/src/test/resources/schema/ref-ref.json b/src/test/resources/schema/ref-ref.json new file mode 100644 index 000000000..3b43a5d06 --- /dev/null +++ b/src/test/resources/schema/ref-ref.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "definitions": { + "DriverProperties": { + "type": "object", + "properties": { + "field1": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "field2": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": ["field1"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json b/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json new file mode 100644 index 000000000..f9eadfc36 --- /dev/null +++ b/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json @@ -0,0 +1,21 @@ +[ + { + "description": "unevaluatedItems should not affect sub-schemas", + "schema": { + "unevaluatedItems": { + "type": "object" + } + }, + "tests": [ + { + "description": "unevaluated item bar is in sub-schema", + "data": [ + { + "foo": ["bar"] + } + ], + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json b/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json new file mode 100644 index 000000000..0c9099768 --- /dev/null +++ b/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json @@ -0,0 +1,2247 @@ +[ + { + "description": "schema with a $ref", + "schema": { + "title": "Person", + "type": "object", + "definitions": { + "address": { + "properties": { + "residence": { + "$ref": "#/definitions/residence", + "description": "Residence details where the person lives" + }, + "city": { + "type": "string", + "description": "City where the person lives." + }, + "street": { + "type": "string", + "description": "street where the person lives." + }, + "pinCode": { + "type": "number", + "description": "pincode of street" + } + }, + "unevaluatedProperties": false + }, + "residence": { + "properties": { + "flatNumber": { + "type": "string" + }, + "flatName": { + "type": "string" + }, + "landmark": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + }, + "address": { + "description": "Address of the person.", + "$ref": "#/definitions/address" + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Basic Success Test", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "address": { + "city": "Hyderabad", + "pinCode": 500025 + } + }, + "valid": true + }, + { + "description": "Unevaluated Property - Outside $ref", + "data": { + "firstName": "First Name", + "invalid": 18, + "lastName": "Last Name", + "address": { + "city": "Hyderabad", + "pinCode": 500025 + } + }, + "valid": false, + "errors": [ + ": property 'invalid' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Unevaluated Property - inside $ref", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "address": { + "city": "Hyderabad", + "pinCode": 500025, + "invalid": "invalid" + } + }, + "valid": false, + "errors": [ + "/address: property 'invalid' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Unevaluated - multiple properties", + "data": { + "invalid1": "First Name", + "age": 18, + "lastName": "Last Name", + "address": { + "city": "Hyderabad", + "pinCode": 500025, + "invalid2": "invalid" + } + }, + "valid": false, + "errors": [ + "/address: property 'invalid2' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Inside nested $ref", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "address": { + "city": "Hyderabad", + "pinCode": 500025, + "residence": { + "invalid": "" + } + } + }, + "valid": false, + "errors": [ + "/address/residence: property 'invalid' is not evaluated and the schema does not allow unevaluated properties" + ] + } + ] + }, + { + "description": "schema with a oneOf", + "schema": { + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + }, + "vehicle": { + "oneOf": [ + { + "title": "Car", + "required": [ + "wheels", + "headlights" + ], + "properties": { + "wheels": { + "type": "string" + }, + "headlights": { + "type": "string" + } + } + }, + { + "title": "Boat", + "required": [ + "pontoons" + ], + "properties": { + "pontoons": { + "type": "string" + } + } + }, + { + "title": "Plane", + "required": [ + "wings" + ], + "properties": { + "wings": { + "type": "string" + } + } + } + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "Data with oneOf and one property", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons" + } + }, + "valid": true + }, + { + "description": "Data which satisfies 1 oneOf schemas, but fails due to unevaluated prop", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "wheels": "wheels" + } + }, + "valid": false, + "errors": [ + "/vehicle: property 'wheels' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Data which satisfies 2 oneOf schemas", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "wings": "wings" + } + }, + "valid": false, + "errors": [ + "/vehicle: must be valid to one and only one schema, but 2 are valid with indexes '1, 2'", + "/vehicle: required property 'wheels' not found", + "/vehicle: required property 'headlights' not found" + ] + }, + { + "description": "Data which satisfies 2 oneOf schemas and an invalid prop", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "wings": "wings", + "invalid": "invalid" + } + }, + "valid": false, + "errors": [ + "/vehicle: must be valid to one and only one schema, but 2 are valid with indexes '1, 2'", + "/vehicle: property 'invalid' is not evaluated and the schema does not allow unevaluated properties", + "/vehicle: required property 'wheels' not found", + "/vehicle: required property 'headlights' not found" + ] + }, + { + "description": "Data which doesn't satisfy any of oneOf schemas but having an invalid prop", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "invalid": "invalid" + } + }, + "valid": false, + "errors": [ + "/vehicle: must be valid to one and only one schema, but 0 are valid", + "/vehicle: property 'invalid' is not evaluated and the schema does not allow unevaluated properties" + ] + } + ] + }, + { + "description": "schema with a anyOf", + "schema": { + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + }, + "vehicle": { + "anyOf": [ + { + "title": "Car", + "required": [ + "wheels", + "headlights" + ], + "properties": { + "wheels": { + "type": "string" + }, + "headlights": { + "type": "string" + } + } + }, + { + "title": "Boat", + "required": [ + "pontoons" + ], + "properties": { + "pontoons": { + "type": "string" + } + } + }, + { + "title": "Plane", + "required": [ + "wings" + ], + "properties": { + "wings": { + "type": "string" + } + } + } + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "Data with 1 valid AnyOf", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons" + } + }, + "valid": true + }, + { + "description": "Data with 1 AnyOf and 1 unevaluated property", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "unevaluated": true + } + }, + "valid": false, + "errors": [ + "/vehicle: property 'unevaluated' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Data with just unevaluated property", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "unevaluated": true + } + }, + "valid": false, + "errors": [ + "/vehicle: property 'unevaluated' is not evaluated and the schema does not allow unevaluated properties" + ] + }, + { + "description": "Data with 2 valid AnyOf and 1 unevaluated property", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "wings": "wings", + "unevaluated": true + } + }, + "valid": false, + "errors": [ + "/vehicle: property 'unevaluated' is not evaluated and the schema does not allow unevaluated properties" + ] + } + ] + }, + { + "description": "schema with a allOf", + "schema": { + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + }, + "vehicle": { + "allOf": [ + { + "title": "Car", + "required": [ + "wheels" + ], + "properties": { + "wheels": { + "type": "string" + }, + "headlights": { + "type": "string" + } + } + }, + { + "title": "Boat", + "required": [ + "pontoons" + ], + "properties": { + "pontoons": { + "type": "string" + } + } + }, + { + "title": "Plane", + "required": [ + "wings" + ], + "properties": { + "wings": { + "type": "string" + } + } + } + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "Data with allOf", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "wheels": "wheels", + "pontoons": "pontoons", + "wings": "wings" + } + }, + "valid": true + }, + { + "description": "Data with invalid allOf and one unevaluated property", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "wheels": "wheels", + "pontoons": "pontoons", + "unevaluated": true + } + }, + "valid": false, + "errors": [ + "/vehicle: required property 'wings' not found", + "/vehicle: property 'unevaluated' is not evaluated and the schema does not allow unevaluated properties" + ] + } + ] + }, + { + "description": "schema with if then and else", + "schema": { + "title": "Person", + "type": "object", + "if": { + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "firstName" + ] + }, + "then": { + "properties": { + "lastName": { + "type": "string", + "description": "The person's last name." + } + } + }, + "else": { + "properties": { + "surName": { + "type": "string", + "description": "The person's sur name." + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Data with if then and else", + "data": { + "age": 18, + "surName": "Sur Name" + }, + "valid": false + }, + { + "description": "Data - else schema with one unevaluated property", + "data": { + "age": 18, + "surName": "Sur Name", + "unevaluated": true + }, + "valid": false, + "errors": [ + ": property 'age' is not evaluated and the schema does not allow unevaluated properties", + ": property 'unevaluated' is not evaluated and the schema does not allow unevaluated properties" + ] + } + ] + }, + { + "description": "schema with additional properties as object", + "schema": { + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": { + "properties": { + "location": { + "type": "string", + "description": "The person's location." + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Data with additional properties as object", + "data": { + "age": 18, + "otherProperty": { + "location": "hello" + } + }, + "valid": true + } + ] + }, + { + "description": "schema with additional properties as type", + "schema": { + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": { + "type": "string" + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Data with additional properties as type", + "data": { + "age": 18, + "otherProperty": "test" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties should not affect sub-schemas", + "schema": { + "properties": { + "foo": {} + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluated property bar is in sub-schema", + "data": { + "foo": { + "bar": "baz" + } + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties true", + "schema": { + "type": "object", + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties false", + "schema": { + "type": "object", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent properties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent patternProperties", + "schema": { + "type": "object", + "patternProperties": { + "^foo": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent additionalProperties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "additionalProperties": true, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested properties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "properties": { + "bar": { + "type": "string" + } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested patternProperties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "patternProperties": { + "^bar": { + "type": "string" + } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested additionalProperties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "additionalProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested unevaluatedProperties", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": { + "type": "string", + "maxLength": 2 + } + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with anyOf", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "anyOf": [ + { + "properties": { + "bar": { + "const": "bar" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "baz": { + "const": "baz" + } + }, + "required": [ + "baz" + ] + }, + { + "properties": { + "quux": { + "const": "quux" + } + }, + "required": [ + "quux" + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when one matches and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when one matches and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "not-baz" + }, + "valid": false + }, + { + "description": "when two match and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "quux": "not-quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with oneOf", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "oneOf": [ + { + "properties": { + "bar": { + "const": "bar" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "baz": { + "const": "baz" + } + }, + "required": [ + "baz" + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "quux": "quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with not", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "not": { + "not": { + "properties": { + "bar": { + "const": "bar" + } + }, + "required": [ + "bar" + ] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else", + "schema": { + "type": "object", + "if": { + "properties": { + "foo": { + "const": "then" + } + }, + "required": [ + "foo" + ] + }, + "then": { + "properties": { + "bar": { + "type": "string" + } + }, + "required": [ + "bar" + ] + }, + "else": { + "properties": { + "baz": { + "type": "string" + } + }, + "required": [ + "baz" + ] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, then not defined", + "schema": { + "type": "object", + "if": { + "properties": { + "foo": { + "const": "then" + } + }, + "required": [ + "foo" + ] + }, + "else": { + "properties": { + "baz": { + "type": "string" + } + }, + "required": [ + "baz" + ] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": false + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, else not defined", + "schema": { + "type": "object", + "if": { + "properties": { + "foo": { + "const": "then" + } + }, + "required": [ + "foo" + ] + }, + "then": { + "properties": { + "bar": { + "type": "string" + } + }, + "required": [ + "bar" + ] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with dependentSchemas", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": { + "const": "bar" + } + }, + "required": [ + "bar" + ] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with boolean schemas", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + true + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $ref", + "schema": { + "type": "object", + "$ref": "#/$defs/bar", + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": false, + "$defs": { + "bar": { + "properties": { + "bar": { + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins", + "schema": { + "allOf": [ + { + "properties": { + "foo": true + } + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties outside", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties inside", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties outside", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "allOf": [ + { + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties inside", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, true with properties", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": true + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, false with properties", + "schema": { + "type": "object", + "allOf": [ + { + "unevaluatedProperties": true + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "property is evaluated in an uncle schema to unevaluatedProperties", + "comment": "see https://stackoverflow.com/questions/66936884/deeply-nested-unevaluatedproperties-and-their-expectations", + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "properties": { + "faz": { + "type": "string" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "no extra properties", + "data": { + "foo": { + "bar": "test" + } + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": { + "bar": "test", + "faz": "test" + } + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, allOf has unevaluated", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + }, + "unevaluatedProperties": false + } + ], + "anyOf": [ + { + "properties": { + "bar": true + } + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, anyOf has unevaluated", + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + } + } + ], + "anyOf": [ + { + "properties": { + "bar": true + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties + single cyclic ref", + "schema": { + "type": "object", + "properties": { + "x": { + "$ref": "#" + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is valid", + "data": {}, + "valid": true + }, + { + "description": "Single is valid", + "data": { + "x": {} + }, + "valid": true + }, + { + "description": "Unevaluated on 1st level is invalid", + "data": { + "x": {}, + "y": {} + }, + "valid": false + }, + { + "description": "Nested is valid", + "data": { + "x": { + "x": {} + } + }, + "valid": true + }, + { + "description": "Unevaluated on 2nd level is invalid", + "data": { + "x": { + "x": {}, + "y": {} + } + }, + "valid": false + }, + { + "description": "Deep nested is valid", + "data": { + "x": { + "x": { + "x": {} + } + } + }, + "valid": true + }, + { + "description": "Unevaluated on 3rd level is invalid", + "data": { + "x": { + "x": { + "x": {}, + "y": {} + } + } + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties + ref inside allOf / oneOf", + "schema": { + "$defs": { + "one": { + "properties": { + "a": true + } + }, + "two": { + "required": [ + "x" + ], + "properties": { + "x": true + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/one" + }, + { + "properties": { + "b": true + } + }, + { + "oneOf": [ + { + "$ref": "#/$defs/two" + }, + { + "required": [ + "y" + ], + "properties": { + "y": true + } + } + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid (no x or y)", + "data": {}, + "valid": false + }, + { + "description": "a and b are invalid (no x or y)", + "data": { + "a": 1, + "b": 1 + }, + "valid": false + }, + { + "description": "x and y are invalid", + "data": { + "x": 1, + "y": 1 + }, + "valid": false + }, + { + "description": "a and x are valid", + "data": { + "a": 1, + "x": 1 + }, + "valid": true + }, + { + "description": "a and y are valid", + "data": { + "a": 1, + "y": 1 + }, + "valid": true + }, + { + "description": "a and b and x are valid", + "data": { + "a": 1, + "b": 1, + "x": 1 + }, + "valid": true + }, + { + "description": "a and b and y are valid", + "data": { + "a": 1, + "b": 1, + "y": 1 + }, + "valid": true + }, + { + "description": "a and b and x and y are invalid", + "data": { + "a": 1, + "b": 1, + "x": 1, + "y": 1 + }, + "valid": false + } + ] + }, + { + "description": "dynamic evalation inside nested refs", + "schema": { + "$defs": { + "one": { + "oneOf": [ + { + "$ref": "#/$defs/two" + }, + { + "required": [ + "b" + ], + "properties": { + "b": true + } + }, + { + "required": [ + "xx" + ], + "patternProperties": { + "x": true + } + }, + { + "required": [ + "all" + ], + "unevaluatedProperties": true + } + ] + }, + "two": { + "oneOf": [ + { + "required": [ + "c" + ], + "properties": { + "c": true + } + }, + { + "required": [ + "d" + ], + "properties": { + "d": true + } + } + ] + } + }, + "oneOf": [ + { + "$ref": "#/$defs/one" + }, + { + "required": [ + "a" + ], + "properties": { + "a": true + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid", + "data": {}, + "valid": false + }, + { + "description": "a is valid", + "data": { + "a": 1 + }, + "valid": true + }, + { + "description": "b is valid", + "data": { + "b": 1 + }, + "valid": true + }, + { + "description": "c is valid", + "data": { + "c": 1 + }, + "valid": true + }, + { + "description": "d is valid", + "data": { + "d": 1 + }, + "valid": true + }, + { + "description": "a + b is invalid", + "data": { + "a": 1, + "b": 1 + }, + "valid": false + }, + { + "description": "a + c is invalid", + "data": { + "a": 1, + "c": 1 + }, + "valid": false + }, + { + "description": "a + d is invalid", + "data": { + "a": 1, + "d": 1 + }, + "valid": false + }, + { + "description": "b + c is invalid", + "data": { + "b": 1, + "c": 1 + }, + "valid": false + }, + { + "description": "b + d is invalid", + "data": { + "b": 1, + "d": 1 + }, + "valid": false + }, + { + "description": "c + d is invalid", + "data": { + "c": 1, + "d": 1 + }, + "valid": false + }, + { + "description": "xx + a is invalid", + "data": { + "xx": 1, + "a": 1 + }, + "valid": false + }, + { + "description": "xx + b is invalid", + "data": { + "xx": 1, + "b": 1 + }, + "valid": false + }, + { + "description": "xx + c is invalid", + "data": { + "xx": 1, + "c": 1 + }, + "valid": false + }, + { + "description": "xx + d is invalid", + "data": { + "xx": 1, + "d": 1 + }, + "valid": false + }, + { + "description": "all is valid", + "data": { + "all": 1 + }, + "valid": true + }, + { + "description": "all + foo is valid", + "data": { + "all": 1, + "foo": 1 + }, + "valid": true + }, + { + "description": "all + a is invalid", + "data": { + "all": 1, + "a": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with patternProperties and type union", + "schema": { + "type": "object", + "patternProperties": { + "^valid_": { + "type": ["array", "string"], + "items": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Not valid key against pattern", + "data": { + "valid_array": ["array1_value", "array2_value"], + "valid_string": "string_value", + "invalid_key": "this is an unevaluated properties due to key not matching the pattern" + }, + "valid": false + }, + { + "description": "Not valid type", + "data": { + "valid_array": ["array1_value", "array2_value"], + "valid_string": "string_value", + "valid_key": 5 + }, + "valid": false + }, + { + "description": "Valid", + "data": { + "valid_array": ["array1_value", "array2_value"], + "valid_string": "string_value" + }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/schema/walk-schema-default.json b/src/test/resources/schema/walk-schema-default.json index a3209136c..e26cd8340 100644 --- a/src/test/resources/schema/walk-schema-default.json +++ b/src/test/resources/schema/walk-schema-default.json @@ -10,6 +10,15 @@ "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it", "type": "string", "default": "hello" + }, + "stringValue_missing_with_default_null": { + "description": "the test data does not have this attribute, so default as null should be applied if the ApplyDefaultsStrategy requires it", + "type": "string", + "default": null + }, + "stringValue_missing_with_no_default": { + "description": "the test data does not have this attribute, no default value should be applied", + "type": "string" } }, "required": [ diff --git a/src/test/resources/test-messages.properties b/src/test/resources/test-messages.properties new file mode 100644 index 000000000..6e4a5b655 --- /dev/null +++ b/src/test/resources/test-messages.properties @@ -0,0 +1 @@ +atmostOne = english diff --git a/src/test/resources/test-messages_fr.properties b/src/test/resources/test-messages_fr.properties new file mode 100644 index 000000000..2d92388f9 --- /dev/null +++ b/src/test/resources/test-messages_fr.properties @@ -0,0 +1 @@ +atmostOne = french diff --git a/src/test/resources/draft4/extra/uri_mapping/example-schema.json b/src/test/resources/uri_mapping/example-schema.json similarity index 100% rename from src/test/resources/draft4/extra/uri_mapping/example-schema.json rename to src/test/resources/uri_mapping/example-schema.json diff --git a/src/test/resources/uri_mapping/invalid-schema-uri.json b/src/test/resources/uri_mapping/invalid-schema-uri.json new file mode 100644 index 000000000..0990fe6f4 --- /dev/null +++ b/src/test/resources/uri_mapping/invalid-schema-uri.json @@ -0,0 +1,15 @@ +[ + { + "publicURL": "http://json-schema.org/draft-04/schema#", + "localURL": "resource:/draftv4.schema.json" + }, + { + "publicURL": "http://example.com/invalid/schema/url", + "localURL": "resource:/uri_mapping/example-schema.json" + }, + { + "publicURL": "https://example.com/invalid/schema/url", + "localURL": "resource:/uri_mapping/example-schema.json" + } + +] diff --git a/src/test/resources/uri_mapping/schema-with-ref-mapping.json b/src/test/resources/uri_mapping/schema-with-ref-mapping.json new file mode 100644 index 000000000..a366c5dca --- /dev/null +++ b/src/test/resources/uri_mapping/schema-with-ref-mapping.json @@ -0,0 +1,10 @@ +[ + { + "publicURL": "http://json-schema.org/draft-04/schema#", + "localURL": "resource:/draftv4.schema.json" + }, + { + "publicURL": "http://example.com/invalid/schema/url", + "localURL": "resource:/uri_mapping/example-schema.json" + } +] diff --git a/src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json b/src/test/resources/uri_mapping/schema-with-ref.json similarity index 100% rename from src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json rename to src/test/resources/uri_mapping/schema-with-ref.json diff --git a/src/test/resources/draft4/extra/uri_mapping/uri-mapping.json b/src/test/resources/uri_mapping/uri-mapping.json similarity index 79% rename from src/test/resources/draft4/extra/uri_mapping/uri-mapping.json rename to src/test/resources/uri_mapping/uri-mapping.json index d45284256..981263b74 100644 --- a/src/test/resources/draft4/extra/uri_mapping/uri-mapping.json +++ b/src/test/resources/uri_mapping/uri-mapping.json @@ -5,6 +5,6 @@ }, { "publicURL": "https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json", - "localURL": "resource:/draft4/extra/uri_mapping/uri-mapping.schema.json" + "localURL": "resource:/uri_mapping/uri-mapping.schema.json" } ] diff --git a/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json b/src/test/resources/uri_mapping/uri-mapping.schema.json similarity index 100% rename from src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json rename to src/test/resources/uri_mapping/uri-mapping.schema.json diff --git a/src/test/suite/.editorconfig b/src/test/suite/.editorconfig new file mode 100644 index 000000000..6db6a5bfa --- /dev/null +++ b/src/test/suite/.editorconfig @@ -0,0 +1 @@ +insert_final_newline = true diff --git a/src/test/suite/.github/CODEOWNERS b/src/test/suite/.github/CODEOWNERS new file mode 100644 index 000000000..15f4a2dbc --- /dev/null +++ b/src/test/suite/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Ping the entire test suite team by default. +* @json-schema-org/test-suite-team diff --git a/src/test/suite/.github/workflows/ci.yml b/src/test/suite/.github/workflows/ci.yml new file mode 100644 index 000000000..a826069b5 --- /dev/null +++ b/src/test/suite/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: Test Suite Sanity Checking + +on: + push: + pull_request: + release: + types: [published] + schedule: + # Daily at 6:42, arbitrarily as a time that's possibly non-busy + - cron: '42 6 * * *' + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install tox + run: python -m pip install tox + - name: Run the sanity checks + run: python -m tox diff --git a/src/test/suite/.gitignore b/src/test/suite/.gitignore new file mode 100644 index 000000000..68bc17f9f --- /dev/null +++ b/src/test/suite/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/src/test/suite/CONTRIBUTING.md b/src/test/suite/CONTRIBUTING.md new file mode 100644 index 000000000..8dab09e61 --- /dev/null +++ b/src/test/suite/CONTRIBUTING.md @@ -0,0 +1,87 @@ +# Contributing to the Suite + +All contributors to the test suite should familiarize themselves with this document. + +Both pull requests *and* reviews are welcome from any and all, regardless of their "formal" relationship (or lack thereof) with the JSON Schema organization. + +## Commit Access + +Any existing members with commit access to the repository may nominate new members to get access, or a contributor may request access for themselves. +Access generally should be granted liberally to anyone who has shown positive contributions to the repository or organization. +All who are active in other parts of the JSON Schema organization should get access to this repository as well. +Access for a former contributor may be removed after long periods of inactivity. + +## Reviewing a Pull Request + +Pull requests may (and often should) be reviewed for approval by a single reviewer whose job it is to confirm the change is specified, correct, minimal and follows general style in the repository. +A reviewer who does not feel comfortable signing off on the correctness of a change is free to comment without explicit approval. +Other contributors are also encouraged to comment on pull requests whenever they have feedback, even if another contributor has submitted review comments. +A submitter may also choose to request additional feedback if they feel the change is particularly technical or complex, or requires expertise in a particular area of the specification. +If additional reviewers have participated in a pull request, the submitter should not rely on a single reviewer's approval without some form of confirmation that all participating reviewers are satisfied. +On the other hand, whenever possible, reviewers who have minor comments should explicitly mention that they are OK with the PR being merged after or potentially *without* addressing them. + +When submitting a change, explicitly soliciting a specific reviewer explicitly is currently not needed, as the entire review team is generally pinged for pull requests. +Nevertheless, submitters may choose to do so if they want specific review from an individual, or if a pull request is sitting without review for a period of time. +For the latter scenario, leaving a comment on the pull request or in Slack is also reasonable. + +Confirming that a pull request runs successfully on an implementation is *not* generally sufficient to merge, though it is helpful evidence, and highly encouraged. +Proposed changes should be confirmed by reading the specification and ensuring the behavior is specified and correct. +Submitters are encouraged to link to the specification whenever doing so will be helpful to a reviewer. + +A reviewer may indicate that the proposed changes are too large for them to review. +In such cases the submitter may wait for another reviewer who is comfortable reviewing the changes, but is generally strongly encouraged to split up the changes into multiple smaller ones. + +Reviewing pull requests is an extremely valuable contribution! +New reviewers are highly encouraged to attempt to review pull requests even if they do not have experience doing so, and to themselves expect feedback from the submitter or from other reviewers on improving the quality of their reviews. +In such cases the submitter should use their judgement to decide whether the new contributor's review is sufficient for merging, or whether they should wait for further feedback. + +## Merging Changes + +Approval of a change may be given using the GitHub UI or via a comment reply. +Once it has been given, the pull request may be merged at any point. + +To merge a pull request, *either* the submitter or reviewer must have commit access to the repo (though this is also partially simply because that party's access is needed to merge). + +*Either* the submitter or reviewer may be the one to do the actual merge (whether via hitting the merge button or externally to the GitHub UI). +If the submitter wishes to make final changes after a review they should attempt to say so (and thereby take responsibility for merging themselves). +Contributors *should not* leave pull requests stagnant whenever possible, and particularly after they have been reviewed and approved. + +Changes should not be merged while continuous integration is failing. +Failures typically are not spurious and indicate issues with the changes. +In the event the change is indeed correct and CI is flaky or itself incorrect, effort should be made by the submitter, reviewer, or a solicited other contributor to fix the CI before the change is made. +Improvements to CI itself are very valuable as well, and reviewers who find repeated issues with proposed changes are highly encouraged to improve CI for any changes which may be automatically detected. + +Changes should be merged *as-is* and not squashed into single commits. +Submitters are free to structure their commits as they wish throughout the review process, or in some cases to restructure the commits after a review is finished and they are merging the branch, but are not required to do so, and reviewers should not do so on behalf of the submitter without being requested to do so. + +Contributors with commit access may choose to merge pull requests (or commit directly) to the repository for trivial changes. +The definition of "trivial" is intentionally slightly ambiguous, and intended to be followed by good-faith contributors. +An example of a trivial change is fixing a typo in the README, or bumping a version of a dependency used by the continuous integration suite. +If another contributor takes issue with a change merged in this fashion, simply commenting politely that they have concerns about the change (either in an issue or directly) is the right remedy. + +## Writing Good Tests + +Be familiar with the test structure and assumptions documented in the [README](README.md). + +Test cases should include both valid and invalid instances which exercise the test case schema whenever possible. +Exceptions include schemas where only one result is ever possible (such as the `false` schema, or ones using keywords which only produce annotations). + +Schemas should be *minimal*, by which we mean that they should contain only those keywords which are being tested by the specific test case, and should not contain complex values when simpler ones would do. +The same applies to instances -- prefer simpler instances to more complex ones, and when testing string instances, consider using ones which are self-descriptive whenever it aids readability. + +Comments can and should be used to explain tests which are unclear or complex. +The `comment` field is present both for test cases and individual tests for this purpose. +Links to the relevant specification sections are also encouraged, though they can be tedious to maintain from one version to the next. + +When adding test cases, they should be added to all past (and future) versions of the specification which they apply to, potentially with minor modifications (e.g. changing `$id` to `id` or accounting for `$ref` not allowing siblings on older drafts). + +Changing the schema used in a particular test case should be done with extra caution, though it is not formally discouraged if the change simplifies the schema. +Contributors should not generally append *additional* behavior to existing test case schemas, unless doing so has specific justification. +Instead, new cases should be added, as it can often be subtle to predict which precise parts of a test case are unique. +Adding additional *tests* however (instances) is of course safe and encouraged if gaps are found. + +Tests which are *incorrect* (against the specification) should be prioritized for fixing or removal whenever possible, as their continued presence in the suite can create confusion for downstream users of the suite. + +## Proposing Changes to the Policy + +This policy itself is of course changeable, and changes to it may be proposed in a discussion. diff --git a/src/test/suite/LICENSE b/src/test/suite/LICENSE new file mode 100644 index 000000000..c28adbadd --- /dev/null +++ b/src/test/suite/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/test/suite/README.md b/src/test/suite/README.md new file mode 100644 index 000000000..36dda7422 --- /dev/null +++ b/src/test/suite/README.md @@ -0,0 +1,346 @@ +# JSON Schema Test Suite + +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/json-schema-org/.github/blob/main/CODE_OF_CONDUCT.md) +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Financial Contributors on Open Collective](https://opencollective.com/json-schema/all/badge.svg?label=financial+contributors)](https://opencollective.com/json-schema) + +[![DOI](https://zenodo.org/badge/5952934.svg)](https://zenodo.org/badge/latestdoi/5952934) +[![Build Status](https://github.com/json-schema-org/JSON-Schema-Test-Suite/workflows/Test%20Suite%20Sanity%20Checking/badge.svg)](https://github.com/json-schema-org/JSON-Schema-Test-Suite/actions?query=workflow%3A%22Test+Suite+Sanity+Checking%22) + +This repository contains a set of JSON objects that implementers of JSON Schema validation libraries can use to test their validators. + +It is meant to be language agnostic and should require only a JSON parser. +The conversion of the JSON objects into tests within a specific language and test framework of choice is left to be done by the validator implementer. + +## Coverage + +All JSON Schema specification releases should be well covered by this suite, including drafts 2020-12, 2019-09, 07, 06, 04 and 03. +Drafts 04 and 03 are considered "frozen" in that less effort is put in to backport new tests to these versions. + +Additional coverage is always welcome, particularly for bugs encountered in real-world implementations. +If you see anything missing or incorrect, please feel free to [file an issue](https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues) or [submit a PR](https://github.com/json-schema-org/JSON-Schema-Test-Suite). + +@gregsdennis has also started a separate [test suite](https://github.com/gregsdennis/json-schema-vocab-test-suites) that is modelled after this suite to cover third-party vocabularies. + +## Introduction to the Test Suite Structure + +The tests in this suite are contained in the `tests` directory at the root of this repository. +Inside that directory is a subdirectory for each released version of the specification. + +The structure and contents of each file in these directories is described below. + +In addition to the version-specific subdirectories, two additional directories are present: + +1. `draft-next/`: containing tests for the next version of the specification whilst it is in development +2. `latest/`: a symbolic link which points to the directory which is the most recent release (which may be useful for implementations providing specific entry points for validating against the latest version of the specification) + +Inside each version directory there are a number of `.json` files each containing a collection of related tests. +Often the grouping is by property under test, but not always. +In addition to the `.json` files, each version directory contains one or more special subdirectories whose purpose is [described below](#subdirectories-within-each-draft), and which contain additional `.json` files. + +Each `.json` file consists of a single JSON array of test cases. + +### Terminology + +For clarity, we first define this document's usage of some testing terminology: + +| term | definition | +|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **test suite** | the entirety of the contents of this repository, containing tests for multiple different releases of the JSON Schema specification | +| **test case** | a single schema, along with a description and an array of *test*s | +| **test** | within a *test case*, a single test example, containing a description, instance and a boolean indicating whether the instance is valid under the test case schema | +| **test runner** | a program, external to this repository and authored by a user of this suite, which is executing each of the tests in the suite | + +An example illustrating this structure is immediately below, and a JSON Schema containing a formal definition of the contents of test cases can be found [alongside this README](./test-schema.json). + +### Sample Test Case + +Here is a single *test case*, containing one or more tests: + +```json +{ + "description": "The test case description", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a test with a valid instance", + "data": "a string", + "valid": true + }, + { + "description": "a test with an invalid instance", + "data": 15, + "valid": false + } + ] +} +``` + +### Subdirectories Within Each Draft + +There is currently only one additional subdirectory that may exist within each draft test directory. + +This is: + +1. `optional/`: Contains tests that are considered optional. + +Note, the `optional/` subdirectory today conflates many reasons why a test may be optional -- it may be because tests within a particular file are indeed not required by the specification but still potentially useful to an implementer, or it may be because tests within it only apply to programming languages with particular functionality (in +which case they are not truly optional in such a language). +In the future this directory structure will be made richer to reflect these differences more clearly. + +## Using the Suite to Test a Validator Implementation + +The test suite structure was described [above](#introduction-to-the-test-suite-structure). + +If you are authoring a new validator implementation, or adding support for an additional version of the specification, this section describes: + +1. How to implement a test runner which passes tests to your validator +2. Assumptions the suite makes about how the test runner will configure your validator +3. Invariants the test suite claims to hold for its tests + +### How to Implement a Test Runner + +Presented here is a possible implementation of a test runner. +The precise steps described do not need to be followed exactly, but the results of your own procedure should produce the same effects. + +To test a specific version: + +* For 2019-09 and later published drafts, implementations that are able to detect the draft of each schema via `$schema` SHOULD be configured to do so +* For draft-07 and earlier, draft-next, and implementations unable to detect via `$schema`, implementations MUST be configured to expect the draft matching the test directory name +* Load any remote references [described below](additional-assumptions) and configure your implementation to retrieve them via their URIs +* Walk the filesystem tree for that version's subdirectory and for each `.json` file found: + + * if the file is located in the root of the version directory: + + * for each test case present in the file: + + * load the schema from the `"schema"` property + * load (or log) the test case description from the `"description"` property for debugging or outputting + * for each test in the `"tests"` property: + + * load the instance to be tested from the `"data"` property + * load (or log) the individual test description from the `"description"` property for debugging or outputting + + * use the schema loaded above to validate whether the instance is considered valid under your implementation + + * if the result from your implementation matches the value found in the `"valid"` property, your implementation correctly implements the specific example + * if the result does not match, or your implementation errors or crashes, your implementation does not correctly implement the specific example + + * otherwise it is located in a special subdirectory as described above. + Follow the additional assumptions and restrictions for the containing subdirectory, then run the test case as above. + +If your implementation supports multiple versions, run the above procedure for each version supported, configuring your implementation as appropriate to call each version individually. + +### Additional Assumptions + +1. The suite, notably in its `refRemote.json` file in each draft, expects a number of remote references to be configured. + These are JSON documents, identified by URI, which are used by the suite to test the behavior of the `$ref` keyword (and related keywords). + Depending on your implementation, you may configure how to "register" these *either*: + + * by directly retrieving them off the filesystem from the `remotes/` directory, in which case you should load each schema with a retrieval URI of `http://localhost:1234` followed by the relative path from the remotes directory -- e.g. a `$ref` to `http://localhost:1234/foo/bar/baz.json` is expected to resolve to the contents of the file at `remotes/foo/bar/baz.json` + + * or alternatively, by executing `bin/jsonschema_suite remotes` using the executable in the `bin/` directory, which will output a JSON object containing all of the remotes combined, e.g.: + + ``` + + $ bin/jsonschema_suite remotes + ``` + ```json + { + "http://localhost:1234/baseUriChange/folderInteger.json": { + "type": "integer" + }, + "http://localhost:1234/baseUriChangeFolder/folderInteger.json": { + "type": "integer" + } + } + ``` + +2. Test cases found within [special subdirectories](#subdirectories-within-each-draft) may require additional configuration to run. + In particular, tests within the `optional/format` subdirectory may require implementations to change the way they treat the `"format"`keyword (particularly on older drafts which did not have a notion of vocabularies). + +### Invariants & Guarantees + +The test suite guarantees a number of things about tests it defines. +Any deviation from the below is generally considered a bug. +If you suspect one, please [file an issue](https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/new): + +1. All files containing test cases are valid JSON. +2. The contents of the `"schema"` property in a test case are always valid + JSON Schemas under the corresponding specification. + + The rationale behind this is that we are testing instances in a test's `"data"` element, and not the schema itself. + A number of tests *do* test the validity of a schema itself, but do so by representing the schema as an instance inside a test, with the associated meta-schema in the `"schema"` property (via the `"$ref"` keyword): + + ```json + { + "description": "Test the \"type\" schema keyword", + "schema": { + "$ref": "https://json-schema.org/draft/2019-09/schema" + }, + "tests": [ + { + "description": "Valid: string", + "data": { + "type": "string" + }, + "valid": true + }, + { + "description": "Invalid: null", + "data": { + "type": null + }, + "valid": false + } + ] + } + ``` + See below for some [known limitations](#known-limitations). + +## Known Limitations + +This suite expresses its assertions about the behavior of an implementation *within* JSON Schema itself. +Each test is the application of a schema to a particular instance. +This means that the suite of tests can test against any behavior a schema can describe, and conversely cannot test against any behavior which a schema is incapable of representing, even if the behavior is mandated by the specification. + +For example, a schema can require that a string is a _URI-reference_ and even that it matches a certain pattern, but even though the specification contains [recommendations about URIs being normalized](https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-id-keyword), a JSON schema cannot today represent this assertion within the core vocabularies of the specifications, so no test covers this behavior. + +## Who Uses the Test Suite + +This suite is being used by: + +### Clojure + +* [jinx](https://github.com/juxt/jinx) +* [json-schema](https://github.com/tatut/json-schema) + +### Coffeescript + +* [jsck](https://github.com/pandastrike/jsck) + +### Common Lisp + +* [json-schema](https://github.com/fisxoj/json-schema) + +### C++ + +* [Modern C++ JSON schema validator](https://github.com/pboettch/json-schema-validator) +* [Valijson](https://github.com/tristanpenman/valijson) + +### Dart + +* [json\_schema](https://github.com/patefacio/json_schema) + +### Elixir + +* [ex\_json\_schema](https://github.com/jonasschmidt/ex_json_schema) + +### Erlang + +* [jesse](https://github.com/for-GET/jesse) + +### Go + +* [gojsonschema](https://github.com/sigu-399/gojsonschema) +* [validate-json](https://github.com/cesanta/validate-json) + +### Haskell + +* [aeson-schema](https://github.com/timjb/aeson-schema) +* [hjsonschema](https://github.com/seagreen/hjsonschema) + +### Java + +* [json-schema-validator](https://github.com/daveclayton/json-schema-validator) +* [everit-org/json-schema](https://github.com/everit-org/json-schema) +* [networknt/json-schema-validator](https://github.com/networknt/json-schema-validator) +* [Justify](https://github.com/leadpony/justify) +* [Snow](https://github.com/ssilverman/snowy-json) +* [jsonschemafriend](https://github.com/jimblackler/jsonschemafriend) + +### JavaScript + +* [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark) +* [direct-schema](https://github.com/IreneKnapp/direct-schema) +* [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) +* [jassi](https://github.com/iclanzan/jassi) +* [JaySchema](https://github.com/natesilva/jayschema) +* [json-schema-valid](https://github.com/ericgj/json-schema-valid) +* [Jsonary](https://github.com/jsonary-js/jsonary) +* [jsonschema](https://github.com/tdegrunt/jsonschema) +* [request-validator](https://github.com/bugventure/request-validator) +* [skeemas](https://github.com/Prestaul/skeemas) +* [tv4](https://github.com/geraintluff/tv4) +* [z-schema](https://github.com/zaggino/z-schema) +* [jsen](https://github.com/bugventure/jsen) +* [ajv](https://github.com/epoberezkin/ajv) +* [djv](https://github.com/korzio/djv) + +### Node.js + +For node.js developers, the suite is also available as an [npm](https://www.npmjs.com/package/@json-schema-org/tests) package. + +Node-specific support is maintained in a [separate repository](https://github.com/json-schema-org/json-schema-test-suite-npm) which also welcomes your contributions! + +### .NET + +* [JsonSchema.Net](https://github.com/gregsdennis/json-everything) +* [Newtonsoft.Json.Schema](https://github.com/JamesNK/Newtonsoft.Json.Schema) + +### Perl + +* [Test::JSON::Schema::Acceptance](https://github.com/karenetheridge/Test-JSON-Schema-Acceptance) (a wrapper of this test suite) +* [JSON::Schema::Modern](https://github.com/karenetheridge/JSON-Schema-Modern) +* [JSON::Schema::Tiny](https://github.com/karenetheridge/JSON-Schema-Tiny) + +### PHP + +* [opis/json-schema](https://github.com/opis/json-schema) +* [json-schema](https://github.com/justinrainbow/json-schema) +* [json-guard](https://github.com/thephpleague/json-guard) + +### PostgreSQL + +* [postgres-json-schema](https://github.com/gavinwahl/postgres-json-schema) +* [is\_jsonb\_valid](https://github.com/furstenheim/is_jsonb_valid) + +### Python + +* [jsonschema](https://github.com/Julian/jsonschema) +* [fastjsonschema](https://github.com/seznam/python-fastjsonschema) +* [hypothesis-jsonschema](https://github.com/Zac-HD/hypothesis-jsonschema) +* [jschon](https://github.com/marksparkza/jschon) +* [python-experimental, OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/python-experimental.md) + +### Ruby + +* [json-schema](https://github.com/hoxworth/json-schema) +* [json\_schemer](https://github.com/davishmcclurg/json_schemer) + +### Rust + +* [jsonschema](https://github.com/Stranger6667/jsonschema-rs) +* [valico](https://github.com/rustless/valico) + +### Scala + +* [typed-json](https://github.com/frawa/typed-json) + +### Swift + +* [JSONSchema](https://github.com/kylef/JSONSchema.swift) + +If you use it as well, please fork and send a pull request adding yourself to +the list :). + +## Contributing + +If you see something missing or incorrect, a pull request is most welcome! + +There are some sanity checks in place for testing the test suite. You can run +them with `bin/jsonschema_suite check` or `tox`. They will be run automatically +by [GitHub Actions](https://github.com/json-schema-org/JSON-Schema-Test-Suite/actions?query=workflow%3A%22Test+Suite+Sanity+Checking%22) +as well. + +This repository is maintained by the JSON Schema organization, and will be governed by the JSON Schema steering committee (once it exists). diff --git a/src/test/suite/annotations/README.md b/src/test/suite/annotations/README.md new file mode 100644 index 000000000..69cd3dd7e --- /dev/null +++ b/src/test/suite/annotations/README.md @@ -0,0 +1,116 @@ +# Annotations Tests Suite + +The Annotations Test Suite tests which annotations should appear (or not appear) +on which values of an instance. These tests are agnostic of any output format. + +## Supported Dialects + +Although the annotation terminology of didn't appear in the spec until 2019-09, +the concept is compatible with every version of JSON Schema. Test Cases in this +Test Suite are designed to be compatible with as many releases of JSON Schema as +possible. They do not include `$schema` or `$id`/`id` keywords so +implementations can run the same Test Suite for each dialect they support. + +Since this Test Suite can be used for a variety of dialects, there are a couple +of options that can be used by Test Runners to filter out Test Cases that don't +apply to the dialect under test. + +## Test Case Components + +### description + +A short description of what behavior the Test Case is covering. + +### compatibility + +The `compatibility` option allows you to set which dialects the Test Case is +compatible with. Test Runners can use this value to filter out Test Cases that +don't apply the to dialect currently under test. The terminology for annotations +didn't appear in the spec until 2019-09, but the concept is compatible with +older releases as well. When setting `compatibility`, test authors should take +into account dialects before 2019-09 for implementations that chose to support +annotations for older dialects. + +Dialects are indicated by the number corresponding to their release. Date-based +releases use just the year. If this option isn't present, it means the Test Case +is compatible with any dialect. + +If this option is present with a number, the number indicates the minimum +release the Test Case is compatible with. This example indicates that the Test +Case is compatible with draft-07 and up. + +**Example**: `"compatibility": "7"` + +You can use a `<=` operator to indicate that the Test Case is compatible with +releases less then or equal to the given release. This example indicates that +the Test Case is compatible with 2019-09 and under. + +**Example**: `"compatibility": "<=2019"` + +You can use comma-separated values to indicate multiple constraints if needed. +This example indicates that the Test Case is compatible with releases between +draft-06 and 2019-09. + +**Example**: `"compatibility": "6,<=2019"` + +For convenience, you can use the `=` operator to indicate a Test Case is only +compatible with a single release. This example indicates that the Test Case is +compatible only with 2020-12. + +**Example**: `"compatibility": "=2020"` + +### schema + +The schema that will serve as the subject for the tests. Whenever possible, this +schema shouldn't include `$schema` or `id`/`$id` because Test Cases should be +designed to work with as many releases as possible. + +### externalSchemas + +This allows you to define additional schemas that `schema` makes references to. +The value is an object where the keys are retrieval URIs and values are schemas. +Most external schemas aren't self identifying (using `id`/`$id`) and rely on the +retrieval URI for identification. This is done to increase the number of +dialects that the test is compatible with. Because `id` changed to `$id` in +draft-06, if you use `$id`, the test becomes incompatible with draft-03/4 and in +most cases, that's not necessary. + +### tests + +A collection of Tests to run to verify the Test Case. + +## Test Components + +### instance + +The JSON instance to be annotated. + +### assertions + +A collection of assertions that must be true for the test to pass. + +## Assertions Components + +### location + +The instance location. + +### keyword + +The annotating keyword. + +### expected + +A collection of `keyword` annotations expected on the instance at `location`. +`expected` is an object where the keys are schema locations and the values are +the annotation that schema location contributed for the given `keyword`. + +There can be more than one expected annotation because multiple schema locations +could contribute annotations for a single keyword. + +An empty object is an assertion that the annotation must not appear at the +`location` for the `keyword`. + +As a convention for this Test Suite, the `expected` array should be sorted such +that the most recently encountered value for an annotation given top-down +evaluation of the schema comes before previously encountered values. diff --git a/src/test/suite/annotations/assertion.schema.json b/src/test/suite/annotations/assertion.schema.json new file mode 100644 index 000000000..882517887 --- /dev/null +++ b/src/test/suite/annotations/assertion.schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "location": { + "markdownDescription": "The instance location.", + "type": "string", + "format": "json-pointer" + }, + "keyword": { + "markdownDescription": "The annotation keyword.", + "type": "string" + }, + "expected": { + "markdownDescription": "An object of schemaLocation/annotations pairs for `keyword` annotations expected on the instance at `location`.", + "type": "object", + "propertyNames": { + "format": "uri" + } + } + }, + "required": ["location", "keyword", "expected"] +} diff --git a/src/test/suite/annotations/test-case.schema.json b/src/test/suite/annotations/test-case.schema.json new file mode 100644 index 000000000..6df5f1098 --- /dev/null +++ b/src/test/suite/annotations/test-case.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "description": { + "markdownDescription": "A short description of what behavior the Test Case is covering.", + "type": "string" + }, + "compatibility": { + "markdownDescription": "Set which dialects the Test Case is compatible with. Examples:\n- `\"7\"` -- draft-07 and above\n- `\"<=2019\"` -- 2019-09 and previous\n- `\"6,<=2019\"` -- Between draft-06 and 2019-09\n- `\"=2020\"` -- 2020-12 only", + "type": "string", + "pattern": "^(<=|=)?([123467]|2019|2020)(,(<=|=)?([123467]|2019|2020))*$" + }, + "schema": { + "markdownDescription": "This schema shouldn't include `$schema` or `id`/`$id` unless necesary for the test because Test Cases should be designed to work with as many releases as possible.", + "type": ["boolean", "object"] + }, + "externalSchemas": { + "markdownDescription": "The keys are retrieval URIs and values are schemas.", + "type": "object", + "patternProperties": { + "": { + "type": ["boolean", "object"] + } + }, + "propertyNames": { + "format": "uri" + } + }, + "tests": { + "markdownDescription": "A collection of Tests to run to verify the Test Case.", + "type": "array", + "items": { "$ref": "./test.schema.json" } + } + }, + "required": ["description", "schema", "tests"] +} diff --git a/src/test/suite/annotations/test-suite.schema.json b/src/test/suite/annotations/test-suite.schema.json new file mode 100644 index 000000000..c8b17f0d5 --- /dev/null +++ b/src/test/suite/annotations/test-suite.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "suite": { + "type": "array", + "items": { "$ref": "./test-case.schema.json" } + } + }, + "required": ["description", "suite"] +} diff --git a/src/test/suite/annotations/test.schema.json b/src/test/suite/annotations/test.schema.json new file mode 100644 index 000000000..3581fbfca --- /dev/null +++ b/src/test/suite/annotations/test.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "instance": { + "markdownDescription": "The JSON instance to be annotated." + }, + "assertions": { + "markdownDescription": "A collection of assertions that must be true for the test to pass.", + "type": "array", + "items": { "$ref": "./assertion.schema.json" } + } + }, + "required": ["instance", "assertions"] +} diff --git a/src/test/suite/annotations/tests/applicators.json b/src/test/suite/annotations/tests/applicators.json new file mode 100644 index 000000000..ceb5044f3 --- /dev/null +++ b/src/test/suite/annotations/tests/applicators.json @@ -0,0 +1,409 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The applicator vocabulary", + "suite": [ + { + "description": "`properties`, `patternProperties`, and `additionalProperties`", + "compatibility": "3", + "schema": { + "properties": { + "foo": { + "title": "Foo" + } + }, + "patternProperties": { + "^a": { + "title": "Bar" + } + }, + "additionalProperties": { + "title": "Baz" + } + }, + "tests": [ + { + "instance": {}, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": {} + }, + { + "location": "/apple", + "keyword": "title", + "expected": {} + }, + { + "location": "/bar", + "keyword": "title", + "expected": {} + } + ] + }, + { + "instance": { + "foo": {}, + "apple": {}, + "baz": {} + }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/properties/foo": "Foo" + } + }, + { + "location": "/apple", + "keyword": "title", + "expected": { + "#/patternProperties/%5Ea": "Bar" + } + }, + { + "location": "/baz", + "keyword": "title", + "expected": { + "#/additionalProperties": "Baz" + } + } + ] + } + ] + }, + { + "description": "`propertyNames` doesn't annotate property values", + "compatibility": "6", + "schema": { + "propertyNames": { + "const": "foo", + "title": "Foo" + } + }, + "tests": [ + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": {} + } + ] + } + ] + }, + { + "description": "`prefixItems` and `items`", + "compatibility": "2020", + "schema": { + "prefixItems": [ + { + "title": "Foo" + } + ], + "items": { + "title": "Bar" + } + }, + "tests": [ + { + "instance": [ + "foo", + "bar" + ], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/prefixItems/0": "Foo" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/items": "Bar" + } + }, + { + "location": "/2", + "keyword": "title", + "expected": {} + } + ] + } + ] + }, + { + "description": "`contains`", + "compatibility": "6", + "schema": { + "contains": { + "type": "number", + "title": "Foo" + } + }, + "tests": [ + { + "instance": [ + "foo", + 42, + true + ], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": {} + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/contains": "Foo" + } + }, + { + "location": "/2", + "keyword": "title", + "expected": {} + }, + { + "location": "/3", + "keyword": "title", + "expected": {} + } + ] + } + ] + }, + { + "description": "`allOf`", + "compatibility": "4", + "schema": { + "allOf": [ + { + "title": "Foo" + }, + { + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/allOf/1": "Bar", + "#/allOf/0": "Foo" + } + } + ] + } + ] + }, + { + "description": "`anyOf`", + "compatibility": "4", + "schema": { + "anyOf": [ + { + "type": "integer", + "title": "Foo" + }, + { + "type": "number", + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/anyOf/1": "Bar", + "#/anyOf/0": "Foo" + } + } + ] + }, + { + "instance": 4.2, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/anyOf/1": "Bar" + } + } + ] + } + ] + }, + { + "description": "`oneOf`", + "compatibility": "4", + "schema": { + "oneOf": [ + { + "type": "string", + "title": "Foo" + }, + { + "type": "number", + "title": "Bar" + } + ] + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/oneOf/0": "Foo" + } + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/oneOf/1": "Bar" + } + } + ] + } + ] + }, + { + "description": "`not`", + "compatibility": "4", + "schema": { + "title": "Foo", + "not": { + "not": { + "title": "Bar" + } + } + }, + "tests": [ + { + "instance": {}, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#": "Foo" + } + } + ] + } + ] + }, + { + "description": "`dependentSchemas`", + "compatibility": "2019", + "schema": { + "dependentSchemas": { + "foo": { + "title": "Foo" + } + } + }, + "tests": [ + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/dependentSchemas/foo": "Foo" + } + } + ] + }, + { + "instance": { + "foo": 42 + }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": {} + } + ] + } + ] + }, + { + "description": "`if`, `then`, and `else`", + "compatibility": "7", + "schema": { + "if": { + "title": "If", + "type": "string" + }, + "then": { + "title": "Then" + }, + "else": { + "title": "Else" + } + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/then": "Then", + "#/if": "If" + } + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/else": "Else" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/content.json b/src/test/suite/annotations/tests/content.json new file mode 100644 index 000000000..ad5006038 --- /dev/null +++ b/src/test/suite/annotations/tests/content.json @@ -0,0 +1,119 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The content vocabulary", + "suite": [ + { + "description": "`contentMediaType` is an annotation for string instances", + "compatibility": "7", + "schema": { + "contentMediaType": "application/json" + }, + "tests": [ + { + "instance": "{ \"foo\": \"bar\" }", + "assertions": [ + { + "location": "", + "keyword": "contentMediaType", + "expected": { + "#": "application/json" + } + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "contentMediaType", + "expected": {} + } + ] + } + ] + }, + { + "description": "`contentEncoding` is an annotation for string instances", + "compatibility": "7", + "schema": { + "contentEncoding": "base64" + }, + "tests": [ + { + "instance": "SGVsbG8gZnJvbSBKU09OIFNjaGVtYQ==", + "assertions": [ + { + "location": "", + "keyword": "contentEncoding", + "expected": { + "#": "base64" + } + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "contentEncoding", + "expected": {} + } + ] + } + ] + }, + { + "description": "`contentSchema` is an annotation for string instances", + "compatibility": "2019", + "schema": { + "contentMediaType": "application/json", + "contentSchema": { "type": "number" } + }, + "tests": [ + { + "instance": "42", + "assertions": [ + { + "location": "", + "keyword": "contentSchema", + "expected": { + "#": { "type": "number" } + } + } + ] + }, + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "contentSchema", + "expected": {} + } + ] + } + ] + }, + { + "description": "`contentSchema` requires `contentMediaType`", + "compatibility": "2019", + "schema": { + "contentSchema": { "type": "number" } + }, + "tests": [ + { + "instance": "42", + "assertions": [ + { + "location": "", + "keyword": "contentSchema", + "expected": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/core.json b/src/test/suite/annotations/tests/core.json new file mode 100644 index 000000000..1d8dee556 --- /dev/null +++ b/src/test/suite/annotations/tests/core.json @@ -0,0 +1,30 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The core vocabulary", + "suite": [ + { + "description": "`$ref` and `$defs`", + "compatibility": "2019", + "schema": { + "$ref": "#/$defs/foo", + "$defs": { + "foo": { "title": "Foo" } + } + }, + "tests": [ + { + "instance": "foo", + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#/$defs/foo": "Foo" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/format.json b/src/test/suite/annotations/tests/format.json new file mode 100644 index 000000000..d8cf9a7af --- /dev/null +++ b/src/test/suite/annotations/tests/format.json @@ -0,0 +1,26 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The format vocabulary", + "suite": [ + { + "description": "`format` is an annotation", + "schema": { + "format": "email" + }, + "tests": [ + { + "instance": "foo@bar.com", + "assertions": [ + { + "location": "", + "keyword": "format", + "expected": { + "#": "email" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/meta-data.json b/src/test/suite/annotations/tests/meta-data.json new file mode 100644 index 000000000..be99b652f --- /dev/null +++ b/src/test/suite/annotations/tests/meta-data.json @@ -0,0 +1,150 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The meta-data vocabulary", + "suite": [ + { + "description": "`title` is an annotation", + "schema": { + "title": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "title", + "expected": { + "#": "Foo" + } + } + ] + } + ] + }, + { + "description": "`description` is an annotation", + "schema": { + "description": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "description", + "expected": { + "#": "Foo" + } + } + ] + } + ] + }, + { + "description": "`default` is an annotation", + "schema": { + "default": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "default", + "expected": { + "#": "Foo" + } + } + ] + } + ] + }, + { + "description": "`deprecated` is an annotation", + "compatibility": "2019", + "schema": { + "deprecated": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "deprecated", + "expected": { + "#": true + } + } + ] + } + ] + }, + { + "description": "`readOnly` is an annotation", + "compatibility": "7", + "schema": { + "readOnly": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "readOnly", + "expected": { + "#": true + } + } + ] + } + ] + }, + { + "description": "`writeOnly` is an annotation", + "compatibility": "7", + "schema": { + "writeOnly": true + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "writeOnly", + "expected": { + "#": true + } + } + ] + } + ] + }, + { + "description": "`examples` is an annotation", + "compatibility": "6", + "schema": { + "examples": ["Foo", "Bar"] + }, + "tests": [ + { + "instance": "Foo", + "assertions": [ + { + "location": "", + "keyword": "examples", + "expected": { + "#": ["Foo", "Bar"] + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/unevaluated.json b/src/test/suite/annotations/tests/unevaluated.json new file mode 100644 index 000000000..9f2db1158 --- /dev/null +++ b/src/test/suite/annotations/tests/unevaluated.json @@ -0,0 +1,661 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "The unevaluated vocabulary", + "suite": [ + { + "description": "`unevaluatedProperties` alone", + "compatibility": "2019", + "schema": { + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `properties`", + "compatibility": "2019", + "schema": { + "properties": { + "foo": { "title": "Evaluated" } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/properties/foo": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `patternProperties`", + "compatibility": "2019", + "schema": { + "patternProperties": { + "^a": { "title": "Evaluated" } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "apple": 42, "bar": 24 }, + "assertions": [ + { + "location": "/apple", + "keyword": "title", + "expected": { + "#/patternProperties/%5Ea": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `additionalProperties`", + "compatibility": "2019", + "schema": { + "additionalProperties": { "title": "Evaluated" }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/additionalProperties": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/additionalProperties": "Evaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `dependentSchemas`", + "compatibility": "2019", + "schema": { + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "title": "Evaluated" } + } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/dependentSchemas/foo/properties/bar": "Evaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `if`, `then`, and `else`", + "compatibility": "2019", + "schema": { + "if": { + "properties": { + "foo": { + "type": "string", + "title": "If" + } + } + }, + "then": { + "properties": { + "foo": { "title": "Then" } + } + }, + "else": { + "properties": { + "foo": { "title": "Else" } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": "", "bar": 42 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/then/properties/foo": "Then", + "#/if/properties/foo": "If" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + }, + { + "instance": { "foo": 42, "bar": "" }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/else/properties/foo": "Else" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `allOf`", + "compatibility": "2019", + "schema": { + "allOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/allOf/0/properties/foo": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `anyOf`", + "compatibility": "2019", + "schema": { + "anyOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/anyOf/0/properties/foo": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `oneOf`", + "compatibility": "2019", + "schema": { + "oneOf": [ + { + "properties": { + "foo": { "title": "Evaluated" } + } + } + ], + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/oneOf/0/properties/foo": "Evaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedProperties` with `not`", + "compatibility": "2019", + "schema": { + "not": { + "not": { + "properties": { + "foo": { "title": "Evaluated" } + } + } + }, + "unevaluatedProperties": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": { "foo": 42, "bar": 24 }, + "assertions": [ + { + "location": "/foo", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + }, + { + "location": "/bar", + "keyword": "title", + "expected": { + "#/unevaluatedProperties": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` alone", + "compatibility": "2019", + "schema": { + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `prefixItems`", + "compatibility": "2020", + "schema": { + "prefixItems": [{ "title": "Evaluated" }], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/prefixItems/0": "Evaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `contains`", + "compatibility": "2020", + "schema": { + "contains": { + "type": "string", + "title": "Evaluated" + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": ["foo", 42], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/contains": "Evaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `if`, `then`, and `else`", + "compatibility": "2020", + "schema": { + "if": { + "prefixItems": [ + { + "type": "string", + "title": "If" + } + ] + }, + "then": { + "prefixItems": [ + { "title": "Then" } + ] + }, + "else": { + "prefixItems": [ + { "title": "Else" } + ] + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": ["", 42], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/then/prefixItems/0": "Then", + "#/if/prefixItems/0": "If" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + }, + { + "instance": [42, ""], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/else/prefixItems/0": "Else" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `allOf`", + "compatibility": "2020", + "schema": { + "allOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/allOf/0/prefixItems/0": "Evaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `anyOf`", + "compatibility": "2020", + "schema": { + "anyOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/anyOf/0/prefixItems/0": "Evaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `oneOf`", + "compatibility": "2020", + "schema": { + "oneOf": [ + { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + ], + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/oneOf/0/prefixItems/0": "Evaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + }, + { + "description": "`unevaluatedItems` with `not`", + "compatibility": "2020", + "schema": { + "not": { + "not": { + "prefixItems": [ + { "title": "Evaluated" } + ] + } + }, + "unevaluatedItems": { "title": "Unevaluated" } + }, + "tests": [ + { + "instance": [42, 24], + "assertions": [ + { + "location": "/0", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + }, + { + "location": "/1", + "keyword": "title", + "expected": { + "#/unevaluatedItems": "Unevaluated" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/annotations/tests/unknown.json b/src/test/suite/annotations/tests/unknown.json new file mode 100644 index 000000000..b0c89003c --- /dev/null +++ b/src/test/suite/annotations/tests/unknown.json @@ -0,0 +1,27 @@ +{ + "$schema": "../test-suite.schema.json", + "description": "Unknown keywords", + "suite": [ + { + "description": "`unknownKeyword` is an annotation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "x-unknownKeyword": "Foo" + }, + "tests": [ + { + "instance": 42, + "assertions": [ + { + "location": "", + "keyword": "x-unknownKeyword", + "expected": { + "#": "Foo" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/suite/bin/annotate-specification-links b/src/test/suite/bin/annotate-specification-links new file mode 100644 index 000000000..963768b43 --- /dev/null +++ b/src/test/suite/bin/annotate-specification-links @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +""" +Annotate pull requests to the GitHub repository with links to specifications. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any +import json +import re +import sys + +from uritemplate import URITemplate + + +BIN_DIR = Path(__file__).parent +TESTS = BIN_DIR.parent / "tests" +URLS = json.loads(BIN_DIR.joinpath("specification_urls.json").read_text()) + + +def urls(version: str) -> dict[str, URITemplate]: + """ + Retrieve the version-specific URLs for specifications. + """ + for_version = {**URLS["json-schema"][version], **URLS["external"]} + return {k: URITemplate(v) for k, v in for_version.items()} + + +def annotation( + path: Path, + message: str, + line: int = 1, + level: str = "notice", + **kwargs: Any, +) -> str: + """ + Format a GitHub annotation. + + See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions + for full syntax. + """ + + if kwargs: + additional = "," + ",".join(f"{k}={v}" for k, v in kwargs.items()) + else: + additional = "" + + relative = path.relative_to(TESTS.parent) + return f"::{level} file={relative},line={line}{additional}::{message}\n" + + +def line_number_of(path: Path, case: dict[str, Any]) -> int: + """ + Crudely find the line number of a test case. + """ + with path.open() as file: + description = case["description"] + return next( + (i + 1 for i, line in enumerate(file, 1) if description in line), + 1, + ) + +def extract_kind_and_spec(key: str) -> (str, str): + """ + Extracts specification number and kind from the defined key + """ + can_have_spec = ["rfc", "iso"] + if not any(key.startswith(el) for el in can_have_spec): + return key, "" + number = re.search(r"\d+", key) + spec = "" if number is None else number.group(0) + kind = key.removesuffix(spec) + return kind, spec + + +def main(): + # Clear annotations which may have been emitted by a previous run. + sys.stdout.write("::remove-matcher owner=me::\n") + + for version in TESTS.iterdir(): + if version.name in {"draft-next", "latest"}: + continue + + version_urls = urls(version.name) + + for path in version.rglob("*.json"): + try: + contents = json.loads(path.read_text()) + except json.JSONDecodeError as error: + error = annotation( + level="error", + path=path, + line=error.lineno, + col=error.pos + 1, + title=str(error), + message=f"cannot load {path}" + ) + sys.stdout.write(error) + continue + + for test_case in contents: + specifications = test_case.get("specification") + if specifications is not None: + for each in specifications: + quote = each.pop("quote", "") + (key, section), = each.items() + + (kind, spec) = extract_kind_and_spec(key) + + url_template = version_urls[kind] + if url_template is None: + error = annotation( + level="error", + path=path, + line=line_number_of(path, test_case), + title=f"unsupported template '{kind}'", + message=f"cannot find a URL template for '{kind}'" + ) + sys.stdout.write(error) + continue + + url = url_template.expand( + spec=spec, + section=section, + ) + + message = f"{url}\n\n{quote}" if quote else url + sys.stdout.write( + annotation( + path=path, + line=line_number_of(path, test_case), + title="Specification Link", + message=message, + ), + ) + + +if __name__ == "__main__": + main() diff --git a/src/test/suite/bin/annotation-tests.ts b/src/test/suite/bin/annotation-tests.ts new file mode 100644 index 000000000..2d3d19326 --- /dev/null +++ b/src/test/suite/bin/annotation-tests.ts @@ -0,0 +1,31 @@ +#!/usr/bin/env deno +import { validate } from "npm:@hyperjump/json-schema/draft-07"; +import { BASIC } from "npm:@hyperjump/json-schema/experimental"; + +const validateTestSuite = await validate("./annotations/test-suite.schema.json"); + +console.log("Validating annotation tests ..."); + +let isValid = true; +for await (const entry of Deno.readDir("./annotations/tests")) { + if (entry.isFile) { + const json = await Deno.readTextFile(`./annotations/tests/${entry.name}`); + const suite = JSON.parse(json); + + const output = validateTestSuite(suite, BASIC); + + if (output.valid) { + console.log(`\x1b[32m✔\x1b[0m ${entry.name}`); + } else { + isValid = false; + console.log(`\x1b[31m✖\x1b[0m ${entry.name}`); + console.log(output); + } + } +} + +console.log("Done."); + +if (!isValid) { + Deno.exit(1); +} diff --git a/src/test/suite/bin/jsonschema_suite b/src/test/suite/bin/jsonschema_suite new file mode 100755 index 000000000..c83e7cb2c --- /dev/null +++ b/src/test/suite/bin/jsonschema_suite @@ -0,0 +1,733 @@ +#! /usr/bin/env python3 +from pathlib import Path +from urllib.parse import urljoin +import argparse +import json +import os +import random +import shutil +import sys +import textwrap +import unittest +import warnings + +try: + import jsonschema.validators +except ImportError: + jsonschema = Unresolvable = None + VALIDATORS = {} +else: + from referencing.exceptions import Unresolvable + + VALIDATORS = { + "draft3": jsonschema.validators.Draft3Validator, + "draft4": jsonschema.validators.Draft4Validator, + "draft6": jsonschema.validators.Draft6Validator, + "draft7": jsonschema.validators.Draft7Validator, + "draft2019-09": jsonschema.validators.Draft201909Validator, + "draft2020-12": jsonschema.validators.Draft202012Validator, + "latest": jsonschema.validators.Draft202012Validator, + } + + +ROOT_DIR = Path(__file__).parent.parent +SUITE_ROOT_DIR = ROOT_DIR / "tests" +OUTPUT_ROOT_DIR = ROOT_DIR / "output-tests" + +REMOTES_DIR = ROOT_DIR / "remotes" +REMOTES_BASE_URL = "http://localhost:1234/" + +TEST_SCHEMA = json.loads(ROOT_DIR.joinpath("test-schema.json").read_text()) +OUTPUT_TEST_SCHEMA = json.loads( + ROOT_DIR.joinpath("output-test-schema.json").read_text(), +) + + +def files(paths): + """ + Each test file in the provided paths, as an array of test cases. + """ + for path in paths: + yield path, json.loads(path.read_text()) + + +def cases(paths): + """ + Each test case within each file in the provided paths. + """ + for _, test_file in files(paths): + yield from test_file + + +def tests(paths): + """ + Each individual test within all cases within the provided paths. + """ + for case in cases(paths): + for test in case["tests"]: + test["schema"] = case["schema"] + yield test + + +def collect(root_dir): + """ + All of the test file paths within the given root directory, recursively. + """ + return root_dir.rglob("*.json") + + +def url_for_path(path): + """ + Return the assumed remote URL for a file in the remotes/ directory. + + Tests in the refRemote.json file reference this URL, and assume the + corresponding contents are available at the URL. + """ + + return urljoin( + REMOTES_BASE_URL, + str(path.relative_to(REMOTES_DIR)).replace("\\", "/"), # Windows... + ) + + +def versions_and_validators(): + """ + All versions we can validate schemas from. + """ + + for version in SUITE_ROOT_DIR.iterdir(): + if not version.is_dir(): + continue + + Validator = VALIDATORS.get(version.name) + if Validator is None: + warnings.warn(f"No schema validator for {version.name}") + continue + + yield version, Validator + + +class SanityTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print(f"Looking for tests in {SUITE_ROOT_DIR}") + print(f"Looking for output tests in {OUTPUT_ROOT_DIR}") + print(f"Looking for remotes in {REMOTES_DIR}") + + cls.test_files = list(collect(SUITE_ROOT_DIR)) + assert cls.test_files, "Didn't find the test files!" + print(f"Found {len(cls.test_files)} test files") + + cls.output_test_files = [ + each + for each in collect(OUTPUT_ROOT_DIR) + if each.name != "output-schema.json" + ] + assert cls.output_test_files, "Didn't find the output test files!" + print(f"Found {len(cls.output_test_files)} output test files") + + cls.remote_files = list(collect(REMOTES_DIR)) + assert cls.remote_files, "Didn't find the remote files!" + print(f"Found {len(cls.remote_files)} remote files") + + def assertUnique(self, iterable): + """ + Assert that the elements of an iterable are unique. + """ + + seen, duplicated = set(), set() + for each in iterable: + if each in seen: + duplicated.add(each) + seen.add(each) + self.assertFalse(duplicated, "Elements are not unique.") + + def assertFollowsDescriptionStyle(self, description): + """ + Instead of saying "test that X frobs" or "X should frob" use "X frobs". + + See e.g. https://jml.io/pages/test-docstrings.html + + This test isn't comprehensive (it doesn't catch all the extra + verbiage there), but it's just to catch whatever it manages to + cover. + """ + + message = ( + "In descriptions, don't say 'Test that X frobs' or 'X should " + "frob' or 'X should be valid'. Just say 'X frobs' or 'X is " + "valid'. It's shorter, and the test suite is entirely about " + "what *should* be already. " + "See https://jml.io/pages/test-docstrings.html for help." + ) + self.assertNotRegex(description, r"\bshould\b", message) + self.assertNotRegex(description, r"(?i)\btest(s)? that\b", message) + + def test_all_json_files_are_valid(self): + """ + All files (tests, output tests, remotes, etc.) contain valid JSON. + """ + for path in collect(ROOT_DIR): + with self.subTest(path=path): + try: + json.loads(path.read_text()) + except ValueError as error: + self.fail(f"{path} contains invalid JSON ({error})") + + def test_all_case_descriptions_have_reasonable_length(self): + """ + All cases have reasonably long descriptions. + """ + for case in cases(self.test_files + self.output_test_files): + with self.subTest(description=case["description"]): + self.assertLess( + len(case["description"]), + 150, + "Description is too long (keep it to less than 150 chars).", + ) + + def test_all_test_descriptions_have_reasonable_length(self): + """ + All tests have reasonably long descriptions. + """ + for count, test in enumerate( + tests(self.test_files + self.output_test_files) + ): + with self.subTest(description=test["description"]): + self.assertLess( + len(test["description"]), + 70, + "Description is too long (keep it to less than 70 chars).", + ) + print(f"Found {count} tests.") + + def test_all_case_descriptions_are_unique(self): + """ + All cases have unique descriptions in their files. + """ + for path, cases in files(self.test_files + self.output_test_files): + with self.subTest(path=path): + self.assertUnique(case["description"] for case in cases) + + def test_all_test_descriptions_are_unique(self): + """ + All test cases have unique test descriptions in their tests. + """ + for count, case in enumerate( + cases(self.test_files + self.output_test_files) + ): + with self.subTest(description=case["description"]): + self.assertUnique( + test["description"] for test in case["tests"] + ) + print(f"Found {count} test cases.") + + def test_case_descriptions_do_not_use_modal_verbs(self): + for case in cases(self.test_files + self.output_test_files): + with self.subTest(description=case["description"]): + self.assertFollowsDescriptionStyle(case["description"]) + + def test_test_descriptions_do_not_use_modal_verbs(self): + for test in tests(self.test_files + self.output_test_files): + with self.subTest(description=test["description"]): + self.assertFollowsDescriptionStyle(test["description"]) + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_all_schemas_are_valid(self): + """ + All schemas are valid under their metaschemas. + """ + for version, Validator in versions_and_validators(): + # Valid (optional test) schemas contain regexes which + # aren't valid Python regexes, so skip checking it + Validator.FORMAT_CHECKER.checkers.pop("regex", None) + + test_files = collect(version) + for case in cases(test_files): + with self.subTest(case=case): + try: + Validator.check_schema( + case["schema"], + format_checker=Validator.FORMAT_CHECKER, + ) + except jsonschema.SchemaError: + self.fail( + "Found an invalid schema. " + "See the traceback for details on why." + ) + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_arbitrary_schemas_do_not_use_unknown_keywords(self): + """ + Test cases do not use unknown keywords. + + (Unless they specifically are testing the specified behavior for + unknown keywords). + + This helps prevent accidental leakage of newer keywords into older + drafts where they didn't exist. + """ + + KNOWN = { + "draft2020-12": { + "$anchor", + "$comment", + "$defs", + "$dynamicAnchor", + "$dynamicRef", + "$id", + "$ref", + "$schema", + "$vocabulary", + "additionalProperties", + "allOf", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "contentSchema", + "dependencies", + "dependentRequired", + "dependentSchemas", + "description", + "else", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "if", + "items", + "maxContains", + "maxItems", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minContains", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "prefixItems", + "properties", + "propertyNames", + "required", + "then", + "title", + "type", + "unevaluatedItems", + "unevaluatedProperties", + "uniqueItems", + }, + "draft2019-09": { + "$anchor", + "$comment", + "$defs", + "$id", + "$recursiveAnchor", + "$recursiveRef", + "$ref", + "$schema", + "$vocabulary", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "contentSchema", + "dependencies", + "dependentRequired", + "dependentSchemas", + "description", + "else", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "if", + "items", + "maxContains", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minContains", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "required", + "then", + "title", + "type", + "unevaluatedItems", + "unevaluatedProperties", + "uniqueItems", + }, + "draft7": { + "$comment", + "$id", + "$ref", + "$schema", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "definitions", + "dependencies", + "description", + "else", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "if", + "items", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "required", + "then", + "title", + "type", + "type", + "uniqueItems", + }, + "draft6": { + "$comment", + "$id", + "$ref", + "$schema", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "definitions", + "dependencies", + "description", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "items", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "required", + "title", + "type", + "uniqueItems", + }, + "draft4": { + "$ref", + "$schema", + "additionalItems", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "definitions", + "dependencies", + "description", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "id", + "items", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "required", + "title", + "type", + "uniqueItems", + + # Technically this is wrong, $comment doesn't exist in this + # draft, but the point of this test is to detect mistakes by, + # test authors, whereas the point of the $comment keyword is + # to just standardize a place for a comment, so it's not a + # mistake to use it in earlier drafts in tests per se. + "$comment", + }, + "draft3": { + "$ref", + "$schema", + "additionalItems", + "additionalProperties", + "definitions", + "dependencies", + "description", + "disallow", + "divisibleBy", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "extends", + "format", + "id", + "items", + "maxItems", + "maxLength", + "maximum", + "minItems", + "minLength", + "minimum", + "pattern", + "patternProperties", + "properties", + "title", + "type", + "uniqueItems", + + # Technically this is wrong, $comment doesn't exist in this + # draft, but the point of this test is to detect mistakes by, + # test authors, whereas the point of the $comment keyword is + # to just standardize a place for a comment, so it's not a + # mistake to use it in earlier drafts in tests per se. + "$comment", + }, + } + + def missing(d): + from collections.abc import Mapping + + class BlowUpForUnknownProperties(Mapping): + def __iter__(this): + return iter(d) + + def __getitem__(this, k): + if k not in KNOWN[version.name]: + self.fail( + f"{k} is not a known keyword for {version.name}. " + "If this test is testing behavior related to " + "unknown keywords you may need to add it to the " + "allowlist in the jsonschema_suite checker. " + "Otherwise it may contain a typo!" + ) + return d[k] + + def __len__(this): + return len(d) + + return BlowUpForUnknownProperties() + + for version, Validator in versions_and_validators(): + if version.name == "latest": + continue + + self.addCleanup( + setattr, Validator, "VALIDATORS", Validator.VALIDATORS, + ) + Validator.VALIDATORS = missing(dict(Validator.VALIDATORS)) + + test_files = [ + each for each in collect(version) + if each.stem != "refOfUnknownKeyword" + ] + for case in cases(test_files): + if "unknown keyword" in case["description"]: + continue + with self.subTest(case=case, version=version.name): + try: + Validator(case["schema"]).is_valid(12) + except Unresolvable: + pass + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_suites_are_valid(self): + """ + All test files are valid under test-schema.json. + """ + Validator = jsonschema.validators.validator_for(TEST_SCHEMA) + validator = Validator(TEST_SCHEMA) + for path, cases in files(self.test_files): + with self.subTest(path=path): + try: + validator.validate(cases) + except jsonschema.ValidationError as error: + self.fail(str(error)) + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_output_suites_are_valid(self): + """ + All output test files are valid under output-test-schema.json. + """ + Validator = jsonschema.validators.validator_for(OUTPUT_TEST_SCHEMA) + validator = Validator(OUTPUT_TEST_SCHEMA) + for path, cases in files(self.output_test_files): + with self.subTest(path=path): + try: + validator.validate(cases) + except jsonschema.ValidationError as error: + self.fail(str(error)) + + +def main(arguments): + if arguments.command == "check": + suite = unittest.TestLoader().loadTestsFromTestCase(SanityTests) + result = unittest.TextTestRunner().run(suite) + sys.exit(not result.wasSuccessful()) + elif arguments.command == "flatten": + selected_cases = [case for case in cases(collect(arguments.version))] + + if arguments.randomize: + random.shuffle(selected_cases) + + json.dump(selected_cases, sys.stdout, indent=4, sort_keys=True) + elif arguments.command == "remotes": + remotes = { + url_for_path(path): json.loads(path.read_text()) + for path in collect(REMOTES_DIR) + } + json.dump(remotes, sys.stdout, indent=4, sort_keys=True) + elif arguments.command == "dump_remotes": + if arguments.update: + shutil.rmtree(arguments.out_dir, ignore_errors=True) + + try: + shutil.copytree(REMOTES_DIR, arguments.out_dir) + except FileExistsError: + print(f"{arguments.out_dir} already exists. Aborting.") + sys.exit(1) + elif arguments.command == "serve": + try: + import flask + except ImportError: + print( + textwrap.dedent( + """ + The Flask library is required to serve the remote schemas. + + You can install it by running `pip install Flask`. + + Alternatively, see the `jsonschema_suite remotes` or + `jsonschema_suite dump_remotes` commands to create static files + that can be served with your own web server. + """.strip( + "\n" + ) + ) + ) + sys.exit(1) + + app = flask.Flask(__name__) + + @app.route("/") + def serve_path(path): + return flask.send_from_directory(REMOTES_DIR, path) + + app.run(port=1234) + + +parser = argparse.ArgumentParser( + description="JSON Schema Test Suite utilities", +) +subparsers = parser.add_subparsers( + help="utility commands", dest="command", metavar="COMMAND" +) +subparsers.required = True + +check = subparsers.add_parser("check", help="Sanity check the test suite.") + +flatten = subparsers.add_parser( + "flatten", + help="Output a flattened file containing a selected version's test cases.", +) +flatten.add_argument( + "--randomize", + action="store_true", + help="Randomize the order of the outputted cases.", +) +flatten.add_argument( + "version", + help="The directory containing the version to output", +) + +remotes = subparsers.add_parser( + "remotes", + help="Output the expected URLs and their associated schemas for remote " + "ref tests as a JSON object.", +) + +dump_remotes = subparsers.add_parser( + "dump_remotes", + help="Dump the remote ref schemas into a file tree", +) +dump_remotes.add_argument( + "--update", + action="store_true", + help="Update the remotes in an existing directory.", +) +dump_remotes.add_argument( + "--out-dir", + default=REMOTES_DIR, + type=os.path.abspath, + help="The output directory to create as the root of the file tree", +) + +serve = subparsers.add_parser( + "serve", + help="Start a webserver to serve schemas used by remote ref tests.", +) + +if __name__ == "__main__": + main(parser.parse_args()) diff --git a/src/test/suite/bin/specification_urls.json b/src/test/suite/bin/specification_urls.json new file mode 100644 index 000000000..e1824999a --- /dev/null +++ b/src/test/suite/bin/specification_urls.json @@ -0,0 +1,34 @@ +{ + "json-schema": { + "draft2020-12": { + "core": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#section-{section}", + "validation": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-{section}" + }, + "draft2019-09": { + "core": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#rfc.section.{section}", + "validation": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-validation-02#rfc.section.{section}" + }, + "draft7": { + "core": "https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.{section}", + "validation": "https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01#rfc.section.{section}" + }, + "draft6": { + "core": "https://json-schema.org/draft-06/draft-wright-json-schema-01#rfc.section.{section}", + "validation": "https://json-schema.org/draft-06/draft-wright-json-schema-validation-01#rfc.section.{section}" + }, + "draft4": { + "core": "https://json-schema.org/draft-04/draft-zyp-json-schema-04#rfc.section.{section}", + "validation": "https://json-schema.org/draft-04/draft-fge-json-schema-validation-00#rfc.section.{section}" + }, + "draft3": { + "core": "https://json-schema.org/draft-03/draft-zyp-json-schema-03.pdf" + } + }, + + "external": { + "ecma262": "https://262.ecma-international.org/{section}", + "perl5": "https://perldoc.perl.org/perlre#{section}", + "rfc": "https://www.rfc-editor.org/rfc/rfc{spec}.html#section-{section}", + "iso": "https://www.iso.org/obp/ui" + } +} diff --git a/src/test/suite/output-test-schema.json b/src/test/suite/output-test-schema.json new file mode 100644 index 000000000..02c51ef55 --- /dev/null +++ b/src/test/suite/output-test-schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/output-test-schema", + "description": "A schema for files contained within this suite", + + "type": "array", + "minItems": 1, + "items": { + "description": "An individual test case, containing multiple tests of a single schema's behavior", + + "type": "object", + "required": [ "description", "schema", "tests" ], + "properties": { + "description": { + "description": "The test case description", + "type": "string" + }, + "comment": { + "description": "Any additional comments about the test case", + "type": "string" + }, + "schema": { + "description": "A valid JSON Schema (one written for the corresponding version directory that the file sits within)." + }, + "tests": { + "description": "A set of related tests all using the same schema", + "type": "array", + "items": { "$ref": "#/$defs/test" }, + "minItems": 1 + } + }, + "additionalProperties": false + }, + + "$defs": { + "test": { + "description": "A single output test", + + "type": "object", + "required": [ "description", "data", "output" ], + "properties": { + "description": { + "description": "The test description, briefly explaining which behavior it exercises", + "type": "string" + }, + "comment": { + "description": "Any additional comments about the test", + "type": "string" + }, + "data": { + "description": "The instance which should be validated against the schema in \"schema\"." + }, + "output": { + "description": "schemas that are used to verify output", + "type": "object", + "properties": { + "flag": { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + "basic": { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + "detailed": { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + "verbose": { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + "list": { "$ref": "https://json-schema.org/draft/2020-12/schema" }, + "hierarchy": { "$ref": "https://json-schema.org/draft/2020-12/schema" } + }, + "minProperties": 1, + "additionalProperties": false + } + } + } + } +} diff --git a/src/test/suite/output-tests/README.md b/src/test/suite/output-tests/README.md new file mode 100644 index 000000000..d209bdb25 --- /dev/null +++ b/src/test/suite/output-tests/README.md @@ -0,0 +1,63 @@ +These tests are intended to validate that implementations are correctly generating output in accordance with the specification. + +Output was initially specified with draft 2019-09. It remained largely unchanged for draft 2020-12, but will receive an update with the next release. + +_**NOTE** Although the formats didn't change much between 2019-09 and 2020-12, the tests are copied for 2020-12 because the `$schema` is different and implementations may (but shouldn't) produce different output._ + +## Organization + +The tests are organized by specification release and then into two categories: content and structure. + +Content tests verify that the keywords are producing the correct annotations and/or error messages. Since there are no requirements on the content of error messages, there's not much that can be verified for them, but it is possible to identify when a error message _could_ be present. Primarily, these tests need to extensively cover the annotation behaviors of each keyword. The only output format needed for these tests is `basic` for 2019-09/2020-12 and `list` for later versions. + +Structure tests verify that the structures of the various formats (i.e. `flag`, `basic`, `detailed`, `verbose` for 2019-09/2020-12 and `flag`, `list`, `hierarchical` for later versions) are correct. These tests don't need to cover each keyword; rather they need to sufficiently cover the various aspects of building the output structures by using whatever keywords are necessary to do so. + +In each release folder, you'll also find an _output-schema.json_ file that contains the schema from the specification repo that describes output for that release. This schema will need to be loaded as the tests reference it. + +## Test Files + +The content of a test file is similar to the validation tests in `tests/`: for each test case, the `valid` property has been removed, and an `output` property has been added. + +The `output` property itself has a property for each of the output formats where the value is a schema that will successfully validate for compliant output. For the content tests, only `basic`/`list` needs to be present. + +## Other notes + +### Ambiguity around 2020-09/2020-12 `basic` + +The 2019-09/2020-12 specs don't define the structure of `basic` very thoroughly. Specifically there is a nuance where if the list contains a single output node, there are two possible structures, given the text: + +- the output node for the root schema appears in the list with a containing node that just has a `valid` property + ```json + { + "valid": false, + "errors": [ + { + "valid": false, + "keywordLocation": "", + "absoluteKeywordLocation": "https://json-schema.org/tests/content/draft2019-09/general/0", + "instanceLocation": "" + } + ] + } + ``` +- the entire structure is collapsed to just the root output node as `detailed` would do. + ```json + { + "valid": false, + "keywordLocation": "", + "absoluteKeywordLocation": "https://json-schema.org/tests/content/draft2019-09/general/0", + "instanceLocation": "" + } + ``` +As the Test Suite should not prefer one interpretation over another, these cases need to be tested another way. + +A simple solution (though there are likely others) is to force a second output unit by adding an `"anyOf": [ true ]`. This has no impact on the validation result while adding superfluous structure to the output that avoids the above ambiguous scenario. The test schema should still be targeted on what's being tested and ignore any output units generated by this extra keyword. + +## Contributing + +Of course, first and foremost, follow the [Contributing guide](/CONTRIBUTING.md). + +When writing test cases, try to keep output validation schemas targeted to verify a single requirement. Where possible (and where it makes sense), create multiple tests to cover multiple requirements. This will help keep the output validation schemas small and increase readability. (It also increases your test count. 😉) + +For the content tests, there is also a _general.json_ file that contains tests that do not necessarily pertain to any single keyword. + diff --git a/src/test/suite/output-tests/draft-next/content/general.json b/src/test/suite/output-tests/draft-next/content/general.json new file mode 100644 index 000000000..23fe1daeb --- /dev/null +++ b/src/test/suite/output-tests/draft-next/content/general.json @@ -0,0 +1,43 @@ +[ + { + "description": "failed validation produces no annotations", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://json-schema.org/tests/content/draft-next/general/0", + "type": "string", + "readOnly": true + }, + "tests": [ + { + "description": "dropped annotations MAY appear in droppedAnnotations", + "data": 1, + "output": { + "list": { + "$id": "https://json-schema.org/tests/content/draft-next/general/0/tests/0/basic", + "$ref": "/draft/next/output/schema", + "properties": { + "details": { + "contains": { + "properties": { + "evaluationPath": {"const": ""}, + "schemaLocation": {"const": "https://json-schema.org/tests/content/draft-next/general/0#"}, + "instanceLocation": {"const": ""}, + "annotations": false, + "droppedAnnotations": { + "properties": { + "readOnly": {"const": true} + }, + "required": ["readOnly"] + } + }, + "required": ["evaluationPath", "schemaLocation", "instanceLocation"] + } + } + }, + "required": ["details"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft-next/content/readOnly.json b/src/test/suite/output-tests/draft-next/content/readOnly.json new file mode 100644 index 000000000..8d1c2dec2 --- /dev/null +++ b/src/test/suite/output-tests/draft-next/content/readOnly.json @@ -0,0 +1,41 @@ +[ + { + "description": "readOnly generates its value as an annotation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://json-schema.org/tests/content/draft-next/readOnly/0", + "readOnly": true + }, + "tests": [ + { + "description": "readOnly is true", + "data": 1, + "output": { + "list": { + "$id": "https://json-schema.org/tests/content/draft-next/readOnly/0/tests/0/basic", + "$ref": "/draft/next/output/schema", + "properties": { + "details": { + "contains": { + "properties": { + "evaluationPath": {"const": ""}, + "schemaLocation": {"const": "https://json-schema.org/tests/content/draft-next/readOnly/0#"}, + "instanceLocation": {"const": ""}, + "annotations": { + "properties": { + "readOnly": {"const": true} + }, + "required": ["readOnly"] + } + }, + "required": ["evaluationPath", "schemaLocation", "instanceLocation", "annotations"] + } + } + }, + "required": ["details"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft-next/content/type.json b/src/test/suite/output-tests/draft-next/content/type.json new file mode 100644 index 000000000..afc7f5fd1 --- /dev/null +++ b/src/test/suite/output-tests/draft-next/content/type.json @@ -0,0 +1,39 @@ +[ + { + "description": "incorrect type", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://json-schema.org/tests/content/draft-next/type/0", + "type": "string" + }, + "tests": [ + { + "description": "incorrect type must be reported, but a message is not required", + "data": 1, + "output": { + "list": { + "$id": "https://json-schema.org/tests/content/draft-next/type/0/tests/0/basic", + "$ref": "/draft/next/output/schema", + "properties": { + "details": { + "contains": { + "properties": { + "evaluationPath": {"const": ""}, + "schemaLocation": {"const": "https://json-schema.org/tests/content/draft-next/type/0#"}, + "instanceLocation": {"const": ""}, + "annotations": false, + "errors": { + "required": ["type"] + } + }, + "required": ["evaluationPath", "schemaLocation", "instanceLocation"] + } + } + }, + "required": ["details"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft-next/output-schema.json b/src/test/suite/output-tests/draft-next/output-schema.json new file mode 100644 index 000000000..26286fa4d --- /dev/null +++ b/src/test/suite/output-tests/draft-next/output-schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://json-schema.org/draft/next/output/schema", + "description": "A schema that validates the minimum requirements for validation output", + + "anyOf": [ + { "$ref": "#/$defs/flag" }, + { "$ref": "#/$defs/basic" }, + { "$ref": "#/$defs/hierarchical" } + ], + "$defs": { + "outputUnit":{ + "properties": { + "valid": { "type": "boolean" }, + "evaluationPath": { + "type": "string", + "format": "json-pointer" + }, + "schemaLocation": { + "type": "string", + "format": "uri" + }, + "instanceLocation": { + "type": "string", + "format": "json-pointer" + }, + "details": { + "$ref": "#/$defs/outputUnitArray" + }, + "annotations": { + "type": "object", + "additionalProperties": true + }, + "droppedAnnotations": { + "type": "object", + "additionalProperties": true + }, + "errors": { + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "required": [ "valid", "evaluationPath", "schemaLocation", "instanceLocation" ], + "allOf": [ + { + "if": { + "anyOf": [ + { + "required": [ "errors" ] + }, + { + "required": [ "droppedAnnotations" ] + } + ] + }, + "then": { + "properties": { + "valid": { "const": false } + } + } + }, + { + "if": { + "required": [ "annotations" ] + }, + "then": { + "properties": { + "valid": { "const": true } + } + } + } + ] + }, + "outputUnitArray": { + "type": "array", + "items": { "$ref": "#/$defs/outputUnit" } + }, + "flag": { + "properties": { + "valid": { "type": "boolean" } + }, + "required": [ "valid" ] + }, + "basic": { + "properties": { + "valid": { "type": "boolean" }, + "details": { + "$ref": "#/$defs/outputUnitArray" + } + }, + "required": [ "valid", "details" ] + }, + "hierarchical": { "$ref": "#/$defs/outputUnit" } + } +} diff --git a/src/test/suite/output-tests/draft2019-09/content/escape.json b/src/test/suite/output-tests/draft2019-09/content/escape.json new file mode 100644 index 000000000..d8cda154b --- /dev/null +++ b/src/test/suite/output-tests/draft2019-09/content/escape.json @@ -0,0 +1,38 @@ +[ + { + "description": "tilde and forward slash in json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/tests/content/draft2019-09/escape/0", + "properties": { + "~a/b": {"type": "number"} + } + }, + "tests": [ + { + "description": "incorrect type must be reported, but a message is not required", + "data": {"~a/b": "foobar"}, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2019-09/escape/0/tests/0/basic", + "$ref": "/draft/2019-09/output/schema", + "properties": { + "errors": { + "contains": { + "properties": { + "keywordLocation": {"const": "/properties/~0a~1b/type"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2019-09/escape/0#/properties/~0a~1b/type"}, + "instanceLocation": {"const": "/~0a~1b"}, + "annotation": false + }, + "required": ["keywordLocation", "instanceLocation"] + } + } + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2019-09/content/general.json b/src/test/suite/output-tests/draft2019-09/content/general.json new file mode 100644 index 000000000..91941700e --- /dev/null +++ b/src/test/suite/output-tests/draft2019-09/content/general.json @@ -0,0 +1,34 @@ +[ + { + "description": "failed validation produces no annotations", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/tests/content/draft2019-09/general/0", + "type": "string", + "readOnly": true + }, + "tests": [ + { + "description": "readOnly annotation is dropped", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2019-09/general/0/tests/0/basic", + "$ref": "/draft/2019-09/output/schema", + "properties": { + "errors": { + "items": { + "properties": { + "annotation": false + } + } + }, + "annotations": false + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2019-09/content/readOnly.json b/src/test/suite/output-tests/draft2019-09/content/readOnly.json new file mode 100644 index 000000000..62db1a83c --- /dev/null +++ b/src/test/suite/output-tests/draft2019-09/content/readOnly.json @@ -0,0 +1,38 @@ +[ + { + "description": "readOnly generates its value as an annotation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/tests/content/draft2019-09/readOnly/0", + "readOnly": true + }, + "tests": [ + { + "description": "readOnly is true", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2019-09/readOnly/0/tests/0/basic", + "$ref": "/draft/2019-09/output/schema", + "properties": { + "annotations": { + "contains": { + "type": "object", + "properties": { + "keywordLocation": {"const": "/readOnly"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2019-09/readOnly/0#/readOnly"}, + "instanceLocation": {"const": ""}, + "annotation": {"const": true} + }, + "required": ["keywordLocation", "instanceLocation", "annotation"] + } + }, + "errors": false + }, + "required": ["annotations"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2019-09/content/type.json b/src/test/suite/output-tests/draft2019-09/content/type.json new file mode 100644 index 000000000..21118fd5f --- /dev/null +++ b/src/test/suite/output-tests/draft2019-09/content/type.json @@ -0,0 +1,37 @@ +[ + { + "description": "validating type", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/tests/content/draft2019-09/type/0", + "type": "string", + "anyOf": [ true ] + }, + "tests": [ + { + "description": "incorrect type must be reported, but a message is not required", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2019-09/type/0/tests/0/basic", + "$ref": "/draft/2019-09/output/schema", + "properties": { + "errors": { + "contains": { + "properties": { + "keywordLocation": {"const": "/type"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2019-09/type/0#/type"}, + "instanceLocation": {"const": ""}, + "annotation": false + }, + "required": ["keywordLocation", "instanceLocation"] + } + } + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2019-09/output-schema.json b/src/test/suite/output-tests/draft2019-09/output-schema.json new file mode 100644 index 000000000..ff523eea5 --- /dev/null +++ b/src/test/suite/output-tests/draft2019-09/output-schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/output/schema", + "description": "A schema that validates the minimum requirements for validation output", + + "anyOf": [ + { "$ref": "#/$defs/flag" }, + { "$ref": "#/$defs/basic" }, + { "$ref": "#/$defs/detailed" }, + { "$ref": "#/$defs/verbose" } + ], + "$defs": { + "outputUnit":{ + "properties": { + "valid": { "type": "boolean" }, + "keywordLocation": { + "type": "string", + "format": "json-pointer" + }, + "absoluteKeywordLocation": { + "type": "string", + "format": "uri" + }, + "instanceLocation": { + "type": "string", + "format": "json-pointer" + }, + "error": { + "type": "string" + }, + "errors": { + "$ref": "#/$defs/outputUnitArray" + }, + "annotations": { + "$ref": "#/$defs/outputUnitArray" + } + }, + "required": [ "valid", "keywordLocation", "instanceLocation" ], + "allOf": [ + { + "if": { + "properties": { + "valid": { "const": false } + } + }, + "then": { + "anyOf": [ + { + "required": [ "error" ] + }, + { + "required": [ "errors" ] + } + ] + } + }, + { + "if": { + "anyOf": [ + { + "properties": { + "keywordLocation": { + "pattern": "/\\$ref/" + } + } + }, + { + "properties": { + "keywordLocation": { + "pattern": "/\\$recursiveRef/" + } + } + } + ] + }, + "then": { + "required": [ "absoluteKeywordLocation" ] + } + } + ] + }, + "outputUnitArray": { + "type": "array", + "items": { "$ref": "#/$defs/outputUnit" } + }, + "flag": { + "properties": { + "valid": { "type": "boolean" } + }, + "required": [ "valid" ] + }, + "basic": { "$ref": "#/$defs/outputUnit" }, + "detailed": { "$ref": "#/$defs/outputUnit" }, + "verbose": { "$ref": "#/$defs/outputUnit" } + } +} diff --git a/src/test/suite/output-tests/draft2020-12/content/escape.json b/src/test/suite/output-tests/draft2020-12/content/escape.json new file mode 100644 index 000000000..c329c919c --- /dev/null +++ b/src/test/suite/output-tests/draft2020-12/content/escape.json @@ -0,0 +1,38 @@ +[ + { + "description": "tilde and forward slash in json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/content/draft2020-12/escape/0", + "properties": { + "~a/b": {"type": "number"} + } + }, + "tests": [ + { + "description": "incorrect type must be reported, but a message is not required", + "data": {"~a/b": "foobar"}, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2020-12/escape/0/tests/0/basic", + "$ref": "/draft/2020-12/output/schema", + "properties": { + "errors": { + "contains": { + "properties": { + "keywordLocation": {"const": "/properties/~0a~1b/type"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2020-12/escape/0#/properties/~0a~1b/type"}, + "instanceLocation": {"const": "/~0a~1b"}, + "annotation": false + }, + "required": ["keywordLocation", "instanceLocation"] + } + } + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2020-12/content/general.json b/src/test/suite/output-tests/draft2020-12/content/general.json new file mode 100644 index 000000000..1f2b370c1 --- /dev/null +++ b/src/test/suite/output-tests/draft2020-12/content/general.json @@ -0,0 +1,34 @@ +[ + { + "description": "failed validation produces no annotations", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/content/draft2020-12/general/0", + "type": "string", + "readOnly": true + }, + "tests": [ + { + "description": "readOnly annotation is dropped", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2020-12/general/0/tests/0/basic", + "$ref": "/draft/2020-12/output/schema", + "properties": { + "errors": { + "items": { + "properties": { + "annotation": false + } + } + }, + "annotations": false + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2020-12/content/readOnly.json b/src/test/suite/output-tests/draft2020-12/content/readOnly.json new file mode 100644 index 000000000..9baf48de2 --- /dev/null +++ b/src/test/suite/output-tests/draft2020-12/content/readOnly.json @@ -0,0 +1,37 @@ +[ + { + "description": "readOnly generates its value as an annotation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/content/draft2020-12/readOnly/0", + "readOnly": true + }, + "tests": [ + { + "description": "readOnly is true", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2020-12/readOnly/0/tests/0/basic", + "$ref": "/draft/2020-12/output/schema", + "properties": { + "annotations": { + "contains": { + "properties": { + "keywordLocation": {"const": "/readOnly"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2020-12/readOnly/0#/readOnly"}, + "instanceLocation": {"const": ""}, + "annotation": {"const": true} + }, + "required": ["keywordLocation", "instanceLocation", "annotation"] + } + }, + "errors": false + }, + "required": ["annotations"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2020-12/content/type.json b/src/test/suite/output-tests/draft2020-12/content/type.json new file mode 100644 index 000000000..2949a6052 --- /dev/null +++ b/src/test/suite/output-tests/draft2020-12/content/type.json @@ -0,0 +1,37 @@ +[ + { + "description": "validating type", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/content/draft2020-12/type/0", + "type": "string", + "anyOf": [ true ] + }, + "tests": [ + { + "description": "incorrect type must be reported, but a message is not required", + "data": 1, + "output": { + "basic": { + "$id": "https://json-schema.org/tests/content/draft2020-12/type/0/tests/0/basic", + "$ref": "/draft/2020-12/output/schema", + "properties": { + "errors": { + "contains": { + "properties": { + "keywordLocation": {"const": "/type"}, + "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2020-12/type/0#/type"}, + "instanceLocation": {"const": ""}, + "annotation": false + }, + "required": ["keywordLocation", "instanceLocation"] + } + } + }, + "required": ["errors"] + } + } + } + ] + } +] diff --git a/src/test/suite/output-tests/draft2020-12/output-schema.json b/src/test/suite/output-tests/draft2020-12/output-schema.json new file mode 100644 index 000000000..1eef288ae --- /dev/null +++ b/src/test/suite/output-tests/draft2020-12/output-schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/output/schema", + "description": "A schema that validates the minimum requirements for validation output", + + "anyOf": [ + { "$ref": "#/$defs/flag" }, + { "$ref": "#/$defs/basic" }, + { "$ref": "#/$defs/detailed" }, + { "$ref": "#/$defs/verbose" } + ], + "$defs": { + "outputUnit":{ + "properties": { + "valid": { "type": "boolean" }, + "keywordLocation": { + "type": "string", + "format": "json-pointer" + }, + "absoluteKeywordLocation": { + "type": "string", + "format": "uri" + }, + "instanceLocation": { + "type": "string", + "format": "json-pointer" + }, + "error": { + "type": "string" + }, + "errors": { + "$ref": "#/$defs/outputUnitArray" + }, + "annotations": { + "$ref": "#/$defs/outputUnitArray" + } + }, + "required": [ "valid", "keywordLocation", "instanceLocation" ], + "allOf": [ + { + "if": { + "properties": { + "valid": { "const": false } + } + }, + "then": { + "anyOf": [ + { + "required": [ "error" ] + }, + { + "required": [ "errors" ] + } + ] + } + }, + { + "if": { + "anyOf": [ + { + "properties": { + "keywordLocation": { + "pattern": "/\\$ref/" + } + } + }, + { + "properties": { + "keywordLocation": { + "pattern": "/\\$dynamicRef/" + } + } + } + ] + }, + "then": { + "required": [ "absoluteKeywordLocation" ] + } + } + ] + }, + "outputUnitArray": { + "type": "array", + "items": { "$ref": "#/$defs/outputUnit" } + }, + "flag": { + "properties": { + "valid": { "type": "boolean" } + }, + "required": [ "valid" ] + }, + "basic": { "$ref": "#/$defs/outputUnit" }, + "detailed": { "$ref": "#/$defs/outputUnit" }, + "verbose": { "$ref": "#/$defs/outputUnit" } + } +} diff --git a/src/test/suite/package.json b/src/test/suite/package.json new file mode 100644 index 000000000..75da9e29b --- /dev/null +++ b/src/test/suite/package.json @@ -0,0 +1,12 @@ +{ + "name": "json-schema-test-suite", + "version": "0.1.0", + "description": "A language agnostic test suite for the JSON Schema specifications", + "repository": "github:json-schema-org/JSON-Schema-Test-Suite", + "keywords": [ + "json-schema", + "tests" + ], + "author": "http://json-schema.org", + "license": "MIT" +} diff --git a/src/test/resources/remotes/baseUriChange/folderInteger.json b/src/test/suite/remotes/baseUriChange/folderInteger.json similarity index 100% rename from src/test/resources/remotes/baseUriChange/folderInteger.json rename to src/test/suite/remotes/baseUriChange/folderInteger.json diff --git a/src/test/resources/remotes/baseUriChangeFolder/folderInteger.json b/src/test/suite/remotes/baseUriChangeFolder/folderInteger.json similarity index 100% rename from src/test/resources/remotes/baseUriChangeFolder/folderInteger.json rename to src/test/suite/remotes/baseUriChangeFolder/folderInteger.json diff --git a/src/test/resources/remotes/baseUriChangeFolderInSubschema/folderInteger.json b/src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json similarity index 100% rename from src/test/resources/remotes/baseUriChangeFolderInSubschema/folderInteger.json rename to src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json diff --git a/src/test/suite/remotes/different-id-ref-string.json b/src/test/suite/remotes/different-id-ref-string.json new file mode 100644 index 000000000..7f8886093 --- /dev/null +++ b/src/test/suite/remotes/different-id-ref-string.json @@ -0,0 +1,5 @@ +{ + "$id": "http://localhost:1234/real-id-ref-string.json", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" +} diff --git a/src/test/suite/remotes/draft-next/baseUriChange/folderInteger.json b/src/test/suite/remotes/draft-next/baseUriChange/folderInteger.json new file mode 100644 index 000000000..388c88108 --- /dev/null +++ b/src/test/suite/remotes/draft-next/baseUriChange/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft-next/baseUriChangeFolder/folderInteger.json b/src/test/suite/remotes/draft-next/baseUriChangeFolder/folderInteger.json new file mode 100644 index 000000000..388c88108 --- /dev/null +++ b/src/test/suite/remotes/draft-next/baseUriChangeFolder/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json b/src/test/suite/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json new file mode 100644 index 000000000..388c88108 --- /dev/null +++ b/src/test/suite/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft-next/detached-dynamicref.json b/src/test/suite/remotes/draft-next/detached-dynamicref.json new file mode 100644 index 000000000..c1a09a583 --- /dev/null +++ b/src/test/suite/remotes/draft-next/detached-dynamicref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft-next/detached-dynamicref.json", + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "foo": { + "$dynamicRef": "#detached" + }, + "detached": { + "$dynamicAnchor": "detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft-next/detached-ref.json b/src/test/suite/remotes/draft-next/detached-ref.json new file mode 100644 index 000000000..d01aaa128 --- /dev/null +++ b/src/test/suite/remotes/draft-next/detached-ref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft-next/detached-ref.json", + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "foo": { + "$ref": "#detached" + }, + "detached": { + "$anchor": "detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft-next/extendible-dynamic-ref.json b/src/test/suite/remotes/draft-next/extendible-dynamic-ref.json new file mode 100644 index 000000000..e787aa3d2 --- /dev/null +++ b/src/test/suite/remotes/draft-next/extendible-dynamic-ref.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "description": "extendible array", + "$id": "http://localhost:1234/draft-next/extendible-dynamic-ref.json", + "type": "object", + "properties": { + "elements": { + "type": "array", + "items": { + "$dynamicRef": "#elements" + } + } + }, + "required": ["elements"], + "additionalProperties": false, + "$defs": { + "elements": { + "$dynamicAnchor": "elements" + } + } +} diff --git a/src/test/suite/remotes/draft-next/format-assertion-false.json b/src/test/suite/remotes/draft-next/format-assertion-false.json new file mode 100644 index 000000000..9cbd2a1de --- /dev/null +++ b/src/test/suite/remotes/draft-next/format-assertion-false.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft-next/format-assertion-false.json", + "$schema": "https://json-schema.org/draft/next/schema", + "$vocabulary": { + "https://json-schema.org/draft/next/vocab/core": true, + "https://json-schema.org/draft/next/vocab/format-assertion": false + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/next/meta/core" }, + { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } + ] +} diff --git a/src/test/suite/remotes/draft-next/format-assertion-true.json b/src/test/suite/remotes/draft-next/format-assertion-true.json new file mode 100644 index 000000000..b3ff69f3a --- /dev/null +++ b/src/test/suite/remotes/draft-next/format-assertion-true.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft-next/format-assertion-true.json", + "$schema": "https://json-schema.org/draft/next/schema", + "$vocabulary": { + "https://json-schema.org/draft/next/vocab/core": true, + "https://json-schema.org/draft/next/vocab/format-assertion": true + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/next/meta/core" }, + { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } + ] +} diff --git a/src/test/suite/remotes/draft-next/integer.json b/src/test/suite/remotes/draft-next/integer.json new file mode 100644 index 000000000..388c88108 --- /dev/null +++ b/src/test/suite/remotes/draft-next/integer.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft-next/locationIndependentIdentifier.json b/src/test/suite/remotes/draft-next/locationIndependentIdentifier.json new file mode 100644 index 000000000..17b4df514 --- /dev/null +++ b/src/test/suite/remotes/draft-next/locationIndependentIdentifier.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$anchor": "foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft-next/metaschema-no-validation.json b/src/test/suite/remotes/draft-next/metaschema-no-validation.json new file mode 100644 index 000000000..90e32a672 --- /dev/null +++ b/src/test/suite/remotes/draft-next/metaschema-no-validation.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/metaschema-no-validation.json", + "$vocabulary": { + "https://json-schema.org/draft/next/vocab/applicator": true, + "https://json-schema.org/draft/next/vocab/core": true + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/next/meta/applicator" }, + { "$ref": "https://json-schema.org/draft/next/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft-next/metaschema-optional-vocabulary.json b/src/test/suite/remotes/draft-next/metaschema-optional-vocabulary.json new file mode 100644 index 000000000..1af0cad4c --- /dev/null +++ b/src/test/suite/remotes/draft-next/metaschema-optional-vocabulary.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/metaschema-optional-vocabulary.json", + "$vocabulary": { + "https://json-schema.org/draft/next/vocab/validation": true, + "https://json-schema.org/draft/next/vocab/core": true, + "http://localhost:1234/draft/next/vocab/custom": false + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/next/meta/validation" }, + { "$ref": "https://json-schema.org/draft/next/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft-next/name-defs.json b/src/test/suite/remotes/draft-next/name-defs.json new file mode 100644 index 000000000..cdb8c0c3a --- /dev/null +++ b/src/test/suite/remotes/draft-next/name-defs.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft-next/nested/foo-ref-string.json b/src/test/suite/remotes/draft-next/nested/foo-ref-string.json new file mode 100644 index 000000000..50bf77f4f --- /dev/null +++ b/src/test/suite/remotes/draft-next/nested/foo-ref-string.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": {"$ref": "string.json"} + } +} diff --git a/src/test/suite/remotes/draft-next/nested/string.json b/src/test/suite/remotes/draft-next/nested/string.json new file mode 100644 index 000000000..7462207e6 --- /dev/null +++ b/src/test/suite/remotes/draft-next/nested/string.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string" +} diff --git a/src/test/suite/remotes/draft-next/ref-and-defs.json b/src/test/suite/remotes/draft-next/ref-and-defs.json new file mode 100644 index 000000000..46fafc9c2 --- /dev/null +++ b/src/test/suite/remotes/draft-next/ref-and-defs.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/ref-and-defs.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" +} diff --git a/src/test/suite/remotes/draft-next/subSchemas.json b/src/test/suite/remotes/draft-next/subSchemas.json new file mode 100644 index 000000000..75b7583ca --- /dev/null +++ b/src/test/suite/remotes/draft-next/subSchemas.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/$defs/integer" + } + } +} diff --git a/src/test/suite/remotes/draft-next/tree.json b/src/test/suite/remotes/draft-next/tree.json new file mode 100644 index 000000000..cad332ad9 --- /dev/null +++ b/src/test/suite/remotes/draft-next/tree.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/next/schema", + "description": "tree schema, extensible", + "$id": "http://localhost:1234/draft-next/tree.json", + "$dynamicAnchor": "node", + + "type": "object", + "properties": { + "data": true, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + } +} diff --git a/src/test/suite/remotes/draft2019-09/baseUriChange/folderInteger.json b/src/test/suite/remotes/draft2019-09/baseUriChange/folderInteger.json new file mode 100644 index 000000000..bf679e80d --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/baseUriChange/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json b/src/test/suite/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json new file mode 100644 index 000000000..bf679e80d --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json b/src/test/suite/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json new file mode 100644 index 000000000..bf679e80d --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2019-09/dependentRequired.json b/src/test/suite/remotes/draft2019-09/dependentRequired.json new file mode 100644 index 000000000..0d691d964 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/dependentRequired.json @@ -0,0 +1,7 @@ +{ + "$id": "http://localhost:1234/draft2019-09/dependentRequired.json", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentRequired": { + "foo": ["bar"] + } +} diff --git a/src/test/suite/remotes/draft2019-09/detached-ref.json b/src/test/suite/remotes/draft2019-09/detached-ref.json new file mode 100644 index 000000000..4a3499fd1 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/detached-ref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft2019-09/detached-ref.json", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "foo": { + "$ref": "#detached" + }, + "detached": { + "$anchor": "detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft2019-09/extendible-dynamic-ref.json b/src/test/suite/remotes/draft2019-09/extendible-dynamic-ref.json new file mode 100644 index 000000000..c11bf0a01 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/extendible-dynamic-ref.json @@ -0,0 +1,21 @@ +{ + "description": "extendible array", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/extendible-dynamic-ref.json", + "type": "object", + "properties": { + "elements": { + "type": "array", + "items": { + "$dynamicRef": "#elements" + } + } + }, + "required": ["elements"], + "additionalProperties": false, + "$defs": { + "elements": { + "$dynamicAnchor": "elements" + } + } +} diff --git a/src/test/suite/remotes/draft2019-09/ignore-prefixItems.json b/src/test/suite/remotes/draft2019-09/ignore-prefixItems.json new file mode 100644 index 000000000..b5ef3928c --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/ignore-prefixItems.json @@ -0,0 +1,7 @@ +{ + "$id": "http://localhost:1234/draft2019-09/ignore-prefixItems.json", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "prefixItems": [ + {"type": "string"} + ] +} diff --git a/src/test/suite/remotes/draft2019-09/integer.json b/src/test/suite/remotes/draft2019-09/integer.json new file mode 100644 index 000000000..bf679e80d --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/integer.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2019-09/locationIndependentIdentifier.json b/src/test/suite/remotes/draft2019-09/locationIndependentIdentifier.json new file mode 100644 index 000000000..a0fb9fbbd --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/locationIndependentIdentifier.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$anchor": "foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft2019-09/metaschema-no-validation.json b/src/test/suite/remotes/draft2019-09/metaschema-no-validation.json new file mode 100644 index 000000000..859006c27 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/metaschema-no-validation.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/metaschema-no-validation.json", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/core": true + }, + "$recursiveAnchor": true, + "allOf": [ + { "$ref": "https://json-schema.org/draft/2019-09/meta/applicator" }, + { "$ref": "https://json-schema.org/draft/2019-09/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft2019-09/metaschema-optional-vocabulary.json b/src/test/suite/remotes/draft2019-09/metaschema-optional-vocabulary.json new file mode 100644 index 000000000..3a7502a21 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/metaschema-optional-vocabulary.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/metaschema-optional-vocabulary.json", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/core": true, + "http://localhost:1234/draft/2019-09/vocab/custom": false + }, + "$recursiveAnchor": true, + "allOf": [ + { "$ref": "https://json-schema.org/draft/2019-09/meta/validation" }, + { "$ref": "https://json-schema.org/draft/2019-09/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft2019-09/name-defs.json b/src/test/suite/remotes/draft2019-09/name-defs.json new file mode 100644 index 000000000..8892f16f8 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/name-defs.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft2019-09/nested/foo-ref-string.json b/src/test/suite/remotes/draft2019-09/nested/foo-ref-string.json new file mode 100644 index 000000000..fe10748c3 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/nested/foo-ref-string.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": {"$ref": "string.json"} + } +} diff --git a/src/test/suite/remotes/draft2019-09/nested/string.json b/src/test/suite/remotes/draft2019-09/nested/string.json new file mode 100644 index 000000000..242f71322 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/nested/string.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" +} diff --git a/src/test/suite/remotes/draft2019-09/ref-and-defs.json b/src/test/suite/remotes/draft2019-09/ref-and-defs.json new file mode 100644 index 000000000..0ad690dd5 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/ref-and-defs.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/ref-and-defs.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" +} diff --git a/src/test/suite/remotes/draft2019-09/subSchemas.json b/src/test/suite/remotes/draft2019-09/subSchemas.json new file mode 100644 index 000000000..fdfee68d9 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/subSchemas.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/$defs/integer" + } + } +} diff --git a/src/test/suite/remotes/draft2019-09/tree.json b/src/test/suite/remotes/draft2019-09/tree.json new file mode 100644 index 000000000..fce794126 --- /dev/null +++ b/src/test/suite/remotes/draft2019-09/tree.json @@ -0,0 +1,17 @@ +{ + "description": "tree schema, extensible", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/tree.json", + "$dynamicAnchor": "node", + + "type": "object", + "properties": { + "data": true, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + } +} diff --git a/src/test/suite/remotes/draft2020-12/baseUriChange/folderInteger.json b/src/test/suite/remotes/draft2020-12/baseUriChange/folderInteger.json new file mode 100644 index 000000000..1f44a6313 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/baseUriChange/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json b/src/test/suite/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json new file mode 100644 index 000000000..1f44a6313 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json b/src/test/suite/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json new file mode 100644 index 000000000..1f44a6313 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2020-12/detached-dynamicref.json b/src/test/suite/remotes/draft2020-12/detached-dynamicref.json new file mode 100644 index 000000000..07cce1dac --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/detached-dynamicref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft2020-12/detached-dynamicref.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "foo": { + "$dynamicRef": "#detached" + }, + "detached": { + "$dynamicAnchor": "detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft2020-12/detached-ref.json b/src/test/suite/remotes/draft2020-12/detached-ref.json new file mode 100644 index 000000000..9c2dca93c --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/detached-ref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft2020-12/detached-ref.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "foo": { + "$ref": "#detached" + }, + "detached": { + "$anchor": "detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft2020-12/extendible-dynamic-ref.json b/src/test/suite/remotes/draft2020-12/extendible-dynamic-ref.json new file mode 100644 index 000000000..65bc0c217 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/extendible-dynamic-ref.json @@ -0,0 +1,21 @@ +{ + "description": "extendible array", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", + "type": "object", + "properties": { + "elements": { + "type": "array", + "items": { + "$dynamicRef": "#elements" + } + } + }, + "required": ["elements"], + "additionalProperties": false, + "$defs": { + "elements": { + "$dynamicAnchor": "elements" + } + } +} diff --git a/src/test/suite/remotes/draft2020-12/format-assertion-false.json b/src/test/suite/remotes/draft2020-12/format-assertion-false.json new file mode 100644 index 000000000..43a711c9d --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/format-assertion-false.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft2020-12/format-assertion-false.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/format-assertion": false + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } + ] +} diff --git a/src/test/suite/remotes/draft2020-12/format-assertion-true.json b/src/test/suite/remotes/draft2020-12/format-assertion-true.json new file mode 100644 index 000000000..39c6b0abf --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/format-assertion-true.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft2020-12/format-assertion-true.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/format-assertion": true + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } + ] +} diff --git a/src/test/suite/remotes/draft2020-12/integer.json b/src/test/suite/remotes/draft2020-12/integer.json new file mode 100644 index 000000000..1f44a6313 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/integer.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" +} diff --git a/src/test/suite/remotes/draft2020-12/locationIndependentIdentifier.json b/src/test/suite/remotes/draft2020-12/locationIndependentIdentifier.json new file mode 100644 index 000000000..6565a1ee0 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/locationIndependentIdentifier.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$anchor": "foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft2020-12/metaschema-no-validation.json b/src/test/suite/remotes/draft2020-12/metaschema-no-validation.json new file mode 100644 index 000000000..71be8b5da --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/metaschema-no-validation.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/metaschema-no-validation.json", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft2020-12/metaschema-optional-vocabulary.json b/src/test/suite/remotes/draft2020-12/metaschema-optional-vocabulary.json new file mode 100644 index 000000000..a6963e548 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/metaschema-optional-vocabulary.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/metaschema-optional-vocabulary.json", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/core": true, + "http://localhost:1234/draft/2020-12/vocab/custom": false + }, + "$dynamicAnchor": "meta", + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/validation" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } + ] +} diff --git a/src/test/suite/remotes/draft2020-12/name-defs.json b/src/test/suite/remotes/draft2020-12/name-defs.json new file mode 100644 index 000000000..67bc33c51 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/name-defs.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft2020-12/nested/foo-ref-string.json b/src/test/suite/remotes/draft2020-12/nested/foo-ref-string.json new file mode 100644 index 000000000..29661ff9f --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/nested/foo-ref-string.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": {"$ref": "string.json"} + } +} diff --git a/src/test/suite/remotes/draft2020-12/nested/string.json b/src/test/suite/remotes/draft2020-12/nested/string.json new file mode 100644 index 000000000..6607ac534 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/nested/string.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" +} diff --git a/src/test/suite/remotes/draft2020-12/prefixItems.json b/src/test/suite/remotes/draft2020-12/prefixItems.json new file mode 100644 index 000000000..acd8293c6 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/prefixItems.json @@ -0,0 +1,7 @@ +{ + "$id": "http://localhost:1234/draft2020-12/prefixItems.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {"type": "string"} + ] +} diff --git a/src/test/suite/remotes/draft2020-12/ref-and-defs.json b/src/test/suite/remotes/draft2020-12/ref-and-defs.json new file mode 100644 index 000000000..16d30fa3a --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/ref-and-defs.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/ref-and-defs.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" +} diff --git a/src/test/suite/remotes/draft2020-12/subSchemas.json b/src/test/suite/remotes/draft2020-12/subSchemas.json new file mode 100644 index 000000000..1bb4846d7 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/subSchemas.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/$defs/integer" + } + } +} diff --git a/src/test/suite/remotes/draft2020-12/tree.json b/src/test/suite/remotes/draft2020-12/tree.json new file mode 100644 index 000000000..b07555fb3 --- /dev/null +++ b/src/test/suite/remotes/draft2020-12/tree.json @@ -0,0 +1,17 @@ +{ + "description": "tree schema, extensible", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/tree.json", + "$dynamicAnchor": "node", + + "type": "object", + "properties": { + "data": true, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + } +} diff --git a/src/test/suite/remotes/draft3/subSchemas.json b/src/test/suite/remotes/draft3/subSchemas.json new file mode 100644 index 000000000..6e9b3de35 --- /dev/null +++ b/src/test/suite/remotes/draft3/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/suite/remotes/draft4/locationIndependentIdentifier.json b/src/test/suite/remotes/draft4/locationIndependentIdentifier.json new file mode 100644 index 000000000..eeff1eb23 --- /dev/null +++ b/src/test/suite/remotes/draft4/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft4/name.json b/src/test/suite/remotes/draft4/name.json new file mode 100644 index 000000000..fceacb809 --- /dev/null +++ b/src/test/suite/remotes/draft4/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft4/subSchemas.json b/src/test/suite/remotes/draft4/subSchemas.json new file mode 100644 index 000000000..6e9b3de35 --- /dev/null +++ b/src/test/suite/remotes/draft4/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/suite/remotes/draft6/detached-ref.json b/src/test/suite/remotes/draft6/detached-ref.json new file mode 100644 index 000000000..05ce071ba --- /dev/null +++ b/src/test/suite/remotes/draft6/detached-ref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft6/detached-ref.json", + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "foo": { + "$ref": "#detached" + }, + "detached": { + "$id": "#detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft6/locationIndependentIdentifier.json b/src/test/suite/remotes/draft6/locationIndependentIdentifier.json new file mode 100644 index 000000000..e72815cd5 --- /dev/null +++ b/src/test/suite/remotes/draft6/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft6/name.json b/src/test/suite/remotes/draft6/name.json new file mode 100644 index 000000000..fceacb809 --- /dev/null +++ b/src/test/suite/remotes/draft6/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft6/ref-and-definitions.json b/src/test/suite/remotes/draft6/ref-and-definitions.json new file mode 100644 index 000000000..b80deeb7b --- /dev/null +++ b/src/test/suite/remotes/draft6/ref-and-definitions.json @@ -0,0 +1,11 @@ +{ + "$id": "http://localhost:1234/draft6/ref-and-definitions.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] +} diff --git a/src/test/suite/remotes/draft6/subSchemas.json b/src/test/suite/remotes/draft6/subSchemas.json new file mode 100644 index 000000000..6e9b3de35 --- /dev/null +++ b/src/test/suite/remotes/draft6/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/suite/remotes/draft7/detached-ref.json b/src/test/suite/remotes/draft7/detached-ref.json new file mode 100644 index 000000000..27f2ec80a --- /dev/null +++ b/src/test/suite/remotes/draft7/detached-ref.json @@ -0,0 +1,13 @@ +{ + "$id": "http://localhost:1234/draft7/detached-ref.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "foo": { + "$ref": "#detached" + }, + "detached": { + "$id": "#detached", + "type": "integer" + } + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft7/ignore-dependentRequired.json b/src/test/suite/remotes/draft7/ignore-dependentRequired.json new file mode 100644 index 000000000..0ea927b50 --- /dev/null +++ b/src/test/suite/remotes/draft7/ignore-dependentRequired.json @@ -0,0 +1,7 @@ +{ + "$id": "http://localhost:1234/draft7/integer.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "dependentRequired": { + "foo": ["bar"] + } +} \ No newline at end of file diff --git a/src/test/suite/remotes/draft7/locationIndependentIdentifier.json b/src/test/suite/remotes/draft7/locationIndependentIdentifier.json new file mode 100644 index 000000000..e72815cd5 --- /dev/null +++ b/src/test/suite/remotes/draft7/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/draft7/name.json b/src/test/suite/remotes/draft7/name.json new file mode 100644 index 000000000..fceacb809 --- /dev/null +++ b/src/test/suite/remotes/draft7/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/draft7/ref-and-definitions.json b/src/test/suite/remotes/draft7/ref-and-definitions.json new file mode 100644 index 000000000..d5929380c --- /dev/null +++ b/src/test/suite/remotes/draft7/ref-and-definitions.json @@ -0,0 +1,11 @@ +{ + "$id": "http://localhost:1234/draft7/ref-and-definitions.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] +} diff --git a/src/test/suite/remotes/draft7/subSchemas.json b/src/test/suite/remotes/draft7/subSchemas.json new file mode 100644 index 000000000..6e9b3de35 --- /dev/null +++ b/src/test/suite/remotes/draft7/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/suite/remotes/extendible-dynamic-ref.json b/src/test/suite/remotes/extendible-dynamic-ref.json new file mode 100644 index 000000000..d0bcd37d3 --- /dev/null +++ b/src/test/suite/remotes/extendible-dynamic-ref.json @@ -0,0 +1,20 @@ +{ + "description": "extendible array", + "$id": "http://localhost:1234/extendible-dynamic-ref.json", + "type": "object", + "properties": { + "elements": { + "type": "array", + "items": { + "$dynamicRef": "#elements" + } + } + }, + "required": ["elements"], + "additionalProperties": false, + "$defs": { + "elements": { + "$dynamicAnchor": "elements" + } + } +} diff --git a/src/test/suite/remotes/integer.json b/src/test/suite/remotes/integer.json new file mode 100644 index 000000000..8b50ea308 --- /dev/null +++ b/src/test/suite/remotes/integer.json @@ -0,0 +1,3 @@ +{ + "type": "integer" +} diff --git a/src/test/suite/remotes/locationIndependentIdentifier.json b/src/test/suite/remotes/locationIndependentIdentifier.json new file mode 100644 index 000000000..96b17c3f1 --- /dev/null +++ b/src/test/suite/remotes/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "$defs": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$anchor": "foo", + "type": "integer" + } + } +} diff --git a/src/test/suite/remotes/name-defs.json b/src/test/suite/remotes/name-defs.json new file mode 100644 index 000000000..1dab4a434 --- /dev/null +++ b/src/test/suite/remotes/name-defs.json @@ -0,0 +1,15 @@ +{ + "$defs": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/suite/remotes/nested-absolute-ref-to-string.json b/src/test/suite/remotes/nested-absolute-ref-to-string.json new file mode 100644 index 000000000..f46c76164 --- /dev/null +++ b/src/test/suite/remotes/nested-absolute-ref-to-string.json @@ -0,0 +1,9 @@ +{ + "$defs": { + "bar": { + "$id": "http://localhost:1234/the-nested-id.json", + "type": "string" + } + }, + "$ref": "http://localhost:1234/the-nested-id.json" +} diff --git a/src/test/suite/remotes/nested/foo-ref-string.json b/src/test/suite/remotes/nested/foo-ref-string.json new file mode 100644 index 000000000..9cd2527ed --- /dev/null +++ b/src/test/suite/remotes/nested/foo-ref-string.json @@ -0,0 +1,6 @@ +{ + "type": "object", + "properties": { + "foo": {"$ref": "string.json"} + } +} diff --git a/src/test/suite/remotes/nested/string.json b/src/test/suite/remotes/nested/string.json new file mode 100644 index 000000000..c2d48c068 --- /dev/null +++ b/src/test/suite/remotes/nested/string.json @@ -0,0 +1,3 @@ +{ + "type": "string" +} diff --git a/src/test/suite/remotes/ref-and-defs.json b/src/test/suite/remotes/ref-and-defs.json new file mode 100644 index 000000000..85d06c399 --- /dev/null +++ b/src/test/suite/remotes/ref-and-defs.json @@ -0,0 +1,11 @@ +{ + "$id": "http://localhost:1234/ref-and-defs.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" +} diff --git a/src/test/suite/remotes/tree.json b/src/test/suite/remotes/tree.json new file mode 100644 index 000000000..a12d98b07 --- /dev/null +++ b/src/test/suite/remotes/tree.json @@ -0,0 +1,16 @@ +{ + "description": "tree schema, extensible", + "$id": "http://localhost:1234/tree.json", + "$dynamicAnchor": "node", + + "type": "object", + "properties": { + "data": true, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + } +} diff --git a/src/test/suite/remotes/urn-ref-string.json b/src/test/suite/remotes/urn-ref-string.json new file mode 100644 index 000000000..aca2211b7 --- /dev/null +++ b/src/test/suite/remotes/urn-ref-string.json @@ -0,0 +1,5 @@ +{ + "$id": "urn:uuid:feebdaed-ffff-0000-ffff-0000deadbeef", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" +} diff --git a/src/test/suite/test-schema.json b/src/test/suite/test-schema.json new file mode 100644 index 000000000..0087c5e3d --- /dev/null +++ b/src/test/suite/test-schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/tests/test-schema", + "description": "A schema for files contained within this suite", + + "type": "array", + "minItems": 1, + "items": { + "description": "An individual test case, containing multiple tests of a single schema's behavior", + + "type": "object", + "required": [ "description", "schema", "tests" ], + "properties": { + "description": { + "description": "The test case description", + "type": "string" + }, + "comment": { + "description": "Any additional comments about the test case", + "type": "string" + }, + "schema": { + "description": "A valid JSON Schema (one written for the corresponding version directory that the file sits within)." + }, + "tests": { + "description": "A set of related tests all using the same schema", + "type": "array", + "items": { "$ref": "#/$defs/test" }, + "minItems": 1 + }, + "specification":{ + "description": "A reference to a specification document which defines the behavior tested by this test case. Typically this should be a JSON Schema specification document, though in cases where the JSON Schema specification points to another RFC it should contain *both* the portion of the JSON Schema specification which indicates what RFC (and section) to follow as *well* as information on where in that specification the behavior is specified.", + + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items":{ + "properties": { + "core": { + "description": "A section in official JSON Schema core drafts", + "url": "https://json-schema.org/specification-links", + "pattern": "^[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)*$", + "type":"string" + }, + "validation": { + "description": "A section in official JSON Schema validation drafts", + "url": "https://json-schema.org/specification-links", + "pattern": "^[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)*$", + "type":"string" + }, + "ecma262": { + "description": "A section in official ECMA 262 specification for defining regular expressions", + "url": "https://262.ecma-international.org/", + "pattern": "^[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)*$", + "type":"string" + }, + "perl5": { + "description": "A section name in Perl documentation for defining regular expressions", + "url": "https://perldoc.perl.org/perlre", + "type":"string" + }, + "quote": { + "description": "Quote describing the test case", + "type":"string" + } + }, + "patternProperties": { + "^rfc\\d+$": { + "description": "A section in official RFC for the given rfc number", + "url": "https://www.rfc-editor.org/", + "pattern": "^[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)*$", + "type":"string" + }, + "^iso\\d+$": { + "description": "A section in official ISO for the given iso number", + "pattern": "^[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)*$", + "type": "string" + } + }, + "additionalProperties": { "type": "string" }, + "minProperties": 1, + "propertyNames": { + "oneOf": [ + { + "pattern": "^((iso)|(rfc))[0-9]+$" + }, + { + "enum": [ "core", "validation", "ecma262", "perl5", "quote" ] + } + ] + } + } + } + }, + "additionalProperties": false + }, + + "$defs": { + "test": { + "description": "A single test", + + "type": "object", + "required": [ "description", "data", "valid" ], + "properties": { + "description": { + "description": "The test description, briefly explaining which behavior it exercises", + "type": "string" + }, + "comment": { + "description": "Any additional comments about the test", + "type": "string" + }, + "data": { + "description": "The instance which should be validated against the schema in \"schema\"." + }, + "valid": { + "description": "Whether the validation process of this instance should consider the instance valid or not", + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/src/test/suite/tests/draft-next/additionalProperties.json b/src/test/suite/tests/draft-next/additionalProperties.json new file mode 100644 index 000000000..51b0edada --- /dev/null +++ b/src/test/suite/tests/draft-next/additionalProperties.json @@ -0,0 +1,248 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"foo": {}, "bar": {}} + }, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "additionalProperties with propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "propertyNames": { + "maxLength": 5 + }, + "additionalProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "Valid against both keywords", + "data": { "apple": 4 }, + "valid": true + }, + { + "description": "Valid against propertyNames, but not additionalProperties", + "data": { "fig": 2, "pear": "available" }, + "valid": false + } + ] + }, + { + "description": "propertyDependencies with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties" : {"foo2" : {}}, + "propertyDependencies": { + "foo" : {}, + "foo2": { + "bar": { + "properties": { + "buz": {} + } + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "additionalProperties doesn't consider propertyDependencies properties" , + "data": {"foo": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see buz even when foo2 is present", + "data": {"foo2": "bar", "buz": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see buz", + "data": {"buz": ""}, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo": {}, + "foo2": { + "properties": { + "bar": {} + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "additionalProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar even when foo2 is present", + "data": {"foo2": "", "bar": ""}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/allOf.json b/src/test/suite/tests/draft-next/allOf.json new file mode 100644 index 000000000..86745da2a --- /dev/null +++ b/src/test/suite/tests/draft-next/allOf.json @@ -0,0 +1,312 @@ +[ + { + "description": "allOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/anchor.json b/src/test/suite/tests/draft-next/anchor.json new file mode 100644 index 000000000..84d4851ca --- /dev/null +++ b/src/test/suite/tests/draft-next/anchor.json @@ -0,0 +1,120 @@ +[ + { + "description": "Location-independent identifier", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "#foo", + "$defs": { + "A": { + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with absolute URI", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/bar#foo", + "$defs": { + "A": { + "$id": "http://localhost:1234/draft-next/bar", + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/root", + "$ref": "http://localhost:1234/draft-next/nested.json#foo", + "$defs": { + "A": { + "$id": "nested.json", + "$defs": { + "B": { + "$anchor": "foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "same $anchor with different base uri", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/foobar", + "$defs": { + "A": { + "$id": "child1", + "allOf": [ + { + "$id": "child2", + "$anchor": "my_anchor", + "type": "number" + }, + { + "$anchor": "my_anchor", + "type": "string" + } + ] + } + }, + "$ref": "child1#my_anchor" + }, + "tests": [ + { + "description": "$ref resolves to /$defs/A/allOf/1", + "data": "a", + "valid": true + }, + { + "description": "$ref does not resolve to /$defs/A/allOf/0", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/anyOf.json b/src/test/suite/tests/draft-next/anyOf.json new file mode 100644 index 000000000..a97267c8e --- /dev/null +++ b/src/test/suite/tests/draft-next/anyOf.json @@ -0,0 +1,203 @@ +[ + { + "description": "anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [true, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/boolean_schema.json b/src/test/suite/tests/draft-next/boolean_schema.json new file mode 100644 index 000000000..6d40f23f2 --- /dev/null +++ b/src/test/suite/tests/draft-next/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/const.json b/src/test/suite/tests/draft-next/const.json new file mode 100644 index 000000000..61fbd8390 --- /dev/null +++ b/src/test/suite/tests/draft-next/const.json @@ -0,0 +1,387 @@ +[ + { + "description": "const validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": 2 + }, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": {"foo": "bar", "baz": "bax"} + }, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": [{ "foo": "bar" }] + }, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": null + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": false + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": true + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": [false] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": [true] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": {"a": false} + }, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": {"a": true} + }, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": 0 + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": 1 + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": -2.0 + }, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": 9007199254740992 + }, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "const": "hello\u0000there" + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/contains.json b/src/test/suite/tests/draft-next/contains.json new file mode 100644 index 000000000..8539a531d --- /dev/null +++ b/src/test/suite/tests/draft-next/contains.json @@ -0,0 +1,197 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "minimum": 5 } + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array or object is valid", + "data": 42, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": true + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid - string", + "data": "contains does not apply to strings", + "valid": true + }, + { + "description": "non-arrays are valid - object", + "data": {}, + "valid": true + }, + { + "description": "non-arrays are valid - number", + "data": 42, + "valid": true + }, + { + "description": "non-arrays are valid - boolean", + "data": false, + "valid": true + }, + { + "description": "non-arrays are valid - null", + "data": null, + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "additionalProperties": { "multipleOf": 2 }, + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [2, 4, 8], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [3, 6, 9], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [6, 12], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [1, 5], + "valid": false + } + ] + }, + { + "description": "contains with false if subschema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { + "if": false, + "else": true + } + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/content.json b/src/test/suite/tests/draft-next/content.json new file mode 100644 index 000000000..37e1f0951 --- /dev/null +++ b/src/test/suite/tests/draft-next/content.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of string-encoded content based on media type", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contentMediaType": "application/json" + }, + "tests": [ + { + "description": "a valid JSON document", + "data": "{\"foo\": \"bar\"}", + "valid": true + }, + { + "description": "an invalid JSON document; validates true", + "data": "{:}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary string-encoding", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64 string", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "an invalid base64 string (% is not a valid character); validates true", + "data": "eyJmb28iOi%iYmFyIn0K", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents with schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64", + "contentSchema": { "type": "object", "required": ["foo"], "properties": { "foo": { "type": "string" } } } + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "another valid base64-encoded JSON document", + "data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==", + "valid": true + }, + { + "description": "an invalid base64-encoded JSON document; validates true", + "data": "eyJib28iOiAyMH0=", + "valid": true + }, + { + "description": "an empty object as a base64-encoded JSON document; validates true", + "data": "e30=", + "valid": true + }, + { + "description": "an empty array as a base64-encoded JSON document", + "data": "W10=", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/default.json b/src/test/suite/tests/draft-next/default.json new file mode 100644 index 000000000..689b68e6a --- /dev/null +++ b/src/test/suite/tests/draft-next/default.json @@ -0,0 +1,82 @@ +[ + { + "description": "invalid type for default", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/defs.json b/src/test/suite/tests/draft-next/defs.json new file mode 100644 index 000000000..7bda8257c --- /dev/null +++ b/src/test/suite/tests/draft-next/defs.json @@ -0,0 +1,21 @@ +[ + { + "description": "validate definition against metaschema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "https://json-schema.org/draft/next/schema" + }, + "tests": [ + { + "description": "valid definition schema", + "data": {"$defs": {"foo": {"type": "integer"}}}, + "valid": true + }, + { + "description": "invalid definition schema", + "data": {"$defs": {"foo": {"type": 1}}}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/dependentRequired.json b/src/test/suite/tests/draft-next/dependentRequired.json new file mode 100644 index 000000000..23447242c --- /dev/null +++ b/src/test/suite/tests/draft-next/dependentRequired.json @@ -0,0 +1,152 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentRequired": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentRequired": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentRequired": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentRequired": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/dependentSchemas.json b/src/test/suite/tests/draft-next/dependentSchemas.json new file mode 100644 index 000000000..86079c34c --- /dev/null +++ b/src/test/suite/tests/draft-next/dependentSchemas.json @@ -0,0 +1,171 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentSchemas": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentSchemas": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependentSchemas": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {} + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/dynamicRef.json b/src/test/suite/tests/draft-next/dynamicRef.json new file mode 100644 index 000000000..30821c5b1 --- /dev/null +++ b/src/test/suite/tests/draft-next/dynamicRef.json @@ -0,0 +1,701 @@ +[ + { + "description": "A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$ref": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root", + "$ref": "intermediate-scope", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "intermediate-scope": { + "$id": "intermediate-scope", + "$ref": "list" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-ignores-anchors/root", + "$ref": "list", + "$defs": { + "foo": { + "$anchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/relative-dynamic-reference/root", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "foo": { "const": "pass" } + }, + "$ref": "extended", + "$defs": { + "extended": { + "$id": "extended", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "bar": { "$ref": "bar" } + } + }, + "bar": { + "$id": "bar", + "type": "object", + "properties": { + "baz": { "$dynamicRef": "extended#meta" } + } + } + } + }, + "tests": [ + { + "description": "The recursive part is valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "pass" } + } + }, + "valid": true + }, + { + "description": "The recursive part is not valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "fail" } + } + }, + "valid": false + } + ] + }, + { + "description": "multiple dynamic paths to the $dynamicRef keyword", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main", + "propertyDependencies": { + "kindOfList": { + "numbers": { "$ref": "numberList" }, + "strings": { "$ref": "stringList" } + } + }, + "$defs": { + "genericList": { + "$id": "genericList", + "properties": { + "list": { + "items": { "$dynamicRef": "#itemType" } + } + } + }, + "numberList": { + "$id": "numberList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "number" + } + }, + "$ref": "genericList" + }, + "stringList": { + "$id": "stringList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "string" + } + }, + "$ref": "genericList" + } + } + }, + "tests": [ + { + "description": "number list with number values", + "data": { + "kindOfList": "numbers", + "list": [1.1] + }, + "valid": true + }, + { + "description": "number list with string values", + "data": { + "kindOfList": "numbers", + "list": ["foo"] + }, + "valid": false + }, + { + "description": "string list with number values", + "data": { + "kindOfList": "strings", + "list": [1.1] + }, + "valid": false + }, + { + "description": "string list with string values", + "data": { + "kindOfList": "strings", + "list": ["foo"] + }, + "valid": true + } + ] + }, + { + "description": "after leaving a dynamic scope, it is not used by a $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main", + "if": { + "$id": "first_scope", + "$defs": { + "thingy": { + "$comment": "this is first_scope#thingy", + "$dynamicAnchor": "thingy", + "type": "number" + } + } + }, + "then": { + "$id": "second_scope", + "$ref": "start", + "$defs": { + "thingy": { + "$comment": "this is second_scope#thingy, the final destination of the $dynamicRef", + "$dynamicAnchor": "thingy", + "type": "null" + } + } + }, + "$defs": { + "start": { + "$comment": "this is the landing spot from $ref", + "$id": "start", + "$dynamicRef": "inner_scope#thingy" + }, + "thingy": { + "$comment": "this is the first stop for the $dynamicRef", + "$id": "inner_scope", + "$dynamicAnchor": "thingy", + "type": "string" + } + } + }, + "tests": [ + { + "description": "string matches /$defs/thingy, but the $dynamicRef does not stop here", + "data": "a string", + "valid": false + }, + { + "description": "first_scope is not in dynamic scope for the $dynamicRef", + "data": 42, + "valid": false + }, + { + "description": "/then/$defs/thingy is the final stop for the $dynamicRef", + "data": null, + "valid": true + } + ] + }, + { + "description": "strict-tree schema, guards against misspelled properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/strict-tree.json", + "$dynamicAnchor": "node", + + "$ref": "tree.json", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "instance with misspelled field", + "data": { + "children": [{ + "daat": 1 + }] + }, + "valid": false + }, + { + "description": "instance with correct field", + "data": { + "children": [{ + "data": 1 + }] + }, + "valid": true + } + ] + }, + { + "description": "tests for implementation dynamic anchor and reference link", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/strict-extendible.json", + "$ref": "extendible-dynamic-ref.json", + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $defs first", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/strict-extendible-allof-defs-first.json", + "allOf": [ + { + "$ref": "extendible-dynamic-ref.json" + }, + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $ref first", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/strict-extendible-allof-ref-first.json", + "allOf": [ + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + { + "$ref": "extendible-dynamic-ref.json" + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$dynamicAnchor inside propertyDependencies", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/dynamicanchor-in-propertydependencies.json", + "$defs": { + "inner": { + "$id": "inner", + "$dynamicAnchor": "foo", + "type": "object", + "properties": { + "expectedTypes": { + "type": "string" + } + }, + "additionalProperties": { + "$dynamicRef": "#foo" + } + } + }, + "propertyDependencies": { + "expectedTypes": { + "strings": { + "$id": "east", + "$ref": "inner", + "$defs": { + "foo": { + "$dynamicAnchor": "foo", + "type": "string" + } + } + }, + "integers": { + "$id": "west", + "$ref": "inner", + "$defs": { + "foo": { + "$dynamicAnchor": "foo", + "type": "integer" + } + } + } + } + } + }, + "tests": [ + { + "description": "expected strings - additional property as string is valid", + "data": { + "expectedTypes": "strings", + "anotherProperty": "also a string" + }, + "valid": true + }, + { + "description": "expected strings - additional property as not string is invalid", + "data": { + "expectedTypes": "strings", + "anotherProperty": 42 + }, + "valid": false + }, + { + "description": "expected integers - additional property as integer is valid", + "data": { + "expectedTypes": "integers", + "anotherProperty": 42 + }, + "valid": true + }, + { + "description": "expected integers - additional property as not integer is invalid", + "data": { + "expectedTypes": "integers", + "anotherProperty": "a string" + }, + "valid": false + } + ] + }, + { + "description": "$ref to $dynamicRef finds detached $dynamicAnchor", + "schema": { + "$ref": "http://localhost:1234/draft-next/detached-dynamicref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$dynamicRef points to a boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "true": true, + "false": false + }, + "properties": { + "true": { + "$dynamicRef": "#/$defs/true" + }, + "false": { + "$dynamicRef": "#/$defs/false" + } + } + }, + "tests": [ + { + "description": "follow $dynamicRef to a true schema", + "data": { "true": 1 }, + "valid": true + }, + { + "description": "follow $dynamicRef to a false schema", + "data": { "false": 1 }, + "valid": false + } + ] + }, + { + "description": "$dynamicRef skips over intermediate resources - direct reference", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main", + "type": "object", + "properties": { + "bar-item": { + "$ref": "item" + } + }, + "$defs": { + "bar": { + "$id": "bar", + "type": "array", + "items": { + "$ref": "item" + }, + "$defs": { + "item": { + "$id": "item", + "type": "object", + "properties": { + "content": { + "$dynamicRef": "#content" + } + }, + "$defs": { + "defaultContent": { + "$dynamicAnchor": "content", + "type": "integer" + } + } + }, + "content": { + "$dynamicAnchor": "content", + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "integer property passes", + "data": { "bar-item": { "content": 42 } }, + "valid": true + }, + { + "description": "string property fails", + "data": { "bar-item": { "content": "value" } }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/enum.json b/src/test/suite/tests/draft-next/enum.json new file mode 100644 index 000000000..e263f3901 --- /dev/null +++ b/src/test/suite/tests/draft-next/enum.json @@ -0,0 +1,358 @@ +[ + { + "description": "simple enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [1, 2, 3] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [6, "foo", [], true, {"foo": 12}] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [6, null] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [false] + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [[false]] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [true] + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [[true]] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [0] + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [[0]] + }, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [1] + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [[1]] + }, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "enum": [ "hello\u0000there" ] + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/exclusiveMaximum.json b/src/test/suite/tests/draft-next/exclusiveMaximum.json new file mode 100644 index 000000000..6ec4752d1 --- /dev/null +++ b/src/test/suite/tests/draft-next/exclusiveMaximum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/exclusiveMinimum.json b/src/test/suite/tests/draft-next/exclusiveMinimum.json new file mode 100644 index 000000000..90262987d --- /dev/null +++ b/src/test/suite/tests/draft-next/exclusiveMinimum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/format.json b/src/test/suite/tests/draft-next/format.json new file mode 100644 index 000000000..ec6c7f1dd --- /dev/null +++ b/src/test/suite/tests/draft-next/format.json @@ -0,0 +1,838 @@ +[ + { + "description": "email format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "idn-email format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid regex string is only an annotation by default", + "data": "^(abc]", + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv4 string is only an annotation by default", + "data": "127.0.0.0.1", + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv6 string is only an annotation by default", + "data": "12345::", + "valid": true + } + ] + }, + { + "description": "idn-hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-hostname string is only an annotation by default", + "data": "〮실례.테스트", + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid hostname string is only an annotation by default", + "data": "-a-host-name-that-starts-with--", + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date string is only an annotation by default", + "data": "06/19/1963", + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date-time string is only an annotation by default", + "data": "1990-02-31T15:59:60.123-08:00", + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid time string is only an annotation by default", + "data": "08:30:06 PST", + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid json-pointer string is only an annotation by default", + "data": "/foo/bar~", + "valid": true + } + ] + }, + { + "description": "relative-json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid relative-json-pointer string is only an annotation by default", + "data": "/foo/bar", + "valid": true + } + ] + }, + { + "description": "iri format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri string is only an annotation by default", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": true + } + ] + }, + { + "description": "iri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\filëßåré", + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri string is only an annotation by default", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\fileshare", + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-template string is only an annotation by default", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": true + } + ] + }, + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uuid string is only an annotation by default", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": true + } + ] + }, + { + "description": "duration format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid duration string is only an annotation by default", + "data": "PT1D", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/if-then-else.json b/src/test/suite/tests/draft-next/if-then-else.json new file mode 100644 index 000000000..70576c42e --- /dev/null +++ b/src/test/suite/tests/draft-next/if-then-else.json @@ -0,0 +1,268 @@ +[ + { + "description": "ignore if without then or else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone if", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone if", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore then without if", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "then": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone then", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone then", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore else without if", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "else": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone else", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone else", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "if and then without else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid when if test fails", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if and else without then", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid when if test passes", + "data": -1, + "valid": true + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "validate against correct branch, then vs else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "non-interference across combined schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] + }, + "tests": [ + { + "description": "valid, but would have been invalid through then", + "data": -100, + "valid": true + }, + { + "description": "valid, but would have been invalid through else", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": true, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema true in if always chooses the then path (valid)", + "data": "then", + "valid": true + }, + { + "description": "boolean schema true in if always chooses the then path (invalid)", + "data": "else", + "valid": false + } + ] + }, + { + "description": "if with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": false, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema false in if always chooses the else path (invalid)", + "data": "then", + "valid": false + }, + { + "description": "boolean schema false in if always chooses the else path (valid)", + "data": "else", + "valid": true + } + ] + }, + { + "description": "if appears at the end when serialized (keyword processing sequence)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "then": { "const": "yes" }, + "else": { "const": "other" }, + "if": { "maxLength": 4 } + }, + "tests": [ + { + "description": "yes redirects to then and passes", + "data": "yes", + "valid": true + }, + { + "description": "other redirects to else and passes", + "data": "other", + "valid": true + }, + { + "description": "no redirects to then and fails", + "data": "no", + "valid": false + }, + { + "description": "invalid redirects to else and fails", + "data": "invalid", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/infinite-loop-detection.json b/src/test/suite/tests/draft-next/infinite-loop-detection.json new file mode 100644 index 000000000..fa66743bc --- /dev/null +++ b/src/test/suite/tests/draft-next/infinite-loop-detection.json @@ -0,0 +1,37 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/$defs/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/$defs/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/items.json b/src/test/suite/tests/draft-next/items.json new file mode 100644 index 000000000..dfb79af2f --- /dev/null +++ b/src/test/suite/tests/draft-next/items.json @@ -0,0 +1,304 @@ +[ + { + "description": "a schema given for items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": true + }, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "item": { + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/sub-item" }, + { "$ref": "#/$defs/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "prefixItems with no additional items allowed", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{}, {}, {}], + "items": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "items does not look in applicators, valid case", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { "prefixItems": [ { "minimum": 3 } ] } + ], + "items": { "minimum": 5 } + }, + "tests": [ + { + "description": "prefixItems in allOf does not constrain items, invalid case", + "data": [ 3, 5 ], + "valid": false + }, + { + "description": "prefixItems in allOf does not constrain items, valid case", + "data": [ 5, 5 ], + "valid": true + } + ] + }, + { + "description": "prefixItems validation adjusts the starting index for items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ { "type": "string" } ], + "items": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "items with heterogeneous array", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{}], + "items": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "items with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/maxContains.json b/src/test/suite/tests/draft-next/maxContains.json new file mode 100644 index 000000000..5af6e4c13 --- /dev/null +++ b/src/test/suite/tests/draft-next/maxContains.json @@ -0,0 +1,102 @@ +[ + { + "description": "maxContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone maxContains", + "data": [1], + "valid": true + }, + { + "description": "two items still valid against lone maxContains", + "data": [1, 2], + "valid": true + } + ] + }, + { + "description": "maxContains with contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty array", + "data": [], + "valid": false + }, + { + "description": "all elements match, valid maxContains", + "data": [1], + "valid": true + }, + { + "description": "all elements match, invalid maxContains", + "data": [1, 1], + "valid": false + }, + { + "description": "some elements match, valid maxContains", + "data": [1, 2], + "valid": true + }, + { + "description": "some elements match, invalid maxContains", + "data": [1, 2, 1], + "valid": false + } + ] + }, + { + "description": "maxContains with contains, value with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": {"const": 1}, + "maxContains": 1.0 + }, + "tests": [ + { + "description": "one element matches, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains < maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "minContains": 1, + "maxContains": 3 + }, + "tests": [ + { + "description": "array with actual < minContains < maxContains", + "data": [], + "valid": false + }, + { + "description": "array with minContains < actual < maxContains", + "data": [1, 1], + "valid": true + }, + { + "description": "array with minContains < maxContains < actual", + "data": [1, 1, 1, 1], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/maxItems.json b/src/test/suite/tests/draft-next/maxItems.json new file mode 100644 index 000000000..b215850fb --- /dev/null +++ b/src/test/suite/tests/draft-next/maxItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "maxItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxItems": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxItems": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/maxLength.json b/src/test/suite/tests/draft-next/maxLength.json new file mode 100644 index 000000000..c88f604ef --- /dev/null +++ b/src/test/suite/tests/draft-next/maxLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "maxLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxLength": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxLength": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/maxProperties.json b/src/test/suite/tests/draft-next/maxProperties.json new file mode 100644 index 000000000..5eec9dadb --- /dev/null +++ b/src/test/suite/tests/draft-next/maxProperties.json @@ -0,0 +1,79 @@ +[ + { + "description": "maxProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxProperties": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxProperties": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maxProperties": 0 + }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/maximum.json b/src/test/suite/tests/draft-next/maximum.json new file mode 100644 index 000000000..656ce8176 --- /dev/null +++ b/src/test/suite/tests/draft-next/maximum.json @@ -0,0 +1,60 @@ +[ + { + "description": "maximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maximum": 300 + }, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/minContains.json b/src/test/suite/tests/draft-next/minContains.json new file mode 100644 index 000000000..a6ad212d5 --- /dev/null +++ b/src/test/suite/tests/draft-next/minContains.json @@ -0,0 +1,224 @@ +[ + { + "description": "minContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone minContains", + "data": [1], + "valid": true + }, + { + "description": "zero items still valid against lone minContains", + "data": [], + "valid": true + } + ] + }, + { + "description": "minContains=1 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "minContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [], + "valid": false + }, + { + "description": "no elements match", + "data": [2], + "valid": false + }, + { + "description": "single element matches, valid minContains", + "data": [1], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [1, 2], + "valid": true + }, + { + "description": "all elements match, valid minContains", + "data": [1, 1], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [1], + "valid": false + }, + { + "description": "some elements match, invalid minContains", + "data": [1, 2], + "valid": false + }, + { + "description": "all elements match, valid minContains (exactly as needed)", + "data": [1, 1], + "valid": true + }, + { + "description": "all elements match, valid minContains (more than needed)", + "data": [1, 1, 1], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [1, 2, 1], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains with a decimal value", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": {"const": 1}, + "minContains": 2.0 + }, + "tests": [ + { + "description": "one element matches, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "both elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains = minContains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "maxContains": 2, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [1], + "valid": false + }, + { + "description": "all elements match, invalid maxContains", + "data": [1, 1, 1], + "valid": false + }, + { + "description": "all elements match, valid maxContains and minContains", + "data": [1, 1], + "valid": true + } + ] + }, + { + "description": "maxContains < minContains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "maxContains": 1, + "minContains": 3 + }, + "tests": [ + { + "description": "empty data", + "data": [], + "valid": false + }, + { + "description": "invalid minContains", + "data": [1], + "valid": false + }, + { + "description": "invalid maxContains", + "data": [1, 1, 1], + "valid": false + }, + { + "description": "invalid maxContains and minContains", + "data": [1, 1], + "valid": false + } + ] + }, + { + "description": "minContains = 0", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": { "const": 1 }, + "minContains": 0 + }, + "tests": [ + { + "description": "empty data", + "data": [], + "valid": true + }, + { + "description": "minContains = 0 makes contains always pass", + "data": [2], + "valid": true + } + ] + }, + { + "description": "minContains = 0 with maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": {"const": 1}, + "minContains": 0, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "not more than maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many", + "data": [ 1, 1 ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/minItems.json b/src/test/suite/tests/draft-next/minItems.json new file mode 100644 index 000000000..b0e1c7211 --- /dev/null +++ b/src/test/suite/tests/draft-next/minItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "minItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minItems": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minItems": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/minLength.json b/src/test/suite/tests/draft-next/minLength.json new file mode 100644 index 000000000..52c9c9a14 --- /dev/null +++ b/src/test/suite/tests/draft-next/minLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "minLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minLength": 2 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minLength": 2.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/minProperties.json b/src/test/suite/tests/draft-next/minProperties.json new file mode 100644 index 000000000..434c0f1f7 --- /dev/null +++ b/src/test/suite/tests/draft-next/minProperties.json @@ -0,0 +1,60 @@ +[ + { + "description": "minProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minProperties": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minProperties": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/minimum.json b/src/test/suite/tests/draft-next/minimum.json new file mode 100644 index 000000000..2c6f71f6b --- /dev/null +++ b/src/test/suite/tests/draft-next/minimum.json @@ -0,0 +1,75 @@ +[ + { + "description": "minimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minimum": 1.1 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minimum": -2 + }, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/multipleOf.json b/src/test/suite/tests/draft-next/multipleOf.json new file mode 100644 index 000000000..f15345453 --- /dev/null +++ b/src/test/suite/tests/draft-next/multipleOf.json @@ -0,0 +1,98 @@ +[ + { + "description": "by int", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "multipleOf": 2 + }, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "multipleOf": 1.5 + }, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "multipleOf": 0.0001 + }, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer", + "multipleOf": 0.123456789 + }, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer", "multipleOf": 1e-8 + }, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/not.json b/src/test/suite/tests/draft-next/not.json new file mode 100644 index 000000000..b2251043c --- /dev/null +++ b/src/test/suite/tests/draft-next/not.json @@ -0,0 +1,153 @@ +[ + { + "description": "not", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "not with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": true + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "not with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": false + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "collect annotations inside a 'not', even if collection is disabled", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "not": { + "$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them", + "anyOf": [ + true, + { "properties": { "foo": true } } + ], + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "unevaluated property", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "annotations are still collected inside a 'not'", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/oneOf.json b/src/test/suite/tests/draft-next/oneOf.json new file mode 100644 index 000000000..840d1579d --- /dev/null +++ b/src/test/suite/tests/draft-next/oneOf.json @@ -0,0 +1,293 @@ +[ + { + "description": "oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [true, true, true] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [true, false, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [true, true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [false, false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/anchor.json b/src/test/suite/tests/draft-next/optional/anchor.json new file mode 100644 index 000000000..1de0b7a70 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/anchor.json @@ -0,0 +1,60 @@ +[ + { + "description": "$anchor inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $anchor buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "anchor_in_enum": { + "enum": [ + { + "$anchor": "my_anchor", + "type": "null" + } + ] + }, + "real_identifier_in_schema": { + "$anchor": "my_anchor", + "type": "string" + }, + "zzz_anchor_in_const": { + "const": { + "$anchor": "my_anchor", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/anchor_in_enum" }, + { "$ref": "#my_anchor" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$anchor": "my_anchor", + "type": "null" + }, + "valid": true + }, + { + "description": "in implementations that strip $anchor, this may match either $def", + "data": { + "type": "null" + }, + "valid": false + }, + { + "description": "match $ref to $anchor", + "data": "a string to match #/$defs/anchor_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $anchor", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/bignum.json b/src/test/suite/tests/draft-next/optional/bignum.json new file mode 100644 index 000000000..9f4f7079b --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/bignum.json @@ -0,0 +1,110 @@ +[ + { + "description": "integer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" + }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "number" + }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string" + }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "maximum": 18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "exclusiveMaximum": 972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "minimum": -18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "exclusiveMinimum": -972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/dependencies-compatibility.json b/src/test/suite/tests/draft-next/optional/dependencies-compatibility.json new file mode 100644 index 000000000..1fe384cad --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/dependencies-compatibility.json @@ -0,0 +1,282 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "single schema dependency", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "schema dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "dependencies": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/dynamicRef.json b/src/test/suite/tests/draft-next/optional/dynamicRef.json new file mode 100644 index 000000000..dcace154e --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/dynamicRef.json @@ -0,0 +1,56 @@ +[ + { + "description": "$dynamicRef skips over intermediate resources - pointer reference across resource boundary", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/optional/main", + "type": "object", + "properties": { + "bar-item": { + "$ref": "bar#/$defs/item" + } + }, + "$defs": { + "bar": { + "$id": "bar", + "type": "array", + "items": { + "$ref": "item" + }, + "$defs": { + "item": { + "$id": "item", + "type": "object", + "properties": { + "content": { + "$dynamicRef": "#content" + } + }, + "$defs": { + "defaultContent": { + "$dynamicAnchor": "content", + "type": "integer" + } + } + }, + "content": { + "$dynamicAnchor": "content", + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "integer property passes", + "data": { "bar-item": { "content": 42 } }, + "valid": true + }, + { + "description": "string property fails", + "data": { "bar-item": { "content": "value" } }, + "valid": false + } + ] + }] \ No newline at end of file diff --git a/src/test/suite/tests/draft-next/optional/ecmascript-regex.json b/src/test/suite/tests/draft-next/optional/ecmascript-regex.json new file mode 100644 index 000000000..a1a4f9638 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/ecmascript-regex.json @@ -0,0 +1,582 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "\\p{Letter}cole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "\\wcole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "[a-z]cole" + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "^\\d+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "^\\p{digit}+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "\\p{Letter}cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "\\wcole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "[a-z]cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "^\\d+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "^\\p{digit}+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/float-overflow.json b/src/test/suite/tests/draft-next/optional/float-overflow.json new file mode 100644 index 000000000..a4fcca679 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/float-overflow.json @@ -0,0 +1,17 @@ +[ + { + "description": "all integers are multiples of 0.5, if overflow is handled", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer", + "multipleOf": 0.5 + }, + "tests": [ + { + "description": "valid if optional overflow handling is implemented", + "data": 1e308, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format-assertion.json b/src/test/suite/tests/draft-next/optional/format-assertion.json new file mode 100644 index 000000000..ede922a24 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format-assertion.json @@ -0,0 +1,42 @@ +[ + { + "description": "schema that uses custom metaschema with format-assertion: false", + "schema": { + "$id": "https://schema/using/format-assertion/false", + "$schema": "http://localhost:1234/draft-next/format-assertion-false.json", + "format": "ipv4" + }, + "tests": [ + { + "description": "format-assertion: false: valid string", + "data": "127.0.0.1", + "valid": true + }, + { + "description": "format-assertion: false: invalid string", + "data": "not-an-ipv4", + "valid": false + } + ] + }, + { + "description": "schema that uses custom metaschema with format-assertion: true", + "schema": { + "$id": "https://schema/using/format-assertion/true", + "$schema": "http://localhost:1234/draft-next/format-assertion-true.json", + "format": "ipv4" + }, + "tests": [ + { + "description": "format-assertion: true: valid string", + "data": "127.0.0.1", + "valid": true + }, + { + "description": "format-assertion: true: invalid string", + "data": "not-an-ipv4", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/date-time.json b/src/test/suite/tests/draft-next/optional/format/date-time.json new file mode 100644 index 000000000..e25845b02 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/date-time.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of date-time strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/date.json b/src/test/suite/tests/draft-next/optional/format/date.json new file mode 100644 index 000000000..aa55555cb --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/date.json @@ -0,0 +1,246 @@ +[ + { + "description": "validation of date strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date string", + "data": "1963-06-19", + "valid": true + }, + { + "description": "a valid date string with 31 days in January", + "data": "2020-01-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in January", + "data": "2020-01-32", + "valid": false + }, + { + "description": "a valid date string with 28 days in February (normal)", + "data": "2021-02-28", + "valid": true + }, + { + "description": "a invalid date string with 29 days in February (normal)", + "data": "2021-02-29", + "valid": false + }, + { + "description": "a valid date string with 29 days in February (leap)", + "data": "2020-02-29", + "valid": true + }, + { + "description": "a invalid date string with 30 days in February (leap)", + "data": "2020-02-30", + "valid": false + }, + { + "description": "a valid date string with 31 days in March", + "data": "2020-03-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in March", + "data": "2020-03-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in April", + "data": "2020-04-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in April", + "data": "2020-04-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in May", + "data": "2020-05-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in May", + "data": "2020-05-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in June", + "data": "2020-06-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in June", + "data": "2020-06-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in July", + "data": "2020-07-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in July", + "data": "2020-07-32", + "valid": false + }, + { + "description": "a valid date string with 31 days in August", + "data": "2020-08-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in August", + "data": "2020-08-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in September", + "data": "2020-09-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in September", + "data": "2020-09-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in October", + "data": "2020-10-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in October", + "data": "2020-10-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in November", + "data": "2020-11-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in November", + "data": "2020-11-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in December", + "data": "2020-12-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in December", + "data": "2020-12-32", + "valid": false + }, + { + "description": "a invalid date string with invalid month", + "data": "2020-13-01", + "valid": false + }, + { + "description": "an invalid date string", + "data": "06/19/1963", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "non-padded month dates are not valid", + "data": "1998-1-20", + "valid": false + }, + { + "description": "non-padded day dates are not valid", + "data": "1998-01-1", + "valid": false + }, + { + "description": "invalid month", + "data": "1998-13-01", + "valid": false + }, + { + "description": "invalid month-day combination", + "data": "1998-04-31", + "valid": false + }, + { + "description": "2021 is not a leap year", + "data": "2021-02-29", + "valid": false + }, + { + "description": "2020 is a leap year", + "data": "2020-02-29", + "valid": true + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1963-06-1৪", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", + "data": "20230328", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", + "data": "2023-W01", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", + "data": "2023-W13-2", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", + "data": "2022W527", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/duration.json b/src/test/suite/tests/draft-next/optional/format/duration.json new file mode 100644 index 000000000..c4aa66bae --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/duration.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of duration strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid duration string", + "data": "P4DT12H30M5S", + "valid": true + }, + { + "description": "an invalid duration string", + "data": "PT1D", + "valid": false + }, + { + "description": "must start with P", + "data": "4DT12H30M5S", + "valid": false + }, + { + "description": "no elements present", + "data": "P", + "valid": false + }, + { + "description": "no time elements present", + "data": "P1YT", + "valid": false + }, + { + "description": "no date or time elements present", + "data": "PT", + "valid": false + }, + { + "description": "elements out of order", + "data": "P2D1Y", + "valid": false + }, + { + "description": "missing time separator", + "data": "P1D2H", + "valid": false + }, + { + "description": "time element in the date position", + "data": "P2S", + "valid": false + }, + { + "description": "four years duration", + "data": "P4Y", + "valid": true + }, + { + "description": "zero time, in seconds", + "data": "PT0S", + "valid": true + }, + { + "description": "zero time, in days", + "data": "P0D", + "valid": true + }, + { + "description": "one month duration", + "data": "P1M", + "valid": true + }, + { + "description": "one minute duration", + "data": "PT1M", + "valid": true + }, + { + "description": "one and a half days, in hours", + "data": "PT36H", + "valid": true + }, + { + "description": "one and a half days, in days and hours", + "data": "P1DT12H", + "valid": true + }, + { + "description": "two weeks", + "data": "P2W", + "valid": true + }, + { + "description": "weeks cannot be combined with other units", + "data": "P1Y2W", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "P২Y", + "valid": false + }, + { + "description": "element without unit", + "data": "P1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/ecmascript-regex.json b/src/test/suite/tests/draft-next/optional/format/ecmascript-regex.json new file mode 100644 index 000000000..1e19c2729 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/ecmascript-regex.json @@ -0,0 +1,16 @@ +[ + { + "description": "\\a is not an ECMA 262 control escape", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "regex" + }, + "tests": [ + { + "description": "when used as a pattern", + "data": "\\a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/email.json b/src/test/suite/tests/draft-next/optional/format/email.json new file mode 100644 index 000000000..70633f188 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/email.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "a quoted string with a space in the local part is valid", + "data": "\"joe bloggs\"@example.com", + "valid": true + }, + { + "description": "a quoted string with a double dot in the local part is valid", + "data": "\"joe..bloggs\"@example.com", + "valid": true + }, + { + "description": "a quoted string with a @ in the local part is valid", + "data": "\"joe@bloggs\"@example.com", + "valid": true + }, + { + "description": "an IPv4-address-literal after the @ is valid", + "data": "joe.bloggs@[127.0.0.1]", + "valid": true + }, + { + "description": "an IPv6-address-literal after the @ is valid", + "data": "joe.bloggs@[IPv6:::1]", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "an invalid domain", + "data": "joe.bloggs@invalid=domain.com", + "valid": false + }, + { + "description": "an invalid IPv4-address-literal", + "data": "joe.bloggs@[127.0.0.300]", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/hostname.json b/src/test/suite/tests/draft-next/optional/format/hostname.json new file mode 100644 index 000000000..bc3a60dcc --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/hostname.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of host names", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/idn-email.json b/src/test/suite/tests/draft-next/optional/format/idn-email.json new file mode 100644 index 000000000..1f3536709 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/idn-email.json @@ -0,0 +1,61 @@ +[ + { + "description": "validation of an internationalized e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid idn e-mail (example@example.test in Hangul)", + "data": "실례@실례.테스트", + "valid": true + }, + { + "description": "an invalid idn e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/idn-hostname.json b/src/test/suite/tests/draft-next/optional/format/idn-hostname.json new file mode 100644 index 000000000..1061f4243 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/idn-hostname.json @@ -0,0 +1,389 @@ +[ + { + "description": "validation of internationalized host names", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name (example.test in Hangul)", + "data": "실례.테스트", + "valid": true + }, + { + "description": "illegal first char U+302E Hangul single dot tone mark", + "data": "〮실례.테스트", + "valid": false + }, + { + "description": "contains illegal char U+302E Hangul single dot tone mark", + "data": "실〮례.테스트", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", + "valid": false + }, + { + "description": "invalid label, correct Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc3492#section-7.1", + "data": "-> $1.00 <--", + "valid": false + }, + { + "description": "valid Chinese Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4", + "data": "xn--ihqwcrb4cv8a8dqg056pqjye", + "valid": true + }, + { + "description": "invalid Punycode", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "xn--X", + "valid": false + }, + { + "description": "U-label contains \"--\" in the 3rd and 4th position", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "XN--aa---o47jg78q", + "valid": false + }, + { + "description": "U-label starts with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello", + "valid": false + }, + { + "description": "U-label ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "hello-", + "valid": false + }, + { + "description": "U-label starts and ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello-", + "valid": false + }, + { + "description": "Begins with a Spacing Combining Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0903hello", + "valid": false + }, + { + "description": "Begins with a Nonspacing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0300hello", + "valid": false + }, + { + "description": "Begins with an Enclosing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0488hello", + "valid": false + }, + { + "description": "Exceptions that are PVALID, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u00df\u03c2\u0f0b\u3007", + "valid": true + }, + { + "description": "Exceptions that are PVALID, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u06fd\u06fe", + "valid": true + }, + { + "description": "Exceptions that are DISALLOWED, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u0640\u07fa", + "valid": false + }, + { + "description": "Exceptions that are DISALLOWED, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6 Note: The two combining marks (U+302E and U+302F) are in the middle and not at the start", + "data": "\u3031\u3032\u3033\u3034\u3035\u302e\u302f\u303b", + "valid": false + }, + { + "description": "MIDDLE DOT with no preceding 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "a\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing preceding", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with no following 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7a", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing following", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7", + "valid": false + }, + { + "description": "MIDDLE DOT with surrounding 'l's", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7l", + "valid": true + }, + { + "description": "Greek KERAIA not followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375S", + "valid": false + }, + { + "description": "Greek KERAIA not followed by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375", + "valid": false + }, + { + "description": "Greek KERAIA followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375\u03b2", + "valid": true + }, + { + "description": "Hebrew GERESH not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "A\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05d0\u05f3\u05d1", + "valid": true + }, + { + "description": "Hebrew GERSHAYIM not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "A\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05d0\u05f4\u05d1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "def\u30fbabc", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with no other characters", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with Hiragana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u3041", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Katakana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u30a1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u4e08", + "valid": true + }, + { + "description": "Arabic-Indic digits mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u06f0", + "valid": false + }, + { + "description": "Arabic-Indic digits not mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u0628", + "valid": true + }, + { + "description": "Extended Arabic-Indic digits not mixed with Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.9", + "data": "\u06f00", + "valid": true + }, + { + "description": "ZERO WIDTH JOINER not preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u094d\u200d\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1", + "data": "\u0915\u094d\u200c\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER not preceded by Virama but matches regexp", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1 https://www.w3.org/TR/alreq/#h_disjoining_enforcement", + "data": "\u0628\u064a\u200c\u0628\u064a", + "valid": true + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + } + ] + }, + { + "description": "validation of separators in internationalized host names", + "specification": [ + {"rfc3490": "3.1", "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)"} + ], + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "single dot", + "data": ".", + "valid": false + }, + { + "description": "single ideographic full stop", + "data": "\u3002", + "valid": false + }, + { + "description": "single fullwidth full stop", + "data": "\uff0e", + "valid": false + }, + { + "description": "single halfwidth ideographic full stop", + "data": "\uff61", + "valid": false + }, + { + "description": "dot as label separator", + "data": "a.b", + "valid": true + }, + { + "description": "ideographic full stop as label separator", + "data": "a\u3002b", + "valid": true + }, + { + "description": "fullwidth full stop as label separator", + "data": "a\uff0eb", + "valid": true + }, + { + "description": "halfwidth ideographic full stop as label separator", + "data": "a\uff61b", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/ipv4.json b/src/test/suite/tests/draft-next/optional/format/ipv4.json new file mode 100644 index 000000000..2a4bc2b2f --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/ipv4.json @@ -0,0 +1,92 @@ +[ + { + "description": "validation of IP addresses", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/ipv6.json b/src/test/suite/tests/draft-next/optional/format/ipv6.json new file mode 100644 index 000000000..241df7f3c --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/ipv6.json @@ -0,0 +1,211 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/iri-reference.json b/src/test/suite/tests/draft-next/optional/format/iri-reference.json new file mode 100644 index 000000000..543b99e7e --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/iri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of IRI References", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid protocol-relative IRI Reference", + "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid relative IRI Reference", + "data": "/âππ", + "valid": true + }, + { + "description": "an invalid IRI Reference", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "a valid IRI Reference", + "data": "âππ", + "valid": true + }, + { + "description": "a valid IRI fragment", + "data": "#ƒrägmênt", + "valid": true + }, + { + "description": "an invalid IRI fragment", + "data": "#ƒräg\\mênt", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/iri.json b/src/test/suite/tests/draft-next/optional/format/iri.json new file mode 100644 index 000000000..48e82a285 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/iri.json @@ -0,0 +1,86 @@ +[ + { + "description": "validation of IRIs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI with anchor tag", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid IRI with anchor tag and parentheses", + "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", + "valid": true + }, + { + "description": "a valid IRI with URL-encoded stuff", + "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid IRI with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid IRI based on IPv6", + "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + "valid": true + }, + { + "description": "an invalid IRI based on IPv6", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": false + }, + { + "description": "an invalid relative IRI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid IRI", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "an invalid IRI though valid IRI reference", + "data": "âππ", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/json-pointer.json b/src/test/suite/tests/draft-next/optional/format/json-pointer.json new file mode 100644 index 000000000..f55342f14 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/json-pointer.json @@ -0,0 +1,201 @@ +[ + { + "description": "validation of JSON-pointers (JSON String Representation)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid JSON-pointer", + "data": "/foo/bar~0/baz~1/%a", + "valid": true + }, + { + "description": "not a valid JSON-pointer (~ not escaped)", + "data": "/foo/bar~", + "valid": false + }, + { + "description": "valid JSON-pointer with empty segment", + "data": "/foo//bar", + "valid": true + }, + { + "description": "valid JSON-pointer with the last empty segment", + "data": "/foo/bar/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #1", + "data": "", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #2", + "data": "/foo", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #3", + "data": "/foo/0", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #4", + "data": "/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #5", + "data": "/a~1b", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #6", + "data": "/c%d", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #7", + "data": "/e^f", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #8", + "data": "/g|h", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #9", + "data": "/i\\j", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #10", + "data": "/k\"l", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #11", + "data": "/ ", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #12", + "data": "/m~0n", + "valid": true + }, + { + "description": "valid JSON-pointer used adding to the last array position", + "data": "/foo/-", + "valid": true + }, + { + "description": "valid JSON-pointer (- used as object member name)", + "data": "/foo/-/bar", + "valid": true + }, + { + "description": "valid JSON-pointer (multiple escaped characters)", + "data": "/~1~0~0~1~1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #1", + "data": "/~1.1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #2", + "data": "/~0.1", + "valid": true + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", + "data": "#", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", + "data": "#/", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", + "data": "#a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #1", + "data": "/~0~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #2", + "data": "/~0/~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #1", + "data": "/~2", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #2", + "data": "/~-1", + "valid": false + }, + { + "description": "not a valid JSON-pointer (multiple characters not escaped)", + "data": "/~~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", + "data": "a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", + "data": "0", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", + "data": "a/a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/regex.json b/src/test/suite/tests/draft-next/optional/format/regex.json new file mode 100644 index 000000000..5987f5349 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/regex.json @@ -0,0 +1,51 @@ +[ + { + "description": "validation of regular expressions", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid regular expression", + "data": "([abc])+\\s+$", + "valid": true + }, + { + "description": "a regular expression with unclosed parens is invalid", + "data": "^(abc]", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/relative-json-pointer.json b/src/test/suite/tests/draft-next/optional/format/relative-json-pointer.json new file mode 100644 index 000000000..1e28fc29f --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/relative-json-pointer.json @@ -0,0 +1,101 @@ +[ + { + "description": "validation of Relative JSON Pointers (RJP)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid upwards RJP", + "data": "1", + "valid": true + }, + { + "description": "a valid downwards RJP", + "data": "0/foo/bar", + "valid": true + }, + { + "description": "a valid up and then down RJP, with array index", + "data": "2/0/baz/1/zip", + "valid": true + }, + { + "description": "a valid RJP taking the member or index name", + "data": "0#", + "valid": true + }, + { + "description": "an invalid RJP that is a valid JSON Pointer", + "data": "/foo/bar", + "valid": false + }, + { + "description": "negative prefix", + "data": "-1/foo/bar", + "valid": false + }, + { + "description": "explicit positive prefix", + "data": "+1/foo/bar", + "valid": false + }, + { + "description": "## is not a valid json-pointer", + "data": "0##", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus json-pointer", + "data": "01/a", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus octothorpe", + "data": "01#", + "valid": false + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "multi-digit integer prefix", + "data": "120/foo/bar", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/time.json b/src/test/suite/tests/draft-next/optional/format/time.json new file mode 100644 index 000000000..0a000a48c --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/time.json @@ -0,0 +1,236 @@ +[ + { + "description": "validation of time strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid time string", + "data": "08:30:06Z", + "valid": true + }, + { + "description": "invalid time string with extra leading zeros", + "data": "008:030:006Z", + "valid": false + }, + { + "description": "invalid time string with no leading zero for single digit", + "data": "8:3:6Z", + "valid": false + }, + { + "description": "hour, minute, second must be two digits", + "data": "8:0030:6Z", + "valid": false + }, + { + "description": "a valid time string with leap second, Zulu", + "data": "23:59:60Z", + "valid": true + }, + { + "description": "invalid leap second, Zulu (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "invalid leap second, Zulu (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "valid leap second, zero time-offset", + "data": "23:59:60+00:00", + "valid": true + }, + { + "description": "invalid leap second, zero time-offset (wrong hour)", + "data": "22:59:60+00:00", + "valid": false + }, + { + "description": "invalid leap second, zero time-offset (wrong minute)", + "data": "23:58:60+00:00", + "valid": false + }, + { + "description": "valid leap second, positive time-offset", + "data": "01:29:60+01:30", + "valid": true + }, + { + "description": "valid leap second, large positive time-offset", + "data": "23:29:60+23:30", + "valid": true + }, + { + "description": "invalid leap second, positive time-offset (wrong hour)", + "data": "23:59:60+01:00", + "valid": false + }, + { + "description": "invalid leap second, positive time-offset (wrong minute)", + "data": "23:59:60+00:30", + "valid": false + }, + { + "description": "valid leap second, negative time-offset", + "data": "15:59:60-08:00", + "valid": true + }, + { + "description": "valid leap second, large negative time-offset", + "data": "00:29:60-23:30", + "valid": true + }, + { + "description": "invalid leap second, negative time-offset (wrong hour)", + "data": "23:59:60-01:00", + "valid": false + }, + { + "description": "invalid leap second, negative time-offset (wrong minute)", + "data": "23:59:60-00:30", + "valid": false + }, + { + "description": "a valid time string with second fraction", + "data": "23:20:50.52Z", + "valid": true + }, + { + "description": "a valid time string with precise second fraction", + "data": "08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid time string with plus offset", + "data": "08:30:06+00:20", + "valid": true + }, + { + "description": "a valid time string with minus offset", + "data": "08:30:06-08:00", + "valid": true + }, + { + "description": "hour, minute in time-offset must be two digits", + "data": "08:30:06-8:000", + "valid": false + }, + { + "description": "a valid time string with case-insensitive Z", + "data": "08:30:06z", + "valid": true + }, + { + "description": "an invalid time string with invalid hour", + "data": "24:00:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid minute", + "data": "00:60:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid second", + "data": "00:00:61Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset hour", + "data": "01:02:03+24:00", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset minute", + "data": "01:02:03+00:60", + "valid": false + }, + { + "description": "an invalid time string with invalid time with both Z and numoffset", + "data": "01:02:03Z+00:30", + "valid": false + }, + { + "description": "an invalid offset indicator", + "data": "08:30:06 PST", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "01:01:01,1111", + "valid": false + }, + { + "description": "no time offset", + "data": "12:00:00", + "valid": false + }, + { + "description": "no time offset with second fraction", + "data": "12:00:00.52", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২:00:00Z", + "valid": false + }, + { + "description": "offset not starting with plus or minus", + "data": "08:30:06#00:20", + "valid": false + }, + { + "description": "contains letters", + "data": "ab:cd:ef", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/uri-reference.json b/src/test/suite/tests/draft-next/optional/format/uri-reference.json new file mode 100644 index 000000000..8d9d254ea --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/uri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of URI References", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid relative URI Reference", + "data": "/abc", + "valid": true + }, + { + "description": "an invalid URI Reference", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "a valid URI Reference", + "data": "abc", + "valid": true + }, + { + "description": "a valid URI fragment", + "data": "#fragment", + "valid": true + }, + { + "description": "an invalid URI fragment", + "data": "#frag\\ment", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/uri-template.json b/src/test/suite/tests/draft-next/optional/format/uri-template.json new file mode 100644 index 000000000..f57d62df2 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/uri-template.json @@ -0,0 +1,61 @@ +[ + { + "description": "format: uri-template", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", + "valid": true + }, + { + "description": "an invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false + }, + { + "description": "a valid uri-template without variables", + "data": "http://example.com/dictionary", + "valid": true + }, + { + "description": "a valid relative uri-template", + "data": "dictionary/{term:1}/{term}", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/uri.json b/src/test/suite/tests/draft-next/optional/format/uri.json new file mode 100644 index 000000000..50908eab2 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/uri.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of URIs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/format/uuid.json b/src/test/suite/tests/draft-next/optional/format/uuid.json new file mode 100644 index 000000000..6cea9dade --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/format/uuid.json @@ -0,0 +1,116 @@ +[ + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "all upper-case", + "data": "2EB8AA08-AA98-11EA-B4AA-73B441D16380", + "valid": true + }, + { + "description": "all lower-case", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d16380", + "valid": true + }, + { + "description": "mixed case", + "data": "2eb8aa08-AA98-11ea-B4Aa-73B441D16380", + "valid": true + }, + { + "description": "all zeroes is valid", + "data": "00000000-0000-0000-0000-000000000000", + "valid": true + }, + { + "description": "wrong length", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": false + }, + { + "description": "missing section", + "data": "2eb8aa08-aa98-11ea-73b441d16380", + "valid": false + }, + { + "description": "bad characters (not hex)", + "data": "2eb8aa08-aa98-11ea-b4ga-73b441d16380", + "valid": false + }, + { + "description": "no dashes", + "data": "2eb8aa08aa9811eab4aa73b441d16380", + "valid": false + }, + { + "description": "too few dashes", + "data": "2eb8aa08aa98-11ea-b4aa73b441d16380", + "valid": false + }, + { + "description": "too many dashes", + "data": "2eb8-aa08-aa98-11ea-b4aa73b44-1d16380", + "valid": false + }, + { + "description": "dashes in the wrong spot", + "data": "2eb8aa08aa9811eab4aa73b441d16380----", + "valid": false + }, + { + "description": "valid version 4", + "data": "98d80576-482e-427f-8434-7f86890ab222", + "valid": true + }, + { + "description": "valid version 5", + "data": "99c17cbb-656f-564a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 6", + "data": "99c17cbb-656f-664a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 15", + "data": "99c17cbb-656f-f64a-940f-1a4568f03487", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/id.json b/src/test/suite/tests/draft-next/optional/id.json new file mode 100644 index 000000000..fc26f26c2 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/id.json @@ -0,0 +1,53 @@ +[ + { + "description": "$id inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $id buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "id_in_enum": { + "enum": [ + { + "$id": "https://localhost:1234/draft-next/id/my_identifier.json", + "type": "null" + } + ] + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft-next/id/my_identifier.json", + "type": "string" + }, + "zzz_id_in_const": { + "const": { + "$id": "https://localhost:1234/draft-next/id/my_identifier.json", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_enum" }, + { "$ref": "https://localhost:1234/draft-next/id/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$id": "https://localhost:1234/draft-next/id/my_identifier.json", + "type": "null" + }, + "valid": true + }, + { + "description": "match $ref to $id", + "data": "a string to match #/$defs/id_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $id", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/non-bmp-regex.json b/src/test/suite/tests/draft-next/optional/non-bmp-regex.json new file mode 100644 index 000000000..3af875c99 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/non-bmp-regex.json @@ -0,0 +1,86 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "^🐲*$" + }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/refOfUnknownKeyword.json b/src/test/suite/tests/draft-next/optional/refOfUnknownKeyword.json new file mode 100644 index 000000000..c832e09f6 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/refOfUnknownKeyword.json @@ -0,0 +1,69 @@ +[ + { + "description": "reference of a root arbitrary keyword ", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unknown-keyword": {"type": "integer"}, + "properties": { + "bar": {"$ref": "#/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference of an arbitrary keyword of a sub-schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"unknown-keyword": {"type": "integer"}}, + "bar": {"$ref": "#/properties/foo/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference internals of known non-applicator", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "/base", + "examples": [ + { "type": "string" } + ], + "$ref": "#/examples/0" + }, + "tests": [ + { + "description": "match", + "data": "a string", + "valid": true + }, + { + "description": "mismatch", + "data": 42, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/optional/unknownKeyword.json b/src/test/suite/tests/draft-next/optional/unknownKeyword.json new file mode 100644 index 000000000..055ff6b66 --- /dev/null +++ b/src/test/suite/tests/draft-next/optional/unknownKeyword.json @@ -0,0 +1,57 @@ +[ + { + "description": "$id inside an unknown keyword is not a real identifier", + "comment": "the implementation must not be confused by an $id in locations we do not know how to parse", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "id_in_unknown0": { + "not": { + "array_of_schemas": [ + { + "$id": "https://localhost:1234/draft-next/unknownKeyword/my_identifier.json", + "type": "null" + } + ] + } + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft-next/unknownKeyword/my_identifier.json", + "type": "string" + }, + "id_in_unknown1": { + "not": { + "object_of_schemas": { + "foo": { + "$id": "https://localhost:1234/draft-next/unknownKeyword/my_identifier.json", + "type": "integer" + } + } + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_unknown0" }, + { "$ref": "#/$defs/id_in_unknown1" }, + { "$ref": "https://localhost:1234/draft-next/unknownKeyword/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "type matches second anyOf, which has a real schema in it", + "data": "a string", + "valid": true + }, + { + "description": "type matches non-schema in first anyOf", + "data": null, + "valid": false + }, + { + "description": "type matches non-schema in third anyOf", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/pattern.json b/src/test/suite/tests/draft-next/pattern.json new file mode 100644 index 000000000..09c6d0fbb --- /dev/null +++ b/src/test/suite/tests/draft-next/pattern.json @@ -0,0 +1,65 @@ +[ + { + "description": "pattern validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "^a*$" + }, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "pattern": "a+" + }, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/patternProperties.json b/src/test/suite/tests/draft-next/patternProperties.json new file mode 100644 index 000000000..c7aca3dfa --- /dev/null +++ b/src/test/suite/tests/draft-next/patternProperties.json @@ -0,0 +1,176 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/prefixItems.json b/src/test/suite/tests/draft-next/prefixItems.json new file mode 100644 index 000000000..a7f5928c9 --- /dev/null +++ b/src/test/suite/tests/draft-next/prefixItems.json @@ -0,0 +1,104 @@ +[ + { + "description": "a schema given for prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "prefixItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "additional items are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{"type": "integer"}] + }, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "prefixItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/properties.json b/src/test/suite/tests/draft-next/properties.json new file mode 100644 index 000000000..1ba4fe8ed --- /dev/null +++ b/src/test/suite/tests/draft-next/properties.json @@ -0,0 +1,242 @@ +[ + { + "description": "object properties validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/propertyDependencies.json b/src/test/suite/tests/draft-next/propertyDependencies.json new file mode 100644 index 000000000..9efa2b48b --- /dev/null +++ b/src/test/suite/tests/draft-next/propertyDependencies.json @@ -0,0 +1,161 @@ +[ + { + "description": "propertyDependencies doesn't act on non-objects", + "schema": { + "propertyDependencies": { + "foo": {"bar": false} + } + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores strings", + "data": "abc", + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "propertyDependencies doesn't act on non-string property values", + "schema": { + "propertyDependencies": { + "foo": {"bar": false} + } + }, + "tests": [ + { + "description": "ignores booleans", + "data": {"foo": false}, + "valid": true + }, + { + "description": "ignores integers", + "data": {"foo": 2}, + "valid": true + }, + { + "description": "ignores floats", + "data": {"foo": 1.1}, + "valid": true + }, + { + "description": "ignores objects", + "data": {"foo": {}}, + "valid": true + }, + { + "description": "ignores objects wth a key of the expected value", + "data": {"foo": {"bar": "baz"}}, + "valid": true + }, + { + "description": "ignores objects with the expected value nested in structure", + "data": {"foo": {"baz": "bar"}}, + "valid": true + }, + { + "description": "ignores arrays", + "data": {"foo": []}, + "valid": true + }, + { + "description": "ignores null", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "multiple options selects the right one", + "schema": { + "propertyDependencies": { + "foo": { + "bar": { + "minProperties": 2, + "maxProperties": 2 + }, + "baz": {"maxProperties": 1}, + "qux": true, + "quux": false + } + } + }, + "tests": [ + { + "description": "bar with exactly 2 properties is valid", + "data": { + "foo": "bar", + "other-foo": "other-bar" + }, + "valid": true + }, + { + "description": "bar with more than 2 properties is invalid", + "data": { + "foo": "bar", + "other-foo": "other-bar", + "too": "many" + }, + "valid": false + }, + { + "description": "bar with fewer than 2 properties is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "baz alone is valid", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "baz with other properties is invalid", + "data": { + "foo": "baz", + "other-foo": "other-bar" + }, + "valid": false + }, + { + "description": "anything allowed with qux", + "data": { + "foo": "qux", + "blah": ["some other property"], + "more": "properties" + }, + "valid": true + }, + { + "description": "quux is disallowed", + "data": { + "foo": "quux" + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/propertyNames.json b/src/test/suite/tests/draft-next/propertyNames.json new file mode 100644 index 000000000..e61401703 --- /dev/null +++ b/src/test/suite/tests/draft-next/propertyNames.json @@ -0,0 +1,85 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "propertyNames": true + }, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "propertyNames": false + }, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/ref.json b/src/test/suite/tests/draft-next/ref.json new file mode 100644 index 000000000..8417ce299 --- /dev/null +++ b/src/test/suite/tests/draft-next/ref.json @@ -0,0 +1,1067 @@ +[ + { + "description": "root pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + {"type": "integer"}, + {"$ref": "#/prefixItems/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/$defs/tilde~0field"}, + "slash": {"$ref": "#/$defs/slash~1field"}, + "percent": {"$ref": "#/$defs/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "a": {"type": "integer"}, + "b": {"$ref": "#/$defs/a"}, + "c": {"$ref": "#/$defs/b"} + }, + "$ref": "#/$defs/c" + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref applies alongside sibling keywords", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/$defs/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid, maxItems valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems invalid", + "data": { "foo": [1, 2, 3] }, + "valid": false + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "https://json-schema.org/draft/next/schema" + }, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "$ref": {"$ref": "#/$defs/is-string"} + }, + "$defs": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "$defs": { + "node": { + "$id": "http://localhost:1234/draft-next/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo\"bar": {"$ref": "#/$defs/foo%22bar"} + }, + "$defs": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "ref creates new scope when adjacent to keywords", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "A": { + "unevaluatedProperties": false + } + }, + "properties": { + "prop1": { + "type": "string" + } + }, + "$ref": "#/$defs/A" + }, + "tests": [ + { + "description": "referenced subschema doesn't see annotations from properties", + "data": { + "prop1": "match" + }, + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/$defs/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/$defs/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-relative-uri-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-refs-absolute-uris-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "$id must be resolved against nearest parent, not just immediate parent", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://example.com/a.json", + "$defs": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "$defs": { + "y": { + "$id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$id": "https://example.com/draft/next/ref-and-id1/base.json", + "$ref": "int.json", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/ref-and-id1/int.json", + "$id": "int.json", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id1-int.json", + "$id": "/draft/next/ref-and-id1-int.json", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $anchor and $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$id": "https://example.com/draft/next/ref-and-id2/base.json", + "$ref": "#bigint", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: https://example.com/ref-and-id2/base.json#bigint", + "$anchor": "bigint", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", + "$id": "/draft/next/ref-and-id2/", + "$anchor": "bigint", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "RFC 8141 §2.2", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "RFC 8141 §2.3.1", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "RFC 8141 §2.3.2", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with f-component", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$comment": "RFC 8141 §2.3.3, but we don't allow fragments", + "$ref": "https://json-schema.org/draft/next/schema" + }, + "tests": [ + { + "description": "is invalid", + "data": {"$id": "urn:example:foo-bar-baz-qux#somepart"}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "$defs": { + "bar": { + "$anchor": "something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN ref with nested pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": { + "foo": { + "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "bar", + "valid": true + }, + { + "description": "a non-string is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "ref to if", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://example.com/ref/if", + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to then", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://example.com/ref/then", + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://example.com/ref/else", + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://example.com/ref/absref.json", + "$defs": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "$ref": "/absref/foobar.json" + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "file:///folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "file:///c:/folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "": { + "$defs": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs//$defs/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/refRemote.json b/src/test/suite/tests/draft-next/refRemote.json new file mode 100644 index 000000000..647fb9f19 --- /dev/null +++ b/src/test/suite/tests/draft-next/refRemote.json @@ -0,0 +1,342 @@ +[ + { + "description": "remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/integer.json" + }, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/subSchemas.json#/$defs/integer" + }, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "anchor within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/locationIndependentIdentifier.json#foo" + }, + "tests": [ + { + "description": "remote anchor valid", + "data": 1, + "valid": true + }, + { + "description": "remote anchor invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/subSchemas.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/scope_change_defs1.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolder/"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/scope_change_defs2.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolderInSubschema/#/$defs/bar"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "$defs": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/object", + "type": "object", + "properties": { + "name": {"$ref": "name-defs.json#/$defs/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to defs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/schema-remote-ref-ref-defs1.json", + "$ref": "ref-and-defs.json" + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/locationIndependentIdentifier.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "http://localhost:1234/draft-next/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different $id", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/different-id-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different URN $id", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/urn-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with nested absolute ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/nested-absolute-ref-to-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds detached $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "http://localhost:1234/draft-next/detached-ref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/required.json b/src/test/suite/tests/draft-next/required.json new file mode 100644 index 000000000..cc02fd34f --- /dev/null +++ b/src/test/suite/tests/draft-next/required.json @@ -0,0 +1,158 @@ +[ + { + "description": "required validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "required": ["__proto__", "toString", "constructor"] + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/type.json b/src/test/suite/tests/draft-next/type.json new file mode 100644 index 000000000..86e551c79 --- /dev/null +++ b/src/test/suite/tests/draft-next/type.json @@ -0,0 +1,501 @@ +[ + { + "description": "integer type matches integers", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "integer" + }, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "number" + }, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "string" + }, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object" + }, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "array" + }, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "boolean" + }, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "null" + }, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": ["integer", "string"] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/unevaluatedItems.json b/src/test/suite/tests/draft-next/unevaluatedItems.json new file mode 100644 index 000000000..6a728f8ae --- /dev/null +++ b/src/test/suite/tests/draft-next/unevaluatedItems.json @@ -0,0 +1,823 @@ +[ + { + "description": "unevaluatedItems true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": true + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems as schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": { "type": "string" } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with valid unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with invalid unevaluated items", + "data": [42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with uniform items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": { "type": "string" }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with tuple", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with items and prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "type": "string" } + ], + "items": true, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "items": {"type": "number"}, + "unevaluatedItems": {"type": "string"} + }, + "tests": [ + { + "description": "valid under items", + "comment": "no elements are considered by unevaluatedItems", + "data": [5, 6, 7, 8], + "valid": true + }, + { + "description": "invalid under items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested tuple", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "type": "string" } + ], + "allOf": [ + { + "prefixItems": [ + true, + { "type": "number" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", 42], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", 42, true], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": {"type": "boolean"}, + "anyOf": [ + { "items": {"type": "string"} }, + true + ] + }, + "tests": [ + { + "description": "with only (valid) additional items", + "data": [true, false], + "valid": true + }, + { + "description": "with no additional items", + "data": ["yes", "no"], + "valid": true + }, + { + "description": "with invalid additional item", + "data": ["yes", false], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested prefixItems and items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ], + "items": true + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with nested unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ] + }, + { "unevaluatedItems": true } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "const": "foo" } + ], + "anyOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when one schema matches and has no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "when one schema matches and has unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + }, + { + "description": "when two schemas match and has no unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "when two schemas match and has unevaluated items", + "data": ["foo", "bar", "baz", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "const": "foo" } + ], + "oneOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with not", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "const": "foo" } + ], + "not": { + "not": { + "prefixItems": [ + true, + { "const": "bar" } + ] + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [ + { "const": "foo" } + ], + "if": { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + "then": { + "prefixItems": [ + true, + true, + { "const": "then" } + ] + }, + "else": { + "prefixItems": [ + true, + true, + true, + { "const": "else" } + ] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when if matches and it has no unevaluated items", + "data": ["foo", "bar", "then"], + "valid": true + }, + { + "description": "when if matches and it has unevaluated items", + "data": ["foo", "bar", "then", "else"], + "valid": false + }, + { + "description": "when if doesn't match and it has no unevaluated items", + "data": ["foo", 42, 42, "else"], + "valid": true + }, + { + "description": "when if doesn't match and it has unevaluated items", + "data": ["foo", 42, 42, "else", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [true], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$ref": "#/$defs/bar", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false, + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": false, + "prefixItems": [ + { "type": "string" } + ], + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://example.com/unevaluated-items-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "prefixItems": [ + true, + { "type": "string" } + ] + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedItems": false, + "type": "array", + "prefixItems": [ + { "type": "string" } + ], + "$dynamicRef": "#addons" + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "prefixItems": [ true ] + }, + { "unevaluatedItems": false } + ] + }, + "tests": [ + { + "description": "always fails", + "data": [ 1 ], + "valid": false + } + ] + }, + { + "description": "item is evaluated in an uncle schema to unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": { + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + } + ] + }, + "tests": [ + { + "description": "no extra items", + "data": { + "foo": [ + "test" + ] + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": [ + "test", + "test" + ] + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on adjacent contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [true], + "contains": {"type": "string"}, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "second item is evaluated by contains", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "contains fails, second item is not evaluated", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "contains passes, second item is not evaluated", + "data": [ 1, 2, "foo" ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on multiple nested contains", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { "contains": { "multipleOf": 2 } }, + { "contains": { "multipleOf": 3 } } + ], + "unevaluatedItems": { "multipleOf": 5 } + }, + "tests": [ + { + "description": "5 not evaluated, passes unevaluatedItems", + "data": [ 2, 3, 4, 5, 6 ], + "valid": true + }, + { + "description": "7 not evaluated, fails unevaluatedItems", + "data": [ 2, 3, 4, 7, 8 ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems and contains interact to control item dependency relationship", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "contains": {"const": "a"} + }, + "then": { + "if": { + "contains": {"const": "b"} + }, + "then": { + "if": { + "contains": {"const": "c"} + } + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "only a's are valid", + "data": [ "a", "a" ], + "valid": true + }, + { + "description": "a's and b's are valid", + "data": [ "a", "b", "a", "b", "a" ], + "valid": true + }, + { + "description": "a's, b's and c's are valid", + "data": [ "c", "a", "c", "c", "b", "a" ], + "valid": true + }, + { + "description": "only b's are invalid", + "data": [ "b", "b" ], + "valid": false + }, + { + "description": "only c's are invalid", + "data": [ "c", "c" ], + "valid": false + }, + { + "description": "only b's and c's are invalid", + "data": [ "c", "b", "c", "b", "c" ], + "valid": false + }, + { + "description": "only a's and c's are invalid", + "data": [ "c", "a", "c", "a", "c" ], + "valid": false + } + ] + }, + { + "description" : "unevaluatedItems with minContains = 0", + "schema" : { + "$schema": "https://json-schema.org/draft/next/schema", + "contains": {"type": "string"}, + "minContains": 0, + "unevaluatedItems": false + }, + "tests" : [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "no items evaluated by contains", + "data": [0], + "valid": false + }, + { + "description": "some but not all items evaluated by contains", + "data": ["foo", 0], + "valid": false + }, + { + "description": "all items evaluated by contains", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "non-array instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "prefixItems": [{"const": "a"}] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": [ "a" ], + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": [ "b" ], + "valid": false + } + + ] + } +] diff --git a/src/test/suite/tests/draft-next/unevaluatedProperties.json b/src/test/suite/tests/draft-next/unevaluatedProperties.json new file mode 100644 index 000000000..13fe6e03a --- /dev/null +++ b/src/test/suite/tests/draft-next/unevaluatedProperties.json @@ -0,0 +1,1676 @@ +[ + { + "description": "unevaluatedProperties true", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties schema", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "unevaluatedProperties": { + "type": "string", + "minLength": 3 + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with valid unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with invalid unevaluated properties", + "data": { + "foo": "fo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "patternProperties": { + "^foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "additionalProperties": true, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "properties": { + "bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "patternProperties": { + "^bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "additionalProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": { + "type": "string", + "maxLength": 2 + } + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "anyOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + }, + { + "properties": { + "quux": { "const": "quux" } + }, + "required": ["quux"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when one matches and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when one matches and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "not-baz" + }, + "valid": false + }, + { + "description": "when two match and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": true + }, + { + "description": "when two match and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "quux": "not-quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "oneOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "quux": "quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with not", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "not": { + "not": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, then not defined", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": false + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, else not defined", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with dependentSchemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [true], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "$ref": "#/$defs/bar", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false, + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "unevaluatedProperties": false, + "properties": { + "foo": { "type": "string" } + }, + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$id": "https://example.com/unevaluated-properties-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "properties": { + "bar": { "type": "string" } + } + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedProperties comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedProperties": false, + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "$dynamicRef": "#addons" + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "properties": { + "foo": true + } + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins (reverse order)", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "allOf": [ + { + "unevaluatedProperties": false + }, + { + "properties": { + "foo": true + } + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, true with properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, false with properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "unevaluatedProperties": true + }, + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "property is evaluated in an uncle schema to unevaluatedProperties", + "comment": "see https://stackoverflow.com/questions/66936884/deeply-nested-unevaluatedproperties-and-their-expectations", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "properties": { + "faz": { + "type": "string" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "no extra properties", + "data": { + "foo": { + "bar": "test" + } + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": { + "bar": "test", + "faz": "test" + } + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, allOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + }, + "unevaluatedProperties": false + } + ], + "anyOf": [ + { + "properties": { + "bar": true + } + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, anyOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + } + } + ], + "anyOf": [ + { + "properties": { + "bar": true + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties + single cyclic ref", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "type": "object", + "properties": { + "x": { "$ref": "#" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is valid", + "data": {}, + "valid": true + }, + { + "description": "Single is valid", + "data": { "x": {} }, + "valid": true + }, + { + "description": "Unevaluated on 1st level is invalid", + "data": { "x": {}, "y": {} }, + "valid": false + }, + { + "description": "Nested is valid", + "data": { "x": { "x": {} } }, + "valid": true + }, + { + "description": "Unevaluated on 2nd level is invalid", + "data": { "x": { "x": {}, "y": {} } }, + "valid": false + }, + { + "description": "Deep nested is valid", + "data": { "x": { "x": { "x": {} } } }, + "valid": true + }, + { + "description": "Unevaluated on 3rd level is invalid", + "data": { "x": { "x": { "x": {}, "y": {} } } }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties + ref inside allOf / oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "one": { + "properties": { "a": true } + }, + "two": { + "required": ["x"], + "properties": { "x": true } + } + }, + "allOf": [ + { "$ref": "#/$defs/one" }, + { "properties": { "b": true } }, + { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { + "required": ["y"], + "properties": { "y": true } + } + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid (no x or y)", + "data": {}, + "valid": false + }, + { + "description": "a and b are invalid (no x or y)", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "x and y are invalid", + "data": { "x": 1, "y": 1 }, + "valid": false + }, + { + "description": "a and x are valid", + "data": { "a": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and y are valid", + "data": { "a": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x are valid", + "data": { "a": 1, "b": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and b and y are valid", + "data": { "a": 1, "b": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x and y are invalid", + "data": { "a": 1, "b": 1, "x": 1, "y": 1 }, + "valid": false + } + ] + }, + { + "description": "dynamic evaluation inside nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "$defs": { + "one": { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { "required": ["b"], "properties": { "b": true } }, + { "required": ["xx"], "patternProperties": { "x": true } }, + { "required": ["all"], "unevaluatedProperties": true } + ] + }, + "two": { + "oneOf": [ + { "required": ["c"], "properties": { "c": true } }, + { "required": ["d"], "properties": { "d": true } } + ] + } + }, + "oneOf": [ + { "$ref": "#/$defs/one" }, + { "required": ["a"], "properties": { "a": true } } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid", + "data": {}, + "valid": false + }, + { + "description": "a is valid", + "data": { "a": 1 }, + "valid": true + }, + { + "description": "b is valid", + "data": { "b": 1 }, + "valid": true + }, + { + "description": "c is valid", + "data": { "c": 1 }, + "valid": true + }, + { + "description": "d is valid", + "data": { "d": 1 }, + "valid": true + }, + { + "description": "a + b is invalid", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "a + c is invalid", + "data": { "a": 1, "c": 1 }, + "valid": false + }, + { + "description": "a + d is invalid", + "data": { "a": 1, "d": 1 }, + "valid": false + }, + { + "description": "b + c is invalid", + "data": { "b": 1, "c": 1 }, + "valid": false + }, + { + "description": "b + d is invalid", + "data": { "b": 1, "d": 1 }, + "valid": false + }, + { + "description": "c + d is invalid", + "data": { "c": 1, "d": 1 }, + "valid": false + }, + { + "description": "xx is valid", + "data": { "xx": 1 }, + "valid": true + }, + { + "description": "xx + foox is valid", + "data": { "xx": 1, "foox": 1 }, + "valid": true + }, + { + "description": "xx + foo is invalid", + "data": { "xx": 1, "foo": 1 }, + "valid": false + }, + { + "description": "xx + a is invalid", + "data": { "xx": 1, "a": 1 }, + "valid": false + }, + { + "description": "xx + b is invalid", + "data": { "xx": 1, "b": 1 }, + "valid": false + }, + { + "description": "xx + c is invalid", + "data": { "xx": 1, "c": 1 }, + "valid": false + }, + { + "description": "xx + d is invalid", + "data": { "xx": 1, "d": 1 }, + "valid": false + }, + { + "description": "all is valid", + "data": { "all": 1 }, + "valid": true + }, + { + "description": "all + foo is valid", + "data": { "all": 1, "foo": 1 }, + "valid": true + }, + { + "description": "all + a is invalid", + "data": { "all": 1, "a": 1 }, + "valid": false + } + ] + }, + { + "description": "non-object instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "unevaluatedProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null valued properties", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties can see inside propertyDependencies", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": { + "foo": { + "type": "string" + } + }, + "propertyDependencies": { + "foo": { + "foo1": { + "properties": { + "bar": true + } + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "allows bar if foo = foo1", + "data": { + "foo": "foo1", + "bar": 42 + }, + "valid": true + }, + { + "description": "disallows bar if foo != foo1", + "data": { + "foo": "foo2", + "bar": 42 + }, + "valid": false + }, + { + "description": "disallows bar if foo is absent", + "data": { + "bar": 42 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties not affected by propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "propertyNames": {"maxLength": 1}, + "unevaluatedProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "allows only number properties", + "data": {"a": 1}, + "valid": true + }, + { + "description": "string property is invalid", + "data": {"a": "b"}, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "if": { + "patternProperties": { + "foo": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": { + "foo": "a" + }, + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": { + "bar": "a" + }, + "valid": false + } + ] + }, + { + "description": "propertyDependencies with unevaluatedProperties" , + "schema" : { + "$schema": "https://json-schema.org/draft/next/schema", + "properties" : {"foo2" : {}}, + "propertyDependencies": { + "foo" : {}, + "foo2": { + "bar": { + "properties": { + "buz": {} + } + } + } + }, + "unevaluatedProperties": false + }, + + "tests": [ + { + "description": "unevaluatedProperties doesn't consider propertyDependencies" , + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "unevaluatedProperties sees buz when foo2 is present", + "data": {"foo2": "bar", "buz": ""}, + "valid": true + }, + { + "description": "unevaluatedProperties doesn't see buz when foo2 is absent", + "data": {"buz": ""}, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar":{} + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluatedProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties doesn't see bar when foo2 is absent", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties sees bar when foo2 is present", + "data": {"foo2": "", "bar": ""}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/uniqueItems.json b/src/test/suite/tests/draft-next/uniqueItems.json new file mode 100644 index 000000000..b16dd505b --- /dev/null +++ b/src/test/suite/tests/draft-next/uniqueItems.json @@ -0,0 +1,419 @@ +[ + { + "description": "uniqueItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "uniqueItems": true + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "uniqueItems": false + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/next/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft-next/vocabulary.json b/src/test/suite/tests/draft-next/vocabulary.json new file mode 100644 index 000000000..f25b9e125 --- /dev/null +++ b/src/test/suite/tests/draft-next/vocabulary.json @@ -0,0 +1,57 @@ +[ + { + "description": "schema that uses custom metaschema with with no validation vocabulary", + "schema": { + "$id": "https://schema/using/no/validation", + "$schema": "http://localhost:1234/draft-next/metaschema-no-validation.json", + "properties": { + "badProperty": false, + "numberProperty": { + "minimum": 10 + } + } + }, + "tests": [ + { + "description": "applicator vocabulary still works", + "data": { + "badProperty": "this property should not exist" + }, + "valid": false + }, + { + "description": "no validation: valid number", + "data": { + "numberProperty": 20 + }, + "valid": true + }, + { + "description": "no validation: invalid number, but it still validates", + "data": { + "numberProperty": 1 + }, + "valid": true + } + ] + }, + { + "description": "ignore unrecognized optional vocabulary", + "schema": { + "$schema": "http://localhost:1234/draft-next/metaschema-optional-vocabulary.json", + "type": "number" + }, + "tests": [ + { + "description": "string value", + "data": "foobar", + "valid": false + }, + { + "description": "number value", + "data": 20, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/additionalItems.json b/src/test/suite/tests/draft2019-09/additionalItems.json new file mode 100644 index 000000000..9a7ae4f8a --- /dev/null +++ b/src/test/suite/tests/draft2019-09/additionalItems.json @@ -0,0 +1,221 @@ +[ + { + "description": "additionalItems as schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{}], + "additionalItems": {"type": "integer"} + }, + "tests": [ + { + "description": "additional items match schema", + "data": [ null, 2, 3, 4 ], + "valid": true + }, + { + "description": "additional items do not match schema", + "data": [ null, 2, 3, "foo" ], + "valid": false + } + ] + }, + { + "description": "when items is schema, additionalItems does nothing", + "schema": { + "$schema":"https://json-schema.org/draft/2019-09/schema", + "items": { + "type": "integer" + }, + "additionalItems": { + "type": "string" + } + }, + "tests": [ + { + "description": "valid with a array of type integers", + "data": [1,2,3], + "valid": true + }, + { + "description": "invalid with a array of mixed types", + "data": [1,"2","3"], + "valid": false + } + ] + }, + { + "description": "when items is schema, boolean additionalItems does nothing", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": {}, + "additionalItems": false + }, + "tests": [ + { + "description": "all items match schema", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + } + ] + }, + { + "description": "array of items with no additionalItems permitted", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{}, {}, {}], + "additionalItems": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "additionalItems as false without items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "additionalItems": false + }, + "tests": [ + { + "description": + "items defaults to empty schema so everything is valid", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "additionalItems are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{"type": "integer"}] + }, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, valid case", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { "items": [ { "type": "integer" } ] } + ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, null ], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, invalid case", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { "items": [ { "type": "integer" }, { "type": "string" } ] } + ], + "items": [ {"type": "integer" } ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, "hello" ], + "valid": false + } + ] + }, + { + "description": "items validation adjusts the starting index for additionalItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ { "type": "string" } ], + "additionalItems": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "additionalItems with heterogeneous array", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{}], + "additionalItems": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "additionalItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/additionalProperties.json b/src/test/suite/tests/draft2019-09/additionalProperties.json new file mode 100644 index 000000000..73f9b909e --- /dev/null +++ b/src/test/suite/tests/draft2019-09/additionalProperties.json @@ -0,0 +1,213 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"foo": {}, "bar": {}} + }, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "additionalProperties with propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": { + "maxLength": 5 + }, + "additionalProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "Valid against both keywords", + "data": { "apple": 4 }, + "valid": true + }, + { + "description": "Valid against propertyNames, but not additionalProperties", + "data": { "fig": 2, "pear": "available" }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar":{} + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "additionalProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar even when foo2 is present", + "data": { "foo2": "", "bar": ""}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/allOf.json b/src/test/suite/tests/draft2019-09/allOf.json new file mode 100644 index 000000000..dec267e17 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/allOf.json @@ -0,0 +1,312 @@ +[ + { + "description": "allOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/anchor.json b/src/test/suite/tests/draft2019-09/anchor.json new file mode 100644 index 000000000..bce05e800 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/anchor.json @@ -0,0 +1,120 @@ +[ + { + "description": "Location-independent identifier", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#foo", + "$defs": { + "A": { + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with absolute URI", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/bar#foo", + "$defs": { + "A": { + "$id": "http://localhost:1234/draft2019-09/bar", + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/root", + "$ref": "http://localhost:1234/draft2019-09/nested.json#foo", + "$defs": { + "A": { + "$id": "nested.json", + "$defs": { + "B": { + "$anchor": "foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "same $anchor with different base uri", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/foobar", + "$defs": { + "A": { + "$id": "child1", + "allOf": [ + { + "$id": "child2", + "$anchor": "my_anchor", + "type": "number" + }, + { + "$anchor": "my_anchor", + "type": "string" + } + ] + } + }, + "$ref": "child1#my_anchor" + }, + "tests": [ + { + "description": "$ref resolves to /$defs/A/allOf/1", + "data": "a", + "valid": true + }, + { + "description": "$ref does not resolve to /$defs/A/allOf/0", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/anyOf.json b/src/test/suite/tests/draft2019-09/anyOf.json new file mode 100644 index 000000000..8712d114c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/anyOf.json @@ -0,0 +1,203 @@ +[ + { + "description": "anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [true, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/boolean_schema.json b/src/test/suite/tests/draft2019-09/boolean_schema.json new file mode 100644 index 000000000..6d40f23f2 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/const.json b/src/test/suite/tests/draft2019-09/const.json new file mode 100644 index 000000000..29700fda3 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/const.json @@ -0,0 +1,387 @@ +[ + { + "description": "const validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": 2 + }, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": {"foo": "bar", "baz": "bax"} + }, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": [{ "foo": "bar" }] + }, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": null + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": false + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": true + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": [false] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": [true] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": {"a": false} + }, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": {"a": true} + }, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": 0 + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": 1 + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": -2.0 + }, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": 9007199254740992 + }, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "const": "hello\u0000there" + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/contains.json b/src/test/suite/tests/draft2019-09/contains.json new file mode 100644 index 000000000..30458bf49 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/contains.json @@ -0,0 +1,176 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"minimum": 5} + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": true + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid", + "data": "contains does not apply to strings", + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [ 2, 4, 8 ], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [ 3, 6, 9 ], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [ 6, 12 ], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [ 1, 5 ], + "valid": false + } + ] + }, + { + "description": "contains with false if subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": { + "if": false, + "else": true + } + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/content.json b/src/test/suite/tests/draft2019-09/content.json new file mode 100644 index 000000000..2a7a5d871 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/content.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of string-encoded content based on media type", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contentMediaType": "application/json" + }, + "tests": [ + { + "description": "a valid JSON document", + "data": "{\"foo\": \"bar\"}", + "valid": true + }, + { + "description": "an invalid JSON document; validates true", + "data": "{:}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary string-encoding", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64 string", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "an invalid base64 string (% is not a valid character); validates true", + "data": "eyJmb28iOi%iYmFyIn0K", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents with schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64", + "contentSchema": { "type": "object", "required": ["foo"], "properties": { "foo": { "type": "string" } } } + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "another valid base64-encoded JSON document", + "data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==", + "valid": true + }, + { + "description": "an invalid base64-encoded JSON document; validates true", + "data": "eyJib28iOiAyMH0=", + "valid": true + }, + { + "description": "an empty object as a base64-encoded JSON document; validates true", + "data": "e30=", + "valid": true + }, + { + "description": "an empty array as a base64-encoded JSON document", + "data": "W10=", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/default.json b/src/test/suite/tests/draft2019-09/default.json new file mode 100644 index 000000000..95eba5a79 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/default.json @@ -0,0 +1,82 @@ +[ + { + "description": "invalid type for default", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/defs.json b/src/test/suite/tests/draft2019-09/defs.json new file mode 100644 index 000000000..3f9088c3c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/defs.json @@ -0,0 +1,21 @@ +[ + { + "description": "validate definition against metaschema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "https://json-schema.org/draft/2019-09/schema" + }, + "tests": [ + { + "description": "valid definition schema", + "data": {"$defs": {"foo": {"type": "integer"}}}, + "valid": true + }, + { + "description": "invalid definition schema", + "data": {"$defs": {"foo": {"type": 1}}}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/dependentRequired.json b/src/test/suite/tests/draft2019-09/dependentRequired.json new file mode 100644 index 000000000..d573c10b8 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/dependentRequired.json @@ -0,0 +1,152 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentRequired": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentRequired": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentRequired": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentRequired": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/dependentSchemas.json b/src/test/suite/tests/draft2019-09/dependentSchemas.json new file mode 100644 index 000000000..c5b8ea05f --- /dev/null +++ b/src/test/suite/tests/draft2019-09/dependentSchemas.json @@ -0,0 +1,171 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentSchemas": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentSchemas": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependentSchemas": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {} + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/enum.json b/src/test/suite/tests/draft2019-09/enum.json new file mode 100644 index 000000000..1315211ea --- /dev/null +++ b/src/test/suite/tests/draft2019-09/enum.json @@ -0,0 +1,358 @@ +[ + { + "description": "simple enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [1, 2, 3] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [6, "foo", [], true, {"foo": 12}] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [6, null] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [false] + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [[false]] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [true] + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [[true]] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [0] + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [[0]] + }, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [1] + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [[1]] + }, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "enum": [ "hello\u0000there" ] + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/exclusiveMaximum.json b/src/test/suite/tests/draft2019-09/exclusiveMaximum.json new file mode 100644 index 000000000..4ec85698c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/exclusiveMaximum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/exclusiveMinimum.json b/src/test/suite/tests/draft2019-09/exclusiveMinimum.json new file mode 100644 index 000000000..24f468984 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/exclusiveMinimum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/format.json b/src/test/suite/tests/draft2019-09/format.json new file mode 100644 index 000000000..2934dcef9 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/format.json @@ -0,0 +1,743 @@ +[ + { + "description": "email format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "idn-email format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "idn-hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "relative-json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "iri format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "iri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "duration format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/if-then-else.json b/src/test/suite/tests/draft2019-09/if-then-else.json new file mode 100644 index 000000000..510a0e0ac --- /dev/null +++ b/src/test/suite/tests/draft2019-09/if-then-else.json @@ -0,0 +1,268 @@ +[ + { + "description": "ignore if without then or else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone if", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone if", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore then without if", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "then": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone then", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone then", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore else without if", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "else": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone else", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone else", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "if and then without else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid when if test fails", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if and else without then", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid when if test passes", + "data": -1, + "valid": true + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "validate against correct branch, then vs else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "non-interference across combined schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] + }, + "tests": [ + { + "description": "valid, but would have been invalid through then", + "data": -100, + "valid": true + }, + { + "description": "valid, but would have been invalid through else", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": true, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema true in if always chooses the then path (valid)", + "data": "then", + "valid": true + }, + { + "description": "boolean schema true in if always chooses the then path (invalid)", + "data": "else", + "valid": false + } + ] + }, + { + "description": "if with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": false, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema false in if always chooses the else path (invalid)", + "data": "then", + "valid": false + }, + { + "description": "boolean schema false in if always chooses the else path (valid)", + "data": "else", + "valid": true + } + ] + }, + { + "description": "if appears at the end when serialized (keyword processing sequence)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "then": { "const": "yes" }, + "else": { "const": "other" }, + "if": { "maxLength": 4 } + }, + "tests": [ + { + "description": "yes redirects to then and passes", + "data": "yes", + "valid": true + }, + { + "description": "other redirects to else and passes", + "data": "other", + "valid": true + }, + { + "description": "no redirects to then and fails", + "data": "no", + "valid": false + }, + { + "description": "invalid redirects to else and fails", + "data": "invalid", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/infinite-loop-detection.json b/src/test/suite/tests/draft2019-09/infinite-loop-detection.json new file mode 100644 index 000000000..eb6941454 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/infinite-loop-detection.json @@ -0,0 +1,37 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/$defs/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/$defs/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/items.json b/src/test/suite/tests/draft2019-09/items.json new file mode 100644 index 000000000..e24156a7c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/items.json @@ -0,0 +1,295 @@ +[ + { + "description": "a schema given for items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": true + }, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "item": { + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/$defs/sub-item" }, + { "$ref": "#/$defs/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "single-form items with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "array-form items with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/maxContains.json b/src/test/suite/tests/draft2019-09/maxContains.json new file mode 100644 index 000000000..ce4507c32 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/maxContains.json @@ -0,0 +1,102 @@ +[ + { + "description": "maxContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "two items still valid against lone maxContains", + "data": [ 1, 2 ], + "valid": true + } + ] + }, + { + "description": "maxContains with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + }, + { + "description": "some elements match, valid maxContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "some elements match, invalid maxContains", + "data": [ 1, 2, 1 ], + "valid": false + } + ] + }, + { + "description": "maxContains with contains, value with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "maxContains": 1.0 + }, + "tests": [ + { + "description": "one element matches, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains < maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 1, + "maxContains": 3 + }, + "tests": [ + { + "description": "actual < minContains < maxContains", + "data": [ ], + "valid": false + }, + { + "description": "minContains < actual < maxContains", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "minContains < maxContains < actual", + "data": [ 1, 1, 1, 1 ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/maxItems.json b/src/test/suite/tests/draft2019-09/maxItems.json new file mode 100644 index 000000000..d9ed15703 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/maxItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "maxItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxItems": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxItems": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/maxLength.json b/src/test/suite/tests/draft2019-09/maxLength.json new file mode 100644 index 000000000..a0cc7d9b8 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/maxLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "maxLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxLength": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxLength": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/maxProperties.json b/src/test/suite/tests/draft2019-09/maxProperties.json new file mode 100644 index 000000000..5b31474cf --- /dev/null +++ b/src/test/suite/tests/draft2019-09/maxProperties.json @@ -0,0 +1,79 @@ +[ + { + "description": "maxProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxProperties": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxProperties": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maxProperties": 0 + }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/maximum.json b/src/test/suite/tests/draft2019-09/maximum.json new file mode 100644 index 000000000..c1f1dfd86 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/maximum.json @@ -0,0 +1,60 @@ +[ + { + "description": "maximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maximum": 300 + }, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/minContains.json b/src/test/suite/tests/draft2019-09/minContains.json new file mode 100644 index 000000000..8d3093c47 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/minContains.json @@ -0,0 +1,224 @@ +[ + { + "description": "minContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "zero items still valid against lone minContains", + "data": [], + "valid": true + } + ] + }, + { + "description": "minContains=1 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "no elements match", + "data": [ 2 ], + "valid": false + }, + { + "description": "single element matches, valid minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "all elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "some elements match, invalid minContains", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "all elements match, valid minContains (exactly as needed)", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "all elements match, valid minContains (more than needed)", + "data": [ 1, 1, 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains with a decimal value", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 2.0 + }, + "tests": [ + { + "description": "one element matches, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "both elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains = minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "maxContains": 2, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "all elements match, valid maxContains and minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains < minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "maxContains": 1, + "minContains": 3 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "invalid maxContains and minContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains = 0 with no maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 0 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "minContains = 0 makes contains always pass", + "data": [ 2 ], + "valid": true + } + ] + }, + { + "description": "minContains = 0 with maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": {"const": 1}, + "minContains": 0, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "not more than maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many", + "data": [ 1, 1 ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/minItems.json b/src/test/suite/tests/draft2019-09/minItems.json new file mode 100644 index 000000000..07817cc28 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/minItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "minItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minItems": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minItems": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/minLength.json b/src/test/suite/tests/draft2019-09/minLength.json new file mode 100644 index 000000000..12782660c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/minLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "minLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minLength": 2 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minLength": 2.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/minProperties.json b/src/test/suite/tests/draft2019-09/minProperties.json new file mode 100644 index 000000000..20e01a9e2 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/minProperties.json @@ -0,0 +1,60 @@ +[ + { + "description": "minProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minProperties": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minProperties": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/minimum.json b/src/test/suite/tests/draft2019-09/minimum.json new file mode 100644 index 000000000..afb5f200a --- /dev/null +++ b/src/test/suite/tests/draft2019-09/minimum.json @@ -0,0 +1,75 @@ +[ + { + "description": "minimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minimum": 1.1 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minimum": -2 + }, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/multipleOf.json b/src/test/suite/tests/draft2019-09/multipleOf.json new file mode 100644 index 000000000..760a434ca --- /dev/null +++ b/src/test/suite/tests/draft2019-09/multipleOf.json @@ -0,0 +1,97 @@ +[ + { + "description": "by int", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "multipleOf": 2 + }, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "multipleOf": 1.5 + }, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "multipleOf": 0.0001 + }, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", "multipleOf": 0.123456789 + }, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", "multipleOf": 1e-8 + }, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/not.json b/src/test/suite/tests/draft2019-09/not.json new file mode 100644 index 000000000..d90728c7b --- /dev/null +++ b/src/test/suite/tests/draft2019-09/not.json @@ -0,0 +1,301 @@ +[ + { + "description": "not", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": {} + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "forbid everything with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": true + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "allow everything with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": false + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "double negation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { "not": {} } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "collect annotations inside a 'not', even if collection is disabled", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { + "$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them", + "anyOf": [ + true, + { "properties": { "foo": true } } + ], + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "unevaluated property", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "annotations are still collected inside a 'not'", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/oneOf.json b/src/test/suite/tests/draft2019-09/oneOf.json new file mode 100644 index 000000000..c27d4865c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/oneOf.json @@ -0,0 +1,293 @@ +[ + { + "description": "oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [true, true, true] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [true, false, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [true, true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [false, false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/anchor.json b/src/test/suite/tests/draft2019-09/optional/anchor.json new file mode 100644 index 000000000..45951d0a3 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/anchor.json @@ -0,0 +1,60 @@ +[ + { + "description": "$anchor inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $anchor buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "anchor_in_enum": { + "enum": [ + { + "$anchor": "my_anchor", + "type": "null" + } + ] + }, + "real_identifier_in_schema": { + "$anchor": "my_anchor", + "type": "string" + }, + "zzz_anchor_in_const": { + "const": { + "$anchor": "my_anchor", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/anchor_in_enum" }, + { "$ref": "#my_anchor" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$anchor": "my_anchor", + "type": "null" + }, + "valid": true + }, + { + "description": "in implementations that strip $anchor, this may match either $def", + "data": { + "type": "null" + }, + "valid": false + }, + { + "description": "match $ref to $anchor", + "data": "a string to match #/$defs/anchor_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $anchor", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/bignum.json b/src/test/suite/tests/draft2019-09/optional/bignum.json new file mode 100644 index 000000000..8b0646776 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/bignum.json @@ -0,0 +1,110 @@ +[ + { + "description": "integer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" + }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "number" + }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" + }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "maximum": 18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMaximum": 972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "minimum": -18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": -972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/cross-draft.json b/src/test/suite/tests/draft2019-09/optional/cross-draft.json new file mode 100644 index 000000000..efd3f87dc --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/cross-draft.json @@ -0,0 +1,41 @@ +[ + { + "description": "refs to future drafts are processed as future drafts", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "array", + "$ref": "http://localhost:1234/draft2020-12/prefixItems.json" + }, + "tests": [ + { + "description": "first item not a string is invalid", + "comment": "if the implementation is not processing the $ref as a 2020-12 schema, this test will fail", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "first item is a string is valid", + "data": ["a string", 1, 2, 3], + "valid": true + } + ] + }, + { + "description": "refs to historic drafts are processed as historic drafts", + "schema": { + "type": "object", + "allOf": [ + { "properties": { "foo": true } }, + { "$ref": "http://localhost:1234/draft7/ignore-dependentRequired.json" } + ] + }, + "tests": [ + { + "description": "missing bar is valid", + "comment": "if the implementation is not processing the $ref as a draft 7 schema, this test will fail", + "data": {"foo": "any value"}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/dependencies-compatibility.json b/src/test/suite/tests/draft2019-09/optional/dependencies-compatibility.json new file mode 100644 index 000000000..5bfbd058f --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/dependencies-compatibility.json @@ -0,0 +1,282 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "single schema dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "schema dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "dependencies": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/ecmascript-regex.json b/src/test/suite/tests/draft2019-09/optional/ecmascript-regex.json new file mode 100644 index 000000000..be1059c9e --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/ecmascript-regex.json @@ -0,0 +1,582 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "\\p{Letter}cole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "\\wcole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "[a-z]cole" + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "^\\d+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "^\\p{digit}+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "\\p{Letter}cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "\\wcole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "[a-z]cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "^\\d+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "^\\p{digit}+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/float-overflow.json b/src/test/suite/tests/draft2019-09/optional/float-overflow.json new file mode 100644 index 000000000..f5741facc --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/float-overflow.json @@ -0,0 +1,16 @@ +[ + { + "description": "all integers are multiples of 0.5, if overflow is handled", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", "multipleOf": 0.5 + }, + "tests": [ + { + "description": "valid if optional overflow handling is implemented", + "data": 1e308, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/date-time.json b/src/test/suite/tests/draft2019-09/optional/format/date-time.json new file mode 100644 index 000000000..731001d50 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/date-time.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of date-time strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/date.json b/src/test/suite/tests/draft2019-09/optional/format/date.json new file mode 100644 index 000000000..805888c2b --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/date.json @@ -0,0 +1,246 @@ +[ + { + "description": "validation of date strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date string", + "data": "1963-06-19", + "valid": true + }, + { + "description": "a valid date string with 31 days in January", + "data": "2020-01-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in January", + "data": "2020-01-32", + "valid": false + }, + { + "description": "a valid date string with 28 days in February (normal)", + "data": "2021-02-28", + "valid": true + }, + { + "description": "a invalid date string with 29 days in February (normal)", + "data": "2021-02-29", + "valid": false + }, + { + "description": "a valid date string with 29 days in February (leap)", + "data": "2020-02-29", + "valid": true + }, + { + "description": "a invalid date string with 30 days in February (leap)", + "data": "2020-02-30", + "valid": false + }, + { + "description": "a valid date string with 31 days in March", + "data": "2020-03-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in March", + "data": "2020-03-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in April", + "data": "2020-04-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in April", + "data": "2020-04-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in May", + "data": "2020-05-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in May", + "data": "2020-05-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in June", + "data": "2020-06-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in June", + "data": "2020-06-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in July", + "data": "2020-07-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in July", + "data": "2020-07-32", + "valid": false + }, + { + "description": "a valid date string with 31 days in August", + "data": "2020-08-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in August", + "data": "2020-08-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in September", + "data": "2020-09-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in September", + "data": "2020-09-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in October", + "data": "2020-10-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in October", + "data": "2020-10-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in November", + "data": "2020-11-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in November", + "data": "2020-11-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in December", + "data": "2020-12-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in December", + "data": "2020-12-32", + "valid": false + }, + { + "description": "a invalid date string with invalid month", + "data": "2020-13-01", + "valid": false + }, + { + "description": "an invalid date string", + "data": "06/19/1963", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "non-padded month dates are not valid", + "data": "1998-1-20", + "valid": false + }, + { + "description": "non-padded day dates are not valid", + "data": "1998-01-1", + "valid": false + }, + { + "description": "invalid month", + "data": "1998-13-01", + "valid": false + }, + { + "description": "invalid month-day combination", + "data": "1998-04-31", + "valid": false + }, + { + "description": "2021 is not a leap year", + "data": "2021-02-29", + "valid": false + }, + { + "description": "2020 is a leap year", + "data": "2020-02-29", + "valid": true + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1963-06-1৪", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", + "data": "20230328", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", + "data": "2023-W01", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", + "data": "2023-W13-2", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", + "data": "2022W527", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/duration.json b/src/test/suite/tests/draft2019-09/optional/format/duration.json new file mode 100644 index 000000000..2d515a64a --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/duration.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of duration strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid duration string", + "data": "P4DT12H30M5S", + "valid": true + }, + { + "description": "an invalid duration string", + "data": "PT1D", + "valid": false + }, + { + "description": "must start with P", + "data": "4DT12H30M5S", + "valid": false + }, + { + "description": "no elements present", + "data": "P", + "valid": false + }, + { + "description": "no time elements present", + "data": "P1YT", + "valid": false + }, + { + "description": "no date or time elements present", + "data": "PT", + "valid": false + }, + { + "description": "elements out of order", + "data": "P2D1Y", + "valid": false + }, + { + "description": "missing time separator", + "data": "P1D2H", + "valid": false + }, + { + "description": "time element in the date position", + "data": "P2S", + "valid": false + }, + { + "description": "four years duration", + "data": "P4Y", + "valid": true + }, + { + "description": "zero time, in seconds", + "data": "PT0S", + "valid": true + }, + { + "description": "zero time, in days", + "data": "P0D", + "valid": true + }, + { + "description": "one month duration", + "data": "P1M", + "valid": true + }, + { + "description": "one minute duration", + "data": "PT1M", + "valid": true + }, + { + "description": "one and a half days, in hours", + "data": "PT36H", + "valid": true + }, + { + "description": "one and a half days, in days and hours", + "data": "P1DT12H", + "valid": true + }, + { + "description": "two weeks", + "data": "P2W", + "valid": true + }, + { + "description": "weeks cannot be combined with other units", + "data": "P1Y2W", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "P২Y", + "valid": false + }, + { + "description": "element without unit", + "data": "P1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/email.json b/src/test/suite/tests/draft2019-09/optional/format/email.json new file mode 100644 index 000000000..ea161eb2d --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/email.json @@ -0,0 +1,96 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/hostname.json b/src/test/suite/tests/draft2019-09/optional/format/hostname.json new file mode 100644 index 000000000..24bfdfc5a --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/hostname.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of host names", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/idn-email.json b/src/test/suite/tests/draft2019-09/optional/format/idn-email.json new file mode 100644 index 000000000..baf01d0aa --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/idn-email.json @@ -0,0 +1,61 @@ +[ + { + "description": "validation of an internationalized e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid idn e-mail (example@example.test in Hangul)", + "data": "실례@실례.테스트", + "valid": true + }, + { + "description": "an invalid idn e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json b/src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json new file mode 100644 index 000000000..348c504c8 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json @@ -0,0 +1,389 @@ +[ + { + "description": "validation of internationalized host names", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name (example.test in Hangul)", + "data": "실례.테스트", + "valid": true + }, + { + "description": "illegal first char U+302E Hangul single dot tone mark", + "data": "〮실례.테스트", + "valid": false + }, + { + "description": "contains illegal char U+302E Hangul single dot tone mark", + "data": "실〮례.테스트", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", + "valid": false + }, + { + "description": "invalid label, correct Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc3492#section-7.1", + "data": "-> $1.00 <--", + "valid": false + }, + { + "description": "valid Chinese Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4", + "data": "xn--ihqwcrb4cv8a8dqg056pqjye", + "valid": true + }, + { + "description": "invalid Punycode", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "xn--X", + "valid": false + }, + { + "description": "U-label contains \"--\" in the 3rd and 4th position", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "XN--aa---o47jg78q", + "valid": false + }, + { + "description": "U-label starts with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello", + "valid": false + }, + { + "description": "U-label ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "hello-", + "valid": false + }, + { + "description": "U-label starts and ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello-", + "valid": false + }, + { + "description": "Begins with a Spacing Combining Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0903hello", + "valid": false + }, + { + "description": "Begins with a Nonspacing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0300hello", + "valid": false + }, + { + "description": "Begins with an Enclosing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0488hello", + "valid": false + }, + { + "description": "Exceptions that are PVALID, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u00df\u03c2\u0f0b\u3007", + "valid": true + }, + { + "description": "Exceptions that are PVALID, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u06fd\u06fe", + "valid": true + }, + { + "description": "Exceptions that are DISALLOWED, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u0640\u07fa", + "valid": false + }, + { + "description": "Exceptions that are DISALLOWED, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6 Note: The two combining marks (U+302E and U+302F) are in the middle and not at the start", + "data": "\u3031\u3032\u3033\u3034\u3035\u302e\u302f\u303b", + "valid": false + }, + { + "description": "MIDDLE DOT with no preceding 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "a\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing preceding", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with no following 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7a", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing following", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7", + "valid": false + }, + { + "description": "MIDDLE DOT with surrounding 'l's", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7l", + "valid": true + }, + { + "description": "Greek KERAIA not followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375S", + "valid": false + }, + { + "description": "Greek KERAIA not followed by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375", + "valid": false + }, + { + "description": "Greek KERAIA followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375\u03b2", + "valid": true + }, + { + "description": "Hebrew GERESH not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "A\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05d0\u05f3\u05d1", + "valid": true + }, + { + "description": "Hebrew GERSHAYIM not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "A\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05d0\u05f4\u05d1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "def\u30fbabc", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with no other characters", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with Hiragana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u3041", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Katakana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u30a1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u4e08", + "valid": true + }, + { + "description": "Arabic-Indic digits mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u06f0", + "valid": false + }, + { + "description": "Arabic-Indic digits not mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u0628", + "valid": true + }, + { + "description": "Extended Arabic-Indic digits not mixed with Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.9", + "data": "\u06f00", + "valid": true + }, + { + "description": "ZERO WIDTH JOINER not preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u094d\u200d\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1", + "data": "\u0915\u094d\u200c\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER not preceded by Virama but matches regexp", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1 https://www.w3.org/TR/alreq/#h_disjoining_enforcement", + "data": "\u0628\u064a\u200c\u0628\u064a", + "valid": true + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + } + ] + }, + { + "description": "validation of separators in internationalized host names", + "specification": [ + {"rfc3490": "3.1", "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)"} + ], + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "single dot", + "data": ".", + "valid": false + }, + { + "description": "single ideographic full stop", + "data": "\u3002", + "valid": false + }, + { + "description": "single fullwidth full stop", + "data": "\uff0e", + "valid": false + }, + { + "description": "single halfwidth ideographic full stop", + "data": "\uff61", + "valid": false + }, + { + "description": "dot as label separator", + "data": "a.b", + "valid": true + }, + { + "description": "ideographic full stop as label separator", + "data": "a\u3002b", + "valid": true + }, + { + "description": "fullwidth full stop as label separator", + "data": "a\uff0eb", + "valid": true + }, + { + "description": "halfwidth ideographic full stop as label separator", + "data": "a\uff61b", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/ipv4.json b/src/test/suite/tests/draft2019-09/optional/format/ipv4.json new file mode 100644 index 000000000..efe42471b --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/ipv4.json @@ -0,0 +1,92 @@ +[ + { + "description": "validation of IP addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/ipv6.json b/src/test/suite/tests/draft2019-09/optional/format/ipv6.json new file mode 100644 index 000000000..04860917c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/ipv6.json @@ -0,0 +1,211 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/iri-reference.json b/src/test/suite/tests/draft2019-09/optional/format/iri-reference.json new file mode 100644 index 000000000..69142102b --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/iri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of IRI References", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid protocol-relative IRI Reference", + "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid relative IRI Reference", + "data": "/âππ", + "valid": true + }, + { + "description": "an invalid IRI Reference", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "a valid IRI Reference", + "data": "âππ", + "valid": true + }, + { + "description": "a valid IRI fragment", + "data": "#ƒrägmênt", + "valid": true + }, + { + "description": "an invalid IRI fragment", + "data": "#ƒräg\\mênt", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/iri.json b/src/test/suite/tests/draft2019-09/optional/format/iri.json new file mode 100644 index 000000000..ad4c79e83 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/iri.json @@ -0,0 +1,86 @@ +[ + { + "description": "validation of IRIs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI with anchor tag", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid IRI with anchor tag and parentheses", + "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", + "valid": true + }, + { + "description": "a valid IRI with URL-encoded stuff", + "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid IRI with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid IRI based on IPv6", + "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + "valid": true + }, + { + "description": "an invalid IRI based on IPv6", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": false + }, + { + "description": "an invalid relative IRI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid IRI", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "an invalid IRI though valid IRI reference", + "data": "âππ", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/json-pointer.json b/src/test/suite/tests/draft2019-09/optional/format/json-pointer.json new file mode 100644 index 000000000..39f1cc923 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/json-pointer.json @@ -0,0 +1,201 @@ +[ + { + "description": "validation of JSON-pointers (JSON String Representation)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid JSON-pointer", + "data": "/foo/bar~0/baz~1/%a", + "valid": true + }, + { + "description": "not a valid JSON-pointer (~ not escaped)", + "data": "/foo/bar~", + "valid": false + }, + { + "description": "valid JSON-pointer with empty segment", + "data": "/foo//bar", + "valid": true + }, + { + "description": "valid JSON-pointer with the last empty segment", + "data": "/foo/bar/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #1", + "data": "", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #2", + "data": "/foo", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #3", + "data": "/foo/0", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #4", + "data": "/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #5", + "data": "/a~1b", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #6", + "data": "/c%d", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #7", + "data": "/e^f", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #8", + "data": "/g|h", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #9", + "data": "/i\\j", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #10", + "data": "/k\"l", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #11", + "data": "/ ", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #12", + "data": "/m~0n", + "valid": true + }, + { + "description": "valid JSON-pointer used adding to the last array position", + "data": "/foo/-", + "valid": true + }, + { + "description": "valid JSON-pointer (- used as object member name)", + "data": "/foo/-/bar", + "valid": true + }, + { + "description": "valid JSON-pointer (multiple escaped characters)", + "data": "/~1~0~0~1~1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #1", + "data": "/~1.1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #2", + "data": "/~0.1", + "valid": true + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", + "data": "#", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", + "data": "#/", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", + "data": "#a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #1", + "data": "/~0~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #2", + "data": "/~0/~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #1", + "data": "/~2", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #2", + "data": "/~-1", + "valid": false + }, + { + "description": "not a valid JSON-pointer (multiple characters not escaped)", + "data": "/~~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", + "data": "a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", + "data": "0", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", + "data": "a/a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/regex.json b/src/test/suite/tests/draft2019-09/optional/format/regex.json new file mode 100644 index 000000000..da32401c5 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/regex.json @@ -0,0 +1,51 @@ +[ + { + "description": "validation of regular expressions", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid regular expression", + "data": "([abc])+\\s+$", + "valid": true + }, + { + "description": "a regular expression with unclosed parens is invalid", + "data": "^(abc]", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/relative-json-pointer.json b/src/test/suite/tests/draft2019-09/optional/format/relative-json-pointer.json new file mode 100644 index 000000000..ba97d2a19 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/relative-json-pointer.json @@ -0,0 +1,101 @@ +[ + { + "description": "validation of Relative JSON Pointers (RJP)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid upwards RJP", + "data": "1", + "valid": true + }, + { + "description": "a valid downwards RJP", + "data": "0/foo/bar", + "valid": true + }, + { + "description": "a valid up and then down RJP, with array index", + "data": "2/0/baz/1/zip", + "valid": true + }, + { + "description": "a valid RJP taking the member or index name", + "data": "0#", + "valid": true + }, + { + "description": "an invalid RJP that is a valid JSON Pointer", + "data": "/foo/bar", + "valid": false + }, + { + "description": "negative prefix", + "data": "-1/foo/bar", + "valid": false + }, + { + "description": "explicit positive prefix", + "data": "+1/foo/bar", + "valid": false + }, + { + "description": "## is not a valid json-pointer", + "data": "0##", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus json-pointer", + "data": "01/a", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus octothorpe", + "data": "01#", + "valid": false + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "multi-digit integer prefix", + "data": "120/foo/bar", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/time.json b/src/test/suite/tests/draft2019-09/optional/format/time.json new file mode 100644 index 000000000..dadaae6a0 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/time.json @@ -0,0 +1,236 @@ +[ + { + "description": "validation of time strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid time string", + "data": "08:30:06Z", + "valid": true + }, + { + "description": "invalid time string with extra leading zeros", + "data": "008:030:006Z", + "valid": false + }, + { + "description": "invalid time string with no leading zero for single digit", + "data": "8:3:6Z", + "valid": false + }, + { + "description": "hour, minute, second must be two digits", + "data": "8:0030:6Z", + "valid": false + }, + { + "description": "a valid time string with leap second, Zulu", + "data": "23:59:60Z", + "valid": true + }, + { + "description": "invalid leap second, Zulu (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "invalid leap second, Zulu (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "valid leap second, zero time-offset", + "data": "23:59:60+00:00", + "valid": true + }, + { + "description": "invalid leap second, zero time-offset (wrong hour)", + "data": "22:59:60+00:00", + "valid": false + }, + { + "description": "invalid leap second, zero time-offset (wrong minute)", + "data": "23:58:60+00:00", + "valid": false + }, + { + "description": "valid leap second, positive time-offset", + "data": "01:29:60+01:30", + "valid": true + }, + { + "description": "valid leap second, large positive time-offset", + "data": "23:29:60+23:30", + "valid": true + }, + { + "description": "invalid leap second, positive time-offset (wrong hour)", + "data": "23:59:60+01:00", + "valid": false + }, + { + "description": "invalid leap second, positive time-offset (wrong minute)", + "data": "23:59:60+00:30", + "valid": false + }, + { + "description": "valid leap second, negative time-offset", + "data": "15:59:60-08:00", + "valid": true + }, + { + "description": "valid leap second, large negative time-offset", + "data": "00:29:60-23:30", + "valid": true + }, + { + "description": "invalid leap second, negative time-offset (wrong hour)", + "data": "23:59:60-01:00", + "valid": false + }, + { + "description": "invalid leap second, negative time-offset (wrong minute)", + "data": "23:59:60-00:30", + "valid": false + }, + { + "description": "a valid time string with second fraction", + "data": "23:20:50.52Z", + "valid": true + }, + { + "description": "a valid time string with precise second fraction", + "data": "08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid time string with plus offset", + "data": "08:30:06+00:20", + "valid": true + }, + { + "description": "a valid time string with minus offset", + "data": "08:30:06-08:00", + "valid": true + }, + { + "description": "hour, minute in time-offset must be two digits", + "data": "08:30:06-8:000", + "valid": false + }, + { + "description": "a valid time string with case-insensitive Z", + "data": "08:30:06z", + "valid": true + }, + { + "description": "an invalid time string with invalid hour", + "data": "24:00:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid minute", + "data": "00:60:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid second", + "data": "00:00:61Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset hour", + "data": "01:02:03+24:00", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset minute", + "data": "01:02:03+00:60", + "valid": false + }, + { + "description": "an invalid time string with invalid time with both Z and numoffset", + "data": "01:02:03Z+00:30", + "valid": false + }, + { + "description": "an invalid offset indicator", + "data": "08:30:06 PST", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "01:01:01,1111", + "valid": false + }, + { + "description": "no time offset", + "data": "12:00:00", + "valid": false + }, + { + "description": "no time offset with second fraction", + "data": "12:00:00.52", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২:00:00Z", + "valid": false + }, + { + "description": "offset not starting with plus or minus", + "data": "08:30:06#00:20", + "valid": false + }, + { + "description": "contains letters", + "data": "ab:cd:ef", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/unknown.json b/src/test/suite/tests/draft2019-09/optional/format/unknown.json new file mode 100644 index 000000000..c89f73036 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/unknown.json @@ -0,0 +1,46 @@ +[ + { + "description": "unknown format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "unknown" + }, + "tests": [ + { + "description": "unknown formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "unknown formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "unknown formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "unknown formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "unknown formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "unknown formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "unknown formats ignore strings", + "data": "string", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/uri-reference.json b/src/test/suite/tests/draft2019-09/optional/format/uri-reference.json new file mode 100644 index 000000000..2c49da63f --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/uri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of URI References", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid relative URI Reference", + "data": "/abc", + "valid": true + }, + { + "description": "an invalid URI Reference", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "a valid URI Reference", + "data": "abc", + "valid": true + }, + { + "description": "a valid URI fragment", + "data": "#fragment", + "valid": true + }, + { + "description": "an invalid URI fragment", + "data": "#frag\\ment", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/uri-template.json b/src/test/suite/tests/draft2019-09/optional/format/uri-template.json new file mode 100644 index 000000000..f4aee1040 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/uri-template.json @@ -0,0 +1,61 @@ +[ + { + "description": "format: uri-template", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", + "valid": true + }, + { + "description": "an invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false + }, + { + "description": "a valid uri-template without variables", + "data": "http://example.com/dictionary", + "valid": true + }, + { + "description": "a valid relative uri-template", + "data": "dictionary/{term:1}/{term}", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/uri.json b/src/test/suite/tests/draft2019-09/optional/format/uri.json new file mode 100644 index 000000000..ad67840de --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/uri.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of URIs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/format/uuid.json b/src/test/suite/tests/draft2019-09/optional/format/uuid.json new file mode 100644 index 000000000..dc6fb7e0d --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/format/uuid.json @@ -0,0 +1,116 @@ +[ + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "all upper-case", + "data": "2EB8AA08-AA98-11EA-B4AA-73B441D16380", + "valid": true + }, + { + "description": "all lower-case", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d16380", + "valid": true + }, + { + "description": "mixed case", + "data": "2eb8aa08-AA98-11ea-B4Aa-73B441D16380", + "valid": true + }, + { + "description": "all zeroes is valid", + "data": "00000000-0000-0000-0000-000000000000", + "valid": true + }, + { + "description": "wrong length", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": false + }, + { + "description": "missing section", + "data": "2eb8aa08-aa98-11ea-73b441d16380", + "valid": false + }, + { + "description": "bad characters (not hex)", + "data": "2eb8aa08-aa98-11ea-b4ga-73b441d16380", + "valid": false + }, + { + "description": "no dashes", + "data": "2eb8aa08aa9811eab4aa73b441d16380", + "valid": false + }, + { + "description": "too few dashes", + "data": "2eb8aa08aa98-11ea-b4aa73b441d16380", + "valid": false + }, + { + "description": "too many dashes", + "data": "2eb8-aa08-aa98-11ea-b4aa73b44-1d16380", + "valid": false + }, + { + "description": "dashes in the wrong spot", + "data": "2eb8aa08aa9811eab4aa73b441d16380----", + "valid": false + }, + { + "description": "valid version 4", + "data": "98d80576-482e-427f-8434-7f86890ab222", + "valid": true + }, + { + "description": "valid version 5", + "data": "99c17cbb-656f-564a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 6", + "data": "99c17cbb-656f-664a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 15", + "data": "99c17cbb-656f-f64a-940f-1a4568f03487", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/id.json b/src/test/suite/tests/draft2019-09/optional/id.json new file mode 100644 index 000000000..4daa8f51f --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/id.json @@ -0,0 +1,53 @@ +[ + { + "description": "$id inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $id buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "id_in_enum": { + "enum": [ + { + "$id": "https://localhost:1234/draft2019-09/id/my_identifier.json", + "type": "null" + } + ] + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft2019-09/id/my_identifier.json", + "type": "string" + }, + "zzz_id_in_const": { + "const": { + "$id": "https://localhost:1234/draft2019-09/id/my_identifier.json", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_enum" }, + { "$ref": "https://localhost:1234/draft2019-09/id/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$id": "https://localhost:1234/draft2019-09/id/my_identifier.json", + "type": "null" + }, + "valid": true + }, + { + "description": "match $ref to $id", + "data": "a string to match #/$defs/id_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $id", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/no-schema.json b/src/test/suite/tests/draft2019-09/optional/no-schema.json new file mode 100644 index 000000000..676e6b53a --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/no-schema.json @@ -0,0 +1,26 @@ +[ + { + "description": "validation without $schema", + "comment": "minLength is the same across all drafts", + "schema": { + "minLength": 2 + }, + "tests": [ + { + "description": "a 3-character string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a 1-character string is not valid", + "data": "a", + "valid": false + }, + { + "description": "a non-string is valid", + "data": 5, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/non-bmp-regex.json b/src/test/suite/tests/draft2019-09/optional/non-bmp-regex.json new file mode 100644 index 000000000..ef2500064 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/non-bmp-regex.json @@ -0,0 +1,86 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "^🐲*$" + }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/refOfUnknownKeyword.json b/src/test/suite/tests/draft2019-09/optional/refOfUnknownKeyword.json new file mode 100644 index 000000000..e9a75dd1e --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/refOfUnknownKeyword.json @@ -0,0 +1,69 @@ +[ + { + "description": "reference of a root arbitrary keyword ", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unknown-keyword": {"type": "integer"}, + "properties": { + "bar": {"$ref": "#/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference of an arbitrary keyword of a sub-schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"unknown-keyword": {"type": "integer"}}, + "bar": {"$ref": "#/properties/foo/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference internals of known non-applicator", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "/base", + "examples": [ + { "type": "string" } + ], + "$ref": "#/examples/0" + }, + "tests": [ + { + "description": "match", + "data": "a string", + "valid": true + }, + { + "description": "mismatch", + "data": 42, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/optional/unknownKeyword.json b/src/test/suite/tests/draft2019-09/optional/unknownKeyword.json new file mode 100644 index 000000000..f98e87c54 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/optional/unknownKeyword.json @@ -0,0 +1,57 @@ +[ + { + "description": "$id inside an unknown keyword is not a real identifier", + "comment": "the implementation must not be confused by an $id in locations we do not know how to parse", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "id_in_unknown0": { + "not": { + "array_of_schemas": [ + { + "$id": "https://localhost:1234/draft2019-09/unknownKeyword/my_identifier.json", + "type": "null" + } + ] + } + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft2019-09/unknownKeyword/my_identifier.json", + "type": "string" + }, + "id_in_unknown1": { + "not": { + "object_of_schemas": { + "foo": { + "$id": "https://localhost:1234/draft2019-09/unknownKeyword/my_identifier.json", + "type": "integer" + } + } + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_unknown0" }, + { "$ref": "#/$defs/id_in_unknown1" }, + { "$ref": "https://localhost:1234/draft2019-09/unknownKeyword/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "type matches second anyOf, which has a real schema in it", + "data": "a string", + "valid": true + }, + { + "description": "type matches non-schema in first anyOf", + "data": null, + "valid": false + }, + { + "description": "type matches non-schema in third anyOf", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/pattern.json b/src/test/suite/tests/draft2019-09/pattern.json new file mode 100644 index 000000000..cfb87744f --- /dev/null +++ b/src/test/suite/tests/draft2019-09/pattern.json @@ -0,0 +1,65 @@ +[ + { + "description": "pattern validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "^a*$" + }, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "pattern": "a+" + }, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/patternProperties.json b/src/test/suite/tests/draft2019-09/patternProperties.json new file mode 100644 index 000000000..354bb48bf --- /dev/null +++ b/src/test/suite/tests/draft2019-09/patternProperties.json @@ -0,0 +1,176 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/properties.json b/src/test/suite/tests/draft2019-09/properties.json new file mode 100644 index 000000000..a53429c5e --- /dev/null +++ b/src/test/suite/tests/draft2019-09/properties.json @@ -0,0 +1,242 @@ +[ + { + "description": "object properties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/propertyNames.json b/src/test/suite/tests/draft2019-09/propertyNames.json new file mode 100644 index 000000000..3b2bb23bb --- /dev/null +++ b/src/test/suite/tests/draft2019-09/propertyNames.json @@ -0,0 +1,168 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames validation with pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": true + }, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": false + }, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with const", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": {"const": "foo"} + }, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with enum", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": {"enum": ["foo", "bar"]} + }, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property foo and bar is valid", + "data": {"foo": 1, "bar": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"baz": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/recursiveRef.json b/src/test/suite/tests/draft2019-09/recursiveRef.json new file mode 100644 index 000000000..22b47e749 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/recursiveRef.json @@ -0,0 +1,408 @@ +[ + { + "description": "$recursiveRef without $recursiveAnchor works like $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": { "$recursiveRef": "#" } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": { "foo": { "foo": false } }, + "valid": true + }, + { + "description": "mismatch", + "data": { "bar": false }, + "valid": false + }, + { + "description": "recursive mismatch", + "data": { "foo": { "bar": false } }, + "valid": false + } + ] + }, + { + "description": "$recursiveRef without using nesting", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef2/schema.json", + "$defs": { + "myobject": { + "$id": "myobject.json", + "$recursiveAnchor": true, + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" } + } + ] + } + }, + "anyOf": [ + { "type": "integer" }, + { "$ref": "#/$defs/myobject" } + ] + }, + "tests": [ + { + "description": "integer matches at the outer level", + "data": 1, + "valid": true + }, + { + "description": "single level match", + "data": { "foo": "hi" }, + "valid": true + }, + { + "description": "integer does not match as a property value", + "data": { "foo": 1 }, + "valid": false + }, + { + "description": "two levels, properties match with inner definition", + "data": { "foo": { "bar": "hi" } }, + "valid": true + }, + { + "description": "two levels, no match", + "data": { "foo": { "bar": 1 } }, + "valid": false + } + ] + }, + { + "description": "$recursiveRef with nesting", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef3/schema.json", + "$recursiveAnchor": true, + "$defs": { + "myobject": { + "$id": "myobject.json", + "$recursiveAnchor": true, + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" } + } + ] + } + }, + "anyOf": [ + { "type": "integer" }, + { "$ref": "#/$defs/myobject" } + ] + }, + "tests": [ + { + "description": "integer matches at the outer level", + "data": 1, + "valid": true + }, + { + "description": "single level match", + "data": { "foo": "hi" }, + "valid": true + }, + { + "description": "integer now matches as a property value", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "two levels, properties match with inner definition", + "data": { "foo": { "bar": "hi" } }, + "valid": true + }, + { + "description": "two levels, properties match with $recursiveRef", + "data": { "foo": { "bar": 1 } }, + "valid": true + } + ] + }, + { + "description": "$recursiveRef with $recursiveAnchor: false works like $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef4/schema.json", + "$recursiveAnchor": false, + "$defs": { + "myobject": { + "$id": "myobject.json", + "$recursiveAnchor": false, + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" } + } + ] + } + }, + "anyOf": [ + { "type": "integer" }, + { "$ref": "#/$defs/myobject" } + ] + }, + "tests": [ + { + "description": "integer matches at the outer level", + "data": 1, + "valid": true + }, + { + "description": "single level match", + "data": { "foo": "hi" }, + "valid": true + }, + { + "description": "integer does not match as a property value", + "data": { "foo": 1 }, + "valid": false + }, + { + "description": "two levels, properties match with inner definition", + "data": { "foo": { "bar": "hi" } }, + "valid": true + }, + { + "description": "two levels, integer does not match as a property value", + "data": { "foo": { "bar": 1 } }, + "valid": false + } + ] + }, + { + "description": "$recursiveRef with no $recursiveAnchor works like $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef5/schema.json", + "$defs": { + "myobject": { + "$id": "myobject.json", + "$recursiveAnchor": false, + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" } + } + ] + } + }, + "anyOf": [ + { "type": "integer" }, + { "$ref": "#/$defs/myobject" } + ] + }, + "tests": [ + { + "description": "integer matches at the outer level", + "data": 1, + "valid": true + }, + { + "description": "single level match", + "data": { "foo": "hi" }, + "valid": true + }, + { + "description": "integer does not match as a property value", + "data": { "foo": 1 }, + "valid": false + }, + { + "description": "two levels, properties match with inner definition", + "data": { "foo": { "bar": "hi" } }, + "valid": true + }, + { + "description": "two levels, integer does not match as a property value", + "data": { "foo": { "bar": 1 } }, + "valid": false + } + ] + }, + { + "description": "$recursiveRef with no $recursiveAnchor in the initial target schema resource", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef6/base.json", + "$recursiveAnchor": true, + "anyOf": [ + { "type": "boolean" }, + { + "type": "object", + "additionalProperties": { + "$id": "http://localhost:4242/draft2019-09/recursiveRef6/inner.json", + "$comment": "there is no $recursiveAnchor: true here, so we do NOT recurse to the base", + "anyOf": [ + { "type": "integer" }, + { "type": "object", "additionalProperties": { "$recursiveRef": "#" } } + ] + } + } + ] + }, + "tests": [ + { + "description": "leaf node does not match; no recursion", + "data": { "foo": true }, + "valid": false + }, + { + "description": "leaf node matches: recursion uses the inner schema", + "data": { "foo": { "bar": 1 } }, + "valid": true + }, + { + "description": "leaf node does not match: recursion uses the inner schema", + "data": { "foo": { "bar": true } }, + "valid": false + } + ] + }, + { + "description": "$recursiveRef with no $recursiveAnchor in the outer schema resource", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:4242/draft2019-09/recursiveRef7/base.json", + "anyOf": [ + { "type": "boolean" }, + { + "type": "object", + "additionalProperties": { + "$id": "http://localhost:4242/draft2019-09/recursiveRef7/inner.json", + "$recursiveAnchor": true, + "anyOf": [ + { "type": "integer" }, + { "type": "object", "additionalProperties": { "$recursiveRef": "#" } } + ] + } + } + ] + }, + "tests": [ + { + "description": "leaf node does not match; no recursion", + "data": { "foo": true }, + "valid": false + }, + { + "description": "leaf node matches: recursion only uses inner schema", + "data": { "foo": { "bar": 1 } }, + "valid": true + }, + { + "description": "leaf node does not match: recursion only uses inner schema", + "data": { "foo": { "bar": true } }, + "valid": false + } + ] + }, + { + "description": "multiple dynamic paths to the $recursiveRef keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/recursiveRef8_main.json", + "$defs": { + "inner": { + "$id": "recursiveRef8_inner.json", + "$recursiveAnchor": true, + "title": "inner", + "additionalProperties": { + "$recursiveRef": "#" + } + } + }, + "if": { + "propertyNames": { + "pattern": "^[a-m]" + } + }, + "then": { + "title": "any type of node", + "$id": "recursiveRef8_anyLeafNode.json", + "$recursiveAnchor": true, + "$ref": "recursiveRef8_inner.json" + }, + "else": { + "title": "integer node", + "$id": "recursiveRef8_integerNode.json", + "$recursiveAnchor": true, + "type": [ "object", "integer" ], + "$ref": "recursiveRef8_inner.json" + } + }, + "tests": [ + { + "description": "recurse to anyLeafNode - floats are allowed", + "data": { "alpha": 1.1 }, + "valid": true + }, + { + "description": "recurse to integerNode - floats are not allowed", + "data": { "november": 1.1 }, + "valid": false + } + ] + }, + { + "description": "dynamic $recursiveRef destination (not predictable at schema compile time)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/main.json", + "$defs": { + "inner": { + "$id": "inner.json", + "$recursiveAnchor": true, + "title": "inner", + "additionalProperties": { + "$recursiveRef": "#" + } + } + + }, + "if": { "propertyNames": { "pattern": "^[a-m]" } }, + "then": { + "title": "any type of node", + "$id": "anyLeafNode.json", + "$recursiveAnchor": true, + "$ref": "main.json#/$defs/inner" + }, + "else": { + "title": "integer node", + "$id": "integerNode.json", + "$recursiveAnchor": true, + "type": [ "object", "integer" ], + "$ref": "main.json#/$defs/inner" + } + }, + "tests": [ + { + "description": "numeric node", + "data": { "alpha": 1.1 }, + "valid": true + }, + { + "description": "integer node", + "data": { "november": 1.1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/ref.json b/src/test/suite/tests/draft2019-09/ref.json new file mode 100644 index 000000000..eff5305c3 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/ref.json @@ -0,0 +1,1090 @@ +[ + { + "description": "root pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + {"type": "integer"}, + {"$ref": "#/items/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/$defs/tilde~0field"}, + "slash": {"$ref": "#/$defs/slash~1field"}, + "percent": {"$ref": "#/$defs/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "a": {"type": "integer"}, + "b": {"$ref": "#/$defs/a"}, + "c": {"$ref": "#/$defs/b"} + }, + "$ref": "#/$defs/c" + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref applies alongside sibling keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/$defs/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid, maxItems valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems invalid", + "data": { "foo": [1, 2, 3] }, + "valid": false + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "https://json-schema.org/draft/2019-09/schema" + }, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "$ref": {"$ref": "#/$defs/is-string"} + }, + "$defs": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "$defs": { + "node": { + "$id": "http://localhost:1234/draft2019-09/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo\"bar": {"$ref": "#/$defs/foo%22bar"} + }, + "$defs": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "ref creates new scope when adjacent to keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "A": { + "unevaluatedProperties": false + } + }, + "properties": { + "prop1": { + "type": "string" + } + }, + "$ref": "#/$defs/A" + }, + "tests": [ + { + "description": "referenced subschema doesn't see annotations from properties", + "data": { + "prop1": "match" + }, + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/$defs/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/$defs/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-relative-uri-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-refs-absolute-uris-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "$id must be resolved against nearest parent, not just immediate parent", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/a.json", + "$defs": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "$defs": { + "y": { + "$id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$id": "https://example.com/draft2019-09/ref-and-id1/base.json", + "$ref": "int.json", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/draft2019-09/ref-and-id1/int.json", + "$id": "int.json", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/draft2019-09/ref-and-id1-int.json", + "$id": "/draft2019-09/ref-and-id1-int.json", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $anchor and $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$id": "https://example.com/draft2019-09/ref-and-id2/base.json", + "$ref": "#bigint", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/draft2019-09/ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: https://example.com/ref-and-id2/base.json#bigint", + "$anchor": "bigint", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/draft2019-09/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", + "$id": "/draft2019-09/ref-and-id2/", + "$anchor": "bigint", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "RFC 8141 §2.2", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "RFC 8141 §2.3.1", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$comment": "RFC 8141 §2.3.2", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "$defs": { + "bar": { + "$anchor": "something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN ref with nested pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": { + "foo": { + "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "bar", + "valid": true + }, + { + "description": "a non-string is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "ref to if", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://example.com/ref/if", + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to then", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://example.com/ref/then", + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://example.com/ref/else", + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/ref/absref.json", + "$defs": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "$ref": "/absref/foobar.json" + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "file:///folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "file:///c:/folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "": { + "$defs": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs//$defs/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$ref with $recursiveAnchor", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/schemas/unevaluated-items-are-disallowed", + "$ref": "/schemas/unevaluated-items-are-allowed", + "$recursiveAnchor": true, + "unevaluatedItems": false, + "$defs": { + "/schemas/unevaluated-items-are-allowed": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "/schemas/unevaluated-items-are-allowed", + "$recursiveAnchor": true, + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#" + } + ] + } + } + }, + "tests": [ + { + "description": "extra items allowed for inner arrays", + "data" : ["foo",["bar" , [] , 8]], + "valid": true + }, + { + "description": "extra items disallowed for root", + "data" : ["foo",["bar" , [] , 8], 8], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/refRemote.json b/src/test/suite/tests/draft2019-09/refRemote.json new file mode 100644 index 000000000..072894cf2 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/refRemote.json @@ -0,0 +1,342 @@ +[ + { + "description": "remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/integer.json" + }, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/subSchemas.json#/$defs/integer" + }, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "anchor within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/locationIndependentIdentifier.json#foo" + }, + "tests": [ + { + "description": "remote anchor valid", + "data": 1, + "valid": true + }, + { + "description": "remote anchor invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/subSchemas.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/scope_change_defs1.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolder/"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/scope_change_defs2.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolderInSubschema/#/$defs/bar"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "$defs": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/object", + "type": "object", + "properties": { + "name": {"$ref": "name-defs.json#/$defs/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to defs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/schema-remote-ref-ref-defs1.json", + "$ref": "ref-and-defs.json" + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/locationIndependentIdentifier.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://localhost:1234/draft2019-09/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different $id", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/different-id-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different URN $id", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/urn-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with nested absolute ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/nested-absolute-ref-to-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds detached $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "http://localhost:1234/draft2019-09/detached-ref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/required.json b/src/test/suite/tests/draft2019-09/required.json new file mode 100644 index 000000000..bca98a986 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/required.json @@ -0,0 +1,158 @@ +[ + { + "description": "required validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "required": ["__proto__", "toString", "constructor"] + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/type.json b/src/test/suite/tests/draft2019-09/type.json new file mode 100644 index 000000000..92c6be89e --- /dev/null +++ b/src/test/suite/tests/draft2019-09/type.json @@ -0,0 +1,501 @@ +[ + { + "description": "integer type matches integers", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer" + }, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "number" + }, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" + }, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object" + }, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "array" + }, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "boolean" + }, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "null" + }, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": ["integer", "string"] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/unevaluatedItems.json b/src/test/suite/tests/draft2019-09/unevaluatedItems.json new file mode 100644 index 000000000..8e2ee4b11 --- /dev/null +++ b/src/test/suite/tests/draft2019-09/unevaluatedItems.json @@ -0,0 +1,703 @@ +[ + { + "description": "unevaluatedItems true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": true + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems as schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": { "type": "string" } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with valid unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with invalid unevaluated items", + "data": [42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with uniform items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": { "type": "string" }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "type": "string" } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with items and additionalItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "type": "string" } + ], + "additionalItems": true, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with ignored additionalItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "additionalItems": {"type": "number"}, + "unevaluatedItems": {"type": "string"} + }, + "tests": [ + { + "description": "invalid under unevaluatedItems", + "comment": "additionalItems is entirely ignored when items isn't present, so all elements need to be valid against the unevaluatedItems schema", + "data": ["foo", 1], + "valid": false + }, + { + "description": "all valid under unevaluatedItems", + "data": ["foo", "bar", "baz"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with ignored applicator additionalItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ { "additionalItems": { "type": "number" } } ], + "unevaluatedItems": {"type": "string"} + }, + "tests": [ + { + "description": "invalid under unevaluatedItems", + "comment": "additionalItems is entirely ignored when items isn't present, so all elements need to be valid against the unevaluatedItems schema", + "data": ["foo", 1], + "valid": false + }, + { + "description": "all valid under unevaluatedItems", + "data": ["foo", "bar", "baz"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with nested tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "type": "string" } + ], + "allOf": [ + { + "items": [ + true, + { "type": "number" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", 42], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", 42, true], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": {"type": "boolean"}, + "anyOf": [ + { "items": {"type": "string"} }, + true + ] + }, + "tests": [ + { + "description": "with only (valid) additional items", + "data": [true, false], + "valid": true + }, + { + "description": "with no additional items", + "data": ["yes", "no"], + "valid": true + }, + { + "description": "with invalid additional item", + "data": ["yes", false], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested items and additionalItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "items": [ + { "type": "string" } + ], + "additionalItems": true + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with nested unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "items": [ + { "type": "string" } + ] + }, + { "unevaluatedItems": true } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "const": "foo" } + ], + "anyOf": [ + { + "items": [ + true, + { "const": "bar" } + ] + }, + { + "items": [ + true, + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when one schema matches and has no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "when one schema matches and has unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + }, + { + "description": "when two schemas match and has no unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "when two schemas match and has unevaluated items", + "data": ["foo", "bar", "baz", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "const": "foo" } + ], + "oneOf": [ + { + "items": [ + true, + { "const": "bar" } + ] + }, + { + "items": [ + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with not", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ + { "const": "foo" } + ], + "not": { + "not": { + "items": [ + true, + { "const": "bar" } + ] + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [ { "const": "foo" } ], + "if": { + "items": [ + true, + { "const": "bar" } + ] + }, + "then": { + "items": [ + true, + true, + { "const": "then" } + ] + }, + "else": { + "items": [ + true, + true, + true, + { "const": "else" } + ] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when if matches and it has no unevaluated items", + "data": ["foo", "bar", "then"], + "valid": true + }, + { + "description": "when if matches and it has unevaluated items", + "data": ["foo", "bar", "then", "else"], + "valid": false + }, + { + "description": "when if doesn't match and it has no unevaluated items", + "data": ["foo", 42, 42, "else"], + "valid": true + }, + { + "description": "when if doesn't match and it has unevaluated items", + "data": ["foo", 42, 42, "else", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [true], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#/$defs/bar", + "items": [ + { "type": "string" } + ], + "unevaluatedItems": false, + "$defs": { + "bar": { + "items": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": false, + "items": [ + { "type": "string" } + ], + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "items": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $recursiveRef", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/unevaluated-items-with-recursive-ref/extended-tree", + + "$recursiveAnchor": true, + + "$ref": "./tree", + "items": [ + true, + true, + { "type": "string" } + ], + + "$defs": { + "tree": { + "$id": "./tree", + "$recursiveAnchor": true, + + "type": "array", + "items": [ + { "type": "number" }, + { + "$comment": "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedItems": false, + "$recursiveRef": "#" + } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [1, [2, [], "b"], "a"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": [1, [2, [], "b", "too many"], "a"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "items": [ true ] + }, + { "unevaluatedItems": false } + ] + }, + "tests": [ + { + "description": "always fails", + "data": [ 1 ], + "valid": false + } + ] + }, + { + "description": "item is evaluated in an uncle schema to unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": { + "items": [ + { "type": "string" } + ], + "unevaluatedItems": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "items": [ + true, + { "type": "string" } + ] + } + } + } + ] + }, + "tests": [ + { + "description": "no extra items", + "data": { + "foo": [ + "test" + ] + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": [ + "test", + "test" + ] + }, + "valid": false + } + ] + }, + { + "description": "non-array instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "items": [{"const": "a"}] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": [ "a" ], + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": [ "b" ], + "valid": false + } + + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/unevaluatedProperties.json b/src/test/suite/tests/draft2019-09/unevaluatedProperties.json new file mode 100644 index 000000000..e8765112c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/unevaluatedProperties.json @@ -0,0 +1,1604 @@ +[ + { + "description": "unevaluatedProperties true", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties schema", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "unevaluatedProperties": { + "type": "string", + "minLength": 3 + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with valid unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with invalid unevaluated properties", + "data": { + "foo": "fo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "patternProperties": { + "^foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "additionalProperties": true, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "properties": { + "bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "patternProperties": { + "^bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "additionalProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": { + "type": "string", + "maxLength": 2 + } + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "anyOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + }, + { + "properties": { + "quux": { "const": "quux" } + }, + "required": ["quux"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when one matches and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when one matches and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "not-baz" + }, + "valid": false + }, + { + "description": "when two match and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": true + }, + { + "description": "when two match and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "quux": "not-quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "oneOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "quux": "quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with not", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "not": { + "not": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, then not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": false + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, else not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with dependentSchemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [true], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "$ref": "#/$defs/bar", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false, + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "unevaluatedProperties": false, + "properties": { + "foo": { "type": "string" } + }, + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $recursiveRef", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://example.com/unevaluated-properties-with-recursive-ref/extended-tree", + + "$recursiveAnchor": true, + + "$ref": "./tree", + "properties": { + "name": { "type": "string" } + }, + + "$defs": { + "tree": { + "$id": "./tree", + "$recursiveAnchor": true, + + "type": "object", + "properties": { + "node": true, + "branches": { + "$comment": "unevaluatedProperties comes first so it's more likely to bugs errors with implementations that are sensitive to keyword ordering", + "unevaluatedProperties": false, + "$recursiveRef": "#" + } + }, + "required": ["node"] + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "name": "a", + "node": 1, + "branches": { + "name": "b", + "node": 2 + } + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "name": "a", + "node": 1, + "branches": { + "foo": "b", + "node": 2 + } + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "properties": { + "foo": true + } + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins (reverse order)", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "unevaluatedProperties": false + }, + { + "properties": { + "foo": true + } + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, true with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, false with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "unevaluatedProperties": true + }, + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "property is evaluated in an uncle schema to unevaluatedProperties", + "comment": "see https://stackoverflow.com/questions/66936884/deeply-nested-unevaluatedproperties-and-their-expectations", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "properties": { + "faz": { + "type": "string" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "no extra properties", + "data": { + "foo": { + "bar": "test" + } + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": { + "bar": "test", + "faz": "test" + } + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, allOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + }, + "unevaluatedProperties": false + } + ], + "anyOf": [ + { + "properties": { + "bar": true + } + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, anyOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + } + } + ], + "anyOf": [ + { + "properties": { + "bar": true + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties + single cyclic ref", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "properties": { + "x": { "$ref": "#" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is valid", + "data": {}, + "valid": true + }, + { + "description": "Single is valid", + "data": { "x": {} }, + "valid": true + }, + { + "description": "Unevaluated on 1st level is invalid", + "data": { "x": {}, "y": {} }, + "valid": false + }, + { + "description": "Nested is valid", + "data": { "x": { "x": {} } }, + "valid": true + }, + { + "description": "Unevaluated on 2nd level is invalid", + "data": { "x": { "x": {}, "y": {} } }, + "valid": false + }, + { + "description": "Deep nested is valid", + "data": { "x": { "x": { "x": {} } } }, + "valid": true + }, + { + "description": "Unevaluated on 3rd level is invalid", + "data": { "x": { "x": { "x": {}, "y": {} } } }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties + ref inside allOf / oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "one": { + "properties": { "a": true } + }, + "two": { + "required": ["x"], + "properties": { "x": true } + } + }, + "allOf": [ + { "$ref": "#/$defs/one" }, + { "properties": { "b": true } }, + { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { + "required": ["y"], + "properties": { "y": true } + } + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid (no x or y)", + "data": {}, + "valid": false + }, + { + "description": "a and b are invalid (no x or y)", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "x and y are invalid", + "data": { "x": 1, "y": 1 }, + "valid": false + }, + { + "description": "a and x are valid", + "data": { "a": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and y are valid", + "data": { "a": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x are valid", + "data": { "a": 1, "b": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and b and y are valid", + "data": { "a": 1, "b": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x and y are invalid", + "data": { "a": 1, "b": 1, "x": 1, "y": 1 }, + "valid": false + } + ] + }, + { + "description": "dynamic evalation inside nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "one": { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { "required": ["b"], "properties": { "b": true } }, + { "required": ["xx"], "patternProperties": { "x": true } }, + { "required": ["all"], "unevaluatedProperties": true } + ] + }, + "two": { + "oneOf": [ + { "required": ["c"], "properties": { "c": true } }, + { "required": ["d"], "properties": { "d": true } } + ] + } + }, + "oneOf": [ + { "$ref": "#/$defs/one" }, + { "required": ["a"], "properties": { "a": true } } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid", + "data": {}, + "valid": false + }, + { + "description": "a is valid", + "data": { "a": 1 }, + "valid": true + }, + { + "description": "b is valid", + "data": { "b": 1 }, + "valid": true + }, + { + "description": "c is valid", + "data": { "c": 1 }, + "valid": true + }, + { + "description": "d is valid", + "data": { "d": 1 }, + "valid": true + }, + { + "description": "a + b is invalid", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "a + c is invalid", + "data": { "a": 1, "c": 1 }, + "valid": false + }, + { + "description": "a + d is invalid", + "data": { "a": 1, "d": 1 }, + "valid": false + }, + { + "description": "b + c is invalid", + "data": { "b": 1, "c": 1 }, + "valid": false + }, + { + "description": "b + d is invalid", + "data": { "b": 1, "d": 1 }, + "valid": false + }, + { + "description": "c + d is invalid", + "data": { "c": 1, "d": 1 }, + "valid": false + }, + { + "description": "xx is valid", + "data": { "xx": 1 }, + "valid": true + }, + { + "description": "xx + foox is valid", + "data": { "xx": 1, "foox": 1 }, + "valid": true + }, + { + "description": "xx + foo is invalid", + "data": { "xx": 1, "foo": 1 }, + "valid": false + }, + { + "description": "xx + a is invalid", + "data": { "xx": 1, "a": 1 }, + "valid": false + }, + { + "description": "xx + b is invalid", + "data": { "xx": 1, "b": 1 }, + "valid": false + }, + { + "description": "xx + c is invalid", + "data": { "xx": 1, "c": 1 }, + "valid": false + }, + { + "description": "xx + d is invalid", + "data": { "xx": 1, "d": 1 }, + "valid": false + }, + { + "description": "all is valid", + "data": { "all": 1 }, + "valid": true + }, + { + "description": "all + foo is valid", + "data": { "all": 1, "foo": 1 }, + "valid": true + }, + { + "description": "all + a is invalid", + "data": { "all": 1, "a": 1 }, + "valid": false + } + ] + }, + { + "description": "non-object instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "unevaluatedProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null valued properties", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties not affected by propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "propertyNames": {"maxLength": 1}, + "unevaluatedProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "allows only number properties", + "data": {"a": 1}, + "valid": true + }, + { + "description": "string property is invalid", + "data": {"a": "b"}, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "if": { + "patternProperties": { + "foo": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": { + "foo": "a" + }, + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": { + "bar": "a" + }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar":{} + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluatedProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties doesn't see bar when foo2 is absent", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties sees bar when foo2 is present", + "data": { "foo2": "", "bar": ""}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/uniqueItems.json b/src/test/suite/tests/draft2019-09/uniqueItems.json new file mode 100644 index 000000000..314b4b9cb --- /dev/null +++ b/src/test/suite/tests/draft2019-09/uniqueItems.json @@ -0,0 +1,419 @@ +[ + { + "description": "uniqueItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "uniqueItems": true + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "uniqueItems": false + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2019-09/vocabulary.json b/src/test/suite/tests/draft2019-09/vocabulary.json new file mode 100644 index 000000000..98482b20c --- /dev/null +++ b/src/test/suite/tests/draft2019-09/vocabulary.json @@ -0,0 +1,57 @@ +[ + { + "description": "schema that uses custom metaschema with with no validation vocabulary", + "schema": { + "$id": "https://schema/using/no/validation", + "$schema": "http://localhost:1234/draft2019-09/metaschema-no-validation.json", + "properties": { + "badProperty": false, + "numberProperty": { + "minimum": 10 + } + } + }, + "tests": [ + { + "description": "applicator vocabulary still works", + "data": { + "badProperty": "this property should not exist" + }, + "valid": false + }, + { + "description": "no validation: valid number", + "data": { + "numberProperty": 20 + }, + "valid": true + }, + { + "description": "no validation: invalid number, but it still validates", + "data": { + "numberProperty": 1 + }, + "valid": true + } + ] + }, + { + "description": "ignore unrecognized optional vocabulary", + "schema": { + "$schema": "http://localhost:1234/draft2019-09/metaschema-optional-vocabulary.json", + "type": "number" + }, + "tests": [ + { + "description": "string value", + "data": "foobar", + "valid": false + }, + { + "description": "number value", + "data": 20, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/additionalProperties.json b/src/test/suite/tests/draft2020-12/additionalProperties.json new file mode 100644 index 000000000..9618575e2 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/additionalProperties.json @@ -0,0 +1,219 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "specification": [ { "core":"10.3.2.3", "quote": "The value of \"additionalProperties\" MUST be a valid JSON Schema. Boolean \"false\" forbids everything." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "specification": [ { "core":"10.3.2.3"} ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "specification": [ { "core":"10.3.2.3", "quote": "The value of \"additionalProperties\" MUST be a valid JSON Schema." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": "additionalProperties can exist by itself", + "specification": [ { "core":"10.3.2.3", "quote": "With no other applicator applying to object instances. This validates all the instance values irrespective of their property names" } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "specification": [ { "core":"10.3.2.3", "quote": "Omitting this keyword has the same assertion behavior as an empty schema." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}} + }, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "specification":[ { "core": "10.2", "quote": "Subschemas of applicator keywords evaluate the instance completely independently such that the results of one such subschema MUST NOT impact the results of sibling subschemas." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "specification": [ { "core":"10.3.2.3" } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "additionalProperties with propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "maxLength": 5 + }, + "additionalProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "Valid against both keywords", + "data": { "apple": 4 }, + "valid": true + }, + { + "description": "Valid against propertyNames, but not additionalProperties", + "data": { "fig": 2, "pear": "available" }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar": {} + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "additionalProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar even when foo2 is present", + "data": {"foo2": "", "bar": ""}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/allOf.json b/src/test/suite/tests/draft2020-12/allOf.json new file mode 100644 index 000000000..9e87903fe --- /dev/null +++ b/src/test/suite/tests/draft2020-12/allOf.json @@ -0,0 +1,312 @@ +[ + { + "description": "allOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/anchor.json b/src/test/suite/tests/draft2020-12/anchor.json new file mode 100644 index 000000000..99143fa11 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/anchor.json @@ -0,0 +1,120 @@ +[ + { + "description": "Location-independent identifier", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#foo", + "$defs": { + "A": { + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with absolute URI", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/bar#foo", + "$defs": { + "A": { + "$id": "http://localhost:1234/draft2020-12/bar", + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/root", + "$ref": "http://localhost:1234/draft2020-12/nested.json#foo", + "$defs": { + "A": { + "$id": "nested.json", + "$defs": { + "B": { + "$anchor": "foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "same $anchor with different base uri", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/foobar", + "$defs": { + "A": { + "$id": "child1", + "allOf": [ + { + "$id": "child2", + "$anchor": "my_anchor", + "type": "number" + }, + { + "$anchor": "my_anchor", + "type": "string" + } + ] + } + }, + "$ref": "child1#my_anchor" + }, + "tests": [ + { + "description": "$ref resolves to /$defs/A/allOf/1", + "data": "a", + "valid": true + }, + { + "description": "$ref does not resolve to /$defs/A/allOf/0", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/anyOf.json b/src/test/suite/tests/draft2020-12/anyOf.json new file mode 100644 index 000000000..89b192dbd --- /dev/null +++ b/src/test/suite/tests/draft2020-12/anyOf.json @@ -0,0 +1,203 @@ +[ + { + "description": "anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [true, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/boolean_schema.json b/src/test/suite/tests/draft2020-12/boolean_schema.json new file mode 100644 index 000000000..6d40f23f2 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/const.json b/src/test/suite/tests/draft2020-12/const.json new file mode 100644 index 000000000..50be86a0d --- /dev/null +++ b/src/test/suite/tests/draft2020-12/const.json @@ -0,0 +1,387 @@ +[ + { + "description": "const validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 2 + }, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"foo": "bar", "baz": "bax"} + }, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [{ "foo": "bar" }] + }, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": null + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": false + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": true + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [false] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [true] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"a": false} + }, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"a": true} + }, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 0 + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 1 + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": -2.0 + }, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 9007199254740992 + }, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": "hello\u0000there" + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/contains.json b/src/test/suite/tests/draft2020-12/contains.json new file mode 100644 index 000000000..08a00a753 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/contains.json @@ -0,0 +1,176 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"minimum": 5} + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": true + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid", + "data": "contains does not apply to strings", + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [ 2, 4, 8 ], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [ 3, 6, 9 ], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [ 6, 12 ], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [ 1, 5 ], + "valid": false + } + ] + }, + { + "description": "contains with false if subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "if": false, + "else": true + } + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/content.json b/src/test/suite/tests/draft2020-12/content.json new file mode 100644 index 000000000..698f78057 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/content.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of string-encoded content based on media type", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json" + }, + "tests": [ + { + "description": "a valid JSON document", + "data": "{\"foo\": \"bar\"}", + "valid": true + }, + { + "description": "an invalid JSON document; validates true", + "data": "{:}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary string-encoding", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64 string", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "an invalid base64 string (% is not a valid character); validates true", + "data": "eyJmb28iOi%iYmFyIn0K", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents with schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64", + "contentSchema": { "type": "object", "required": ["foo"], "properties": { "foo": { "type": "string" } } } + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "another valid base64-encoded JSON document", + "data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==", + "valid": true + }, + { + "description": "an invalid base64-encoded JSON document; validates true", + "data": "eyJib28iOiAyMH0=", + "valid": true + }, + { + "description": "an empty object as a base64-encoded JSON document; validates true", + "data": "e30=", + "valid": true + }, + { + "description": "an empty array as a base64-encoded JSON document", + "data": "W10=", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/default.json b/src/test/suite/tests/draft2020-12/default.json new file mode 100644 index 000000000..ceb3ae271 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/default.json @@ -0,0 +1,82 @@ +[ + { + "description": "invalid type for default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/defs.json b/src/test/suite/tests/draft2020-12/defs.json new file mode 100644 index 000000000..da2a503bf --- /dev/null +++ b/src/test/suite/tests/draft2020-12/defs.json @@ -0,0 +1,21 @@ +[ + { + "description": "validate definition against metaschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://json-schema.org/draft/2020-12/schema" + }, + "tests": [ + { + "description": "valid definition schema", + "data": {"$defs": {"foo": {"type": "integer"}}}, + "valid": true + }, + { + "description": "invalid definition schema", + "data": {"$defs": {"foo": {"type": 1}}}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/dependentRequired.json b/src/test/suite/tests/draft2020-12/dependentRequired.json new file mode 100644 index 000000000..2baa38e9f --- /dev/null +++ b/src/test/suite/tests/draft2020-12/dependentRequired.json @@ -0,0 +1,152 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/dependentSchemas.json b/src/test/suite/tests/draft2020-12/dependentSchemas.json new file mode 100644 index 000000000..1c5f0574a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/dependentSchemas.json @@ -0,0 +1,171 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/dynamicRef.json b/src/test/suite/tests/draft2020-12/dynamicRef.json new file mode 100644 index 000000000..ffa211ba2 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/dynamicRef.json @@ -0,0 +1,815 @@ +[ + { + "description": "A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef to an $anchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-anchor-same-schema/root", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "foo": { + "$anchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$ref": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef without anchor in fragment behaves identical to $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-without-anchor/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#/$defs/items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items", + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is invalid", + "data": ["foo", "bar"], + "valid": false + }, + { + "description": "An array of numbers is valid", + "data": [24, 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root", + "$ref": "intermediate-scope", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "intermediate-scope": { + "$id": "intermediate-scope", + "$ref": "list" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-ignores-anchors/root", + "$ref": "list", + "$defs": { + "foo": { + "$anchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef without a matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-without-bookend/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", + "$anchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/unmatched-dynamic-anchor/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", + "$anchor": "items", + "$dynamicAnchor": "foo" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/relative-dynamic-reference/root", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "foo": { "const": "pass" } + }, + "$ref": "extended", + "$defs": { + "extended": { + "$id": "extended", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "bar": { "$ref": "bar" } + } + }, + "bar": { + "$id": "bar", + "type": "object", + "properties": { + "baz": { "$dynamicRef": "extended#meta" } + } + } + } + }, + "tests": [ + { + "description": "The recursive part is valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "pass" } + } + }, + "valid": true + }, + { + "description": "The recursive part is not valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "fail" } + } + }, + "valid": false + } + ] + }, + { + "description": "A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/relative-dynamic-reference-without-bookend/root", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "foo": { "const": "pass" } + }, + "$ref": "extended", + "$defs": { + "extended": { + "$id": "extended", + "$anchor": "meta", + "type": "object", + "properties": { + "bar": { "$ref": "bar" } + } + }, + "bar": { + "$id": "bar", + "type": "object", + "properties": { + "baz": { "$dynamicRef": "extended#meta" } + } + } + } + }, + "tests": [ + { + "description": "The recursive part doesn't need to validate against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "fail" } + } + }, + "valid": true + } + ] + }, + { + "description": "multiple dynamic paths to the $dynamicRef keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main", + "if": { + "properties": { + "kindOfList": { "const": "numbers" } + }, + "required": ["kindOfList"] + }, + "then": { "$ref": "numberList" }, + "else": { "$ref": "stringList" }, + + "$defs": { + "genericList": { + "$id": "genericList", + "properties": { + "list": { + "items": { "$dynamicRef": "#itemType" } + } + }, + "$defs": { + "defaultItemType": { + "$comment": "Only needed to satisfy bookending requirement", + "$dynamicAnchor": "itemType" + } + } + }, + "numberList": { + "$id": "numberList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "number" + } + }, + "$ref": "genericList" + }, + "stringList": { + "$id": "stringList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "string" + } + }, + "$ref": "genericList" + } + } + }, + "tests": [ + { + "description": "number list with number values", + "data": { + "kindOfList": "numbers", + "list": [1.1] + }, + "valid": true + }, + { + "description": "number list with string values", + "data": { + "kindOfList": "numbers", + "list": ["foo"] + }, + "valid": false + }, + { + "description": "string list with number values", + "data": { + "kindOfList": "strings", + "list": [1.1] + }, + "valid": false + }, + { + "description": "string list with string values", + "data": { + "kindOfList": "strings", + "list": ["foo"] + }, + "valid": true + } + ] + }, + { + "description": "after leaving a dynamic scope, it is not used by a $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main", + "if": { + "$id": "first_scope", + "$defs": { + "thingy": { + "$comment": "this is first_scope#thingy", + "$dynamicAnchor": "thingy", + "type": "number" + } + } + }, + "then": { + "$id": "second_scope", + "$ref": "start", + "$defs": { + "thingy": { + "$comment": "this is second_scope#thingy, the final destination of the $dynamicRef", + "$dynamicAnchor": "thingy", + "type": "null" + } + } + }, + "$defs": { + "start": { + "$comment": "this is the landing spot from $ref", + "$id": "start", + "$dynamicRef": "inner_scope#thingy" + }, + "thingy": { + "$comment": "this is the first stop for the $dynamicRef", + "$id": "inner_scope", + "$dynamicAnchor": "thingy", + "type": "string" + } + } + }, + "tests": [ + { + "description": "string matches /$defs/thingy, but the $dynamicRef does not stop here", + "data": "a string", + "valid": false + }, + { + "description": "first_scope is not in dynamic scope for the $dynamicRef", + "data": 42, + "valid": false + }, + { + "description": "/then/$defs/thingy is the final stop for the $dynamicRef", + "data": null, + "valid": true + } + ] + }, + { + "description": "strict-tree schema, guards against misspelled properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-tree.json", + "$dynamicAnchor": "node", + + "$ref": "tree.json", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "instance with misspelled field", + "data": { + "children": [{ + "daat": 1 + }] + }, + "valid": false + }, + { + "description": "instance with correct field", + "data": { + "children": [{ + "data": 1 + }] + }, + "valid": true + } + ] + }, + { + "description": "tests for implementation dynamic anchor and reference link", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible.json", + "$ref": "extendible-dynamic-ref.json", + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $defs first", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json", + "allOf": [ + { + "$ref": "extendible-dynamic-ref.json" + }, + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $ref first", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json", + "allOf": [ + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + { + "$ref": "extendible-dynamic-ref.json" + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref to $dynamicRef finds detached $dynamicAnchor", + "schema": { + "$ref": "http://localhost:1234/draft2020-12/detached-dynamicref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$dynamicRef points to a boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "true": true, + "false": false + }, + "properties": { + "true": { + "$dynamicRef": "#/$defs/true" + }, + "false": { + "$dynamicRef": "#/$defs/false" + } + } + }, + "tests": [ + { + "description": "follow $dynamicRef to a true schema", + "data": { "true": 1 }, + "valid": true + }, + { + "description": "follow $dynamicRef to a false schema", + "data": { "false": 1 }, + "valid": false + } + ] + }, + { + "description": "$dynamicRef skips over intermediate resources - direct reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main", + "type": "object", + "properties": { + "bar-item": { + "$ref": "item" + } + }, + "$defs": { + "bar": { + "$id": "bar", + "type": "array", + "items": { + "$ref": "item" + }, + "$defs": { + "item": { + "$id": "item", + "type": "object", + "properties": { + "content": { + "$dynamicRef": "#content" + } + }, + "$defs": { + "defaultContent": { + "$dynamicAnchor": "content", + "type": "integer" + } + } + }, + "content": { + "$dynamicAnchor": "content", + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "integer property passes", + "data": { "bar-item": { "content": 42 } }, + "valid": true + }, + { + "description": "string property fails", + "data": { "bar-item": { "content": "value" } }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/enum.json b/src/test/suite/tests/draft2020-12/enum.json new file mode 100644 index 000000000..c8f35eacf --- /dev/null +++ b/src/test/suite/tests/draft2020-12/enum.json @@ -0,0 +1,358 @@ +[ + { + "description": "simple enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [1, 2, 3] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [6, "foo", [], true, {"foo": 12}] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [6, null] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [false] + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[false]] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [true] + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[true]] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [0] + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[0]] + }, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [1] + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[1]] + }, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ "hello\u0000there" ] + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/exclusiveMaximum.json b/src/test/suite/tests/draft2020-12/exclusiveMaximum.json new file mode 100644 index 000000000..05db23351 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/exclusiveMaximum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/exclusiveMinimum.json b/src/test/suite/tests/draft2020-12/exclusiveMinimum.json new file mode 100644 index 000000000..00af9d7ff --- /dev/null +++ b/src/test/suite/tests/draft2020-12/exclusiveMinimum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/format.json b/src/test/suite/tests/draft2020-12/format.json new file mode 100644 index 000000000..01adcbda3 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/format.json @@ -0,0 +1,838 @@ +[ + { + "description": "email format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "idn-email format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid regex string is only an annotation by default", + "data": "^(abc]", + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv4 string is only an annotation by default", + "data": "127.0.0.0.1", + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv6 string is only an annotation by default", + "data": "12345::", + "valid": true + } + ] + }, + { + "description": "idn-hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-hostname string is only an annotation by default", + "data": "〮실례.테스트", + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid hostname string is only an annotation by default", + "data": "-a-host-name-that-starts-with--", + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date string is only an annotation by default", + "data": "06/19/1963", + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date-time string is only an annotation by default", + "data": "1990-02-31T15:59:60.123-08:00", + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid time string is only an annotation by default", + "data": "08:30:06 PST", + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid json-pointer string is only an annotation by default", + "data": "/foo/bar~", + "valid": true + } + ] + }, + { + "description": "relative-json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid relative-json-pointer string is only an annotation by default", + "data": "/foo/bar", + "valid": true + } + ] + }, + { + "description": "iri format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri string is only an annotation by default", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": true + } + ] + }, + { + "description": "iri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\filëßåré", + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri string is only an annotation by default", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\fileshare", + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-template string is only an annotation by default", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": true + } + ] + }, + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uuid string is only an annotation by default", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": true + } + ] + }, + { + "description": "duration format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid duration string is only an annotation by default", + "data": "PT1D", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/if-then-else.json b/src/test/suite/tests/draft2020-12/if-then-else.json new file mode 100644 index 000000000..1c35d7e61 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/if-then-else.json @@ -0,0 +1,268 @@ +[ + { + "description": "ignore if without then or else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone if", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone if", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore then without if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone then", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone then", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore else without if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "else": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone else", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone else", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "if and then without else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid when if test fails", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if and else without then", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid when if test passes", + "data": -1, + "valid": true + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "validate against correct branch, then vs else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "non-interference across combined schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] + }, + "tests": [ + { + "description": "valid, but would have been invalid through then", + "data": -100, + "valid": true + }, + { + "description": "valid, but would have been invalid through else", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": true, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema true in if always chooses the then path (valid)", + "data": "then", + "valid": true + }, + { + "description": "boolean schema true in if always chooses the then path (invalid)", + "data": "else", + "valid": false + } + ] + }, + { + "description": "if with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": false, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema false in if always chooses the else path (invalid)", + "data": "then", + "valid": false + }, + { + "description": "boolean schema false in if always chooses the else path (valid)", + "data": "else", + "valid": true + } + ] + }, + { + "description": "if appears at the end when serialized (keyword processing sequence)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { "const": "yes" }, + "else": { "const": "other" }, + "if": { "maxLength": 4 } + }, + "tests": [ + { + "description": "yes redirects to then and passes", + "data": "yes", + "valid": true + }, + { + "description": "other redirects to else and passes", + "data": "other", + "valid": true + }, + { + "description": "no redirects to then and fails", + "data": "no", + "valid": false + }, + { + "description": "invalid redirects to else and fails", + "data": "invalid", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/infinite-loop-detection.json b/src/test/suite/tests/draft2020-12/infinite-loop-detection.json new file mode 100644 index 000000000..46f157a35 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/infinite-loop-detection.json @@ -0,0 +1,37 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/$defs/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/$defs/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/items.json b/src/test/suite/tests/draft2020-12/items.json new file mode 100644 index 000000000..6a3e1cf26 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/items.json @@ -0,0 +1,304 @@ +[ + { + "description": "a schema given for items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": true + }, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "item": { + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/sub-item" }, + { "$ref": "#/$defs/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "prefixItems with no additional items allowed", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{}, {}, {}], + "items": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "items does not look in applicators, valid case", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "prefixItems": [ { "minimum": 3 } ] } + ], + "items": { "minimum": 5 } + }, + "tests": [ + { + "description": "prefixItems in allOf does not constrain items, invalid case", + "data": [ 3, 5 ], + "valid": false + }, + { + "description": "prefixItems in allOf does not constrain items, valid case", + "data": [ 5, 5 ], + "valid": true + } + ] + }, + { + "description": "prefixItems validation adjusts the starting index for items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ { "type": "string" } ], + "items": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "items with heterogeneous array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{}], + "items": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "items with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/maxContains.json b/src/test/suite/tests/draft2020-12/maxContains.json new file mode 100644 index 000000000..8cd3ca741 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/maxContains.json @@ -0,0 +1,102 @@ +[ + { + "description": "maxContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "two items still valid against lone maxContains", + "data": [ 1, 2 ], + "valid": true + } + ] + }, + { + "description": "maxContains with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + }, + { + "description": "some elements match, valid maxContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "some elements match, invalid maxContains", + "data": [ 1, 2, 1 ], + "valid": false + } + ] + }, + { + "description": "maxContains with contains, value with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1.0 + }, + "tests": [ + { + "description": "one element matches, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains < maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 1, + "maxContains": 3 + }, + "tests": [ + { + "description": "actual < minContains < maxContains", + "data": [ ], + "valid": false + }, + { + "description": "minContains < actual < maxContains", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "minContains < maxContains < actual", + "data": [ 1, 1, 1, 1 ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/maxItems.json b/src/test/suite/tests/draft2020-12/maxItems.json new file mode 100644 index 000000000..f6a6b7c9a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/maxItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "maxItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/maxLength.json b/src/test/suite/tests/draft2020-12/maxLength.json new file mode 100644 index 000000000..7462726d7 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/maxLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "maxLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/maxProperties.json b/src/test/suite/tests/draft2020-12/maxProperties.json new file mode 100644 index 000000000..73ae7316f --- /dev/null +++ b/src/test/suite/tests/draft2020-12/maxProperties.json @@ -0,0 +1,79 @@ +[ + { + "description": "maxProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 0 + }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/maximum.json b/src/test/suite/tests/draft2020-12/maximum.json new file mode 100644 index 000000000..b99a541ea --- /dev/null +++ b/src/test/suite/tests/draft2020-12/maximum.json @@ -0,0 +1,60 @@ +[ + { + "description": "maximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 300 + }, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/minContains.json b/src/test/suite/tests/draft2020-12/minContains.json new file mode 100644 index 000000000..ee72d7d62 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/minContains.json @@ -0,0 +1,224 @@ +[ + { + "description": "minContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "zero items still valid against lone minContains", + "data": [], + "valid": true + } + ] + }, + { + "description": "minContains=1 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "no elements match", + "data": [ 2 ], + "valid": false + }, + { + "description": "single element matches, valid minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "all elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "some elements match, invalid minContains", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "all elements match, valid minContains (exactly as needed)", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "all elements match, valid minContains (more than needed)", + "data": [ 1, 1, 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains with a decimal value", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 2.0 + }, + "tests": [ + { + "description": "one element matches, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "both elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains = minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 2, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "all elements match, valid maxContains and minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains < minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1, + "minContains": 3 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "invalid maxContains and minContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains = 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 0 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "minContains = 0 makes contains always pass", + "data": [ 2 ], + "valid": true + } + ] + }, + { + "description": "minContains = 0 with maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 0, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "not more than maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many", + "data": [ 1, 1 ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/minItems.json b/src/test/suite/tests/draft2020-12/minItems.json new file mode 100644 index 000000000..9d6a8b6d2 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/minItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "minItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/minLength.json b/src/test/suite/tests/draft2020-12/minLength.json new file mode 100644 index 000000000..5076c5a92 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/minLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "minLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/minProperties.json b/src/test/suite/tests/draft2020-12/minProperties.json new file mode 100644 index 000000000..a753ad35f --- /dev/null +++ b/src/test/suite/tests/draft2020-12/minProperties.json @@ -0,0 +1,60 @@ +[ + { + "description": "minProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/minimum.json b/src/test/suite/tests/draft2020-12/minimum.json new file mode 100644 index 000000000..dc4405278 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/minimum.json @@ -0,0 +1,75 @@ +[ + { + "description": "minimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": 1.1 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": -2 + }, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/multipleOf.json b/src/test/suite/tests/draft2020-12/multipleOf.json new file mode 100644 index 000000000..92d6979b0 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/multipleOf.json @@ -0,0 +1,97 @@ +[ + { + "description": "by int", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 2 + }, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 1.5 + }, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 0.0001 + }, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", "multipleOf": 0.123456789 + }, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", "multipleOf": 1e-8 + }, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/not.json b/src/test/suite/tests/draft2020-12/not.json new file mode 100644 index 000000000..d0f2b6e84 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/not.json @@ -0,0 +1,301 @@ +[ + { + "description": "not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {} + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "forbid everything with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "allow everything with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": false + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "double negation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "not": {} } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "collect annotations inside a 'not', even if collection is disabled", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them", + "anyOf": [ + true, + { "properties": { "foo": true } } + ], + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "unevaluated property", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "annotations are still collected inside a 'not'", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/oneOf.json b/src/test/suite/tests/draft2020-12/oneOf.json new file mode 100644 index 000000000..7a7c7ffe3 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/oneOf.json @@ -0,0 +1,293 @@ +[ + { + "description": "oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, true, true] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, false, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [false, false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/anchor.json b/src/test/suite/tests/draft2020-12/optional/anchor.json new file mode 100644 index 000000000..6d6713be5 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/anchor.json @@ -0,0 +1,60 @@ +[ + { + "description": "$anchor inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $anchor buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "anchor_in_enum": { + "enum": [ + { + "$anchor": "my_anchor", + "type": "null" + } + ] + }, + "real_identifier_in_schema": { + "$anchor": "my_anchor", + "type": "string" + }, + "zzz_anchor_in_const": { + "const": { + "$anchor": "my_anchor", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/anchor_in_enum" }, + { "$ref": "#my_anchor" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$anchor": "my_anchor", + "type": "null" + }, + "valid": true + }, + { + "description": "in implementations that strip $anchor, this may match either $def", + "data": { + "type": "null" + }, + "valid": false + }, + { + "description": "match $ref to $anchor", + "data": "a string to match #/$defs/anchor_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $anchor", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/bignum.json b/src/test/suite/tests/draft2020-12/optional/bignum.json new file mode 100644 index 000000000..d69b29e85 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/bignum.json @@ -0,0 +1,110 @@ +[ + { + "description": "integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" + }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number" + }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMaximum": 972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": -18446744073709551615 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": -972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/cross-draft.json b/src/test/suite/tests/draft2020-12/optional/cross-draft.json new file mode 100644 index 000000000..5113bd649 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/cross-draft.json @@ -0,0 +1,18 @@ +[ + { + "description": "refs to historic drafts are processed as historic drafts", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "$ref": "http://localhost:1234/draft2019-09/ignore-prefixItems.json" + }, + "tests": [ + { + "description": "first item not a string is valid", + "comment": "if the implementation is not processing the $ref as a 2019-09 schema, this test will fail", + "data": [1, 2, 3], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/dependencies-compatibility.json b/src/test/suite/tests/draft2020-12/optional/dependencies-compatibility.json new file mode 100644 index 000000000..47d5bd79d --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/dependencies-compatibility.json @@ -0,0 +1,282 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "single schema dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "schema dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependencies": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/dynamicRef.json b/src/test/suite/tests/draft2020-12/optional/dynamicRef.json new file mode 100644 index 000000000..7e63f209a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/dynamicRef.json @@ -0,0 +1,56 @@ +[ + { + "description": "$dynamicRef skips over intermediate resources - pointer reference across resource boundary", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/optional/main", + "type": "object", + "properties": { + "bar-item": { + "$ref": "bar#/$defs/item" + } + }, + "$defs": { + "bar": { + "$id": "bar", + "type": "array", + "items": { + "$ref": "item" + }, + "$defs": { + "item": { + "$id": "item", + "type": "object", + "properties": { + "content": { + "$dynamicRef": "#content" + } + }, + "$defs": { + "defaultContent": { + "$dynamicAnchor": "content", + "type": "integer" + } + } + }, + "content": { + "$dynamicAnchor": "content", + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "integer property passes", + "data": { "bar-item": { "content": 42 } }, + "valid": true + }, + { + "description": "string property fails", + "data": { "bar-item": { "content": "value" } }, + "valid": false + } + ] + }] \ No newline at end of file diff --git a/src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json b/src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json new file mode 100644 index 000000000..a4d62e0cf --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json @@ -0,0 +1,582 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "\\p{Letter}cole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "\\wcole" + }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "[a-z]cole" + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^\\d+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^\\p{digit}+$" + }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "\\p{Letter}cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "\\wcole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "[a-z]cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "^\\d+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "^\\p{digit}+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/float-overflow.json b/src/test/suite/tests/draft2020-12/optional/float-overflow.json new file mode 100644 index 000000000..f5ae8b18f --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/float-overflow.json @@ -0,0 +1,17 @@ +[ + { + "description": "all integers are multiples of 0.5, if overflow is handled", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "multipleOf": 0.5 + }, + "tests": [ + { + "description": "valid if optional overflow handling is implemented", + "data": 1e308, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format-assertion.json b/src/test/suite/tests/draft2020-12/optional/format-assertion.json new file mode 100644 index 000000000..034003708 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format-assertion.json @@ -0,0 +1,42 @@ +[ + { + "description": "schema that uses custom metaschema with format-assertion: false", + "schema": { + "$id": "https://schema/using/format-assertion/false", + "$schema": "http://localhost:1234/draft2020-12/format-assertion-false.json", + "format": "ipv4" + }, + "tests": [ + { + "description": "format-assertion: false: valid string", + "data": "127.0.0.1", + "valid": true + }, + { + "description": "format-assertion: false: invalid string", + "data": "not-an-ipv4", + "valid": false + } + ] + }, + { + "description": "schema that uses custom metaschema with format-assertion: true", + "schema": { + "$id": "https://schema/using/format-assertion/true", + "$schema": "http://localhost:1234/draft2020-12/format-assertion-true.json", + "format": "ipv4" + }, + "tests": [ + { + "description": "format-assertion: true: valid string", + "data": "127.0.0.1", + "valid": true + }, + { + "description": "format-assertion: true: invalid string", + "data": "not-an-ipv4", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/date-time.json b/src/test/suite/tests/draft2020-12/optional/format/date-time.json new file mode 100644 index 000000000..8783d732a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/date-time.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of date-time strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/date.json b/src/test/suite/tests/draft2020-12/optional/format/date.json new file mode 100644 index 000000000..dfb1c80ad --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/date.json @@ -0,0 +1,246 @@ +[ + { + "description": "validation of date strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date string", + "data": "1963-06-19", + "valid": true + }, + { + "description": "a valid date string with 31 days in January", + "data": "2020-01-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in January", + "data": "2020-01-32", + "valid": false + }, + { + "description": "a valid date string with 28 days in February (normal)", + "data": "2021-02-28", + "valid": true + }, + { + "description": "a invalid date string with 29 days in February (normal)", + "data": "2021-02-29", + "valid": false + }, + { + "description": "a valid date string with 29 days in February (leap)", + "data": "2020-02-29", + "valid": true + }, + { + "description": "a invalid date string with 30 days in February (leap)", + "data": "2020-02-30", + "valid": false + }, + { + "description": "a valid date string with 31 days in March", + "data": "2020-03-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in March", + "data": "2020-03-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in April", + "data": "2020-04-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in April", + "data": "2020-04-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in May", + "data": "2020-05-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in May", + "data": "2020-05-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in June", + "data": "2020-06-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in June", + "data": "2020-06-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in July", + "data": "2020-07-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in July", + "data": "2020-07-32", + "valid": false + }, + { + "description": "a valid date string with 31 days in August", + "data": "2020-08-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in August", + "data": "2020-08-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in September", + "data": "2020-09-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in September", + "data": "2020-09-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in October", + "data": "2020-10-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in October", + "data": "2020-10-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in November", + "data": "2020-11-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in November", + "data": "2020-11-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in December", + "data": "2020-12-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in December", + "data": "2020-12-32", + "valid": false + }, + { + "description": "a invalid date string with invalid month", + "data": "2020-13-01", + "valid": false + }, + { + "description": "an invalid date string", + "data": "06/19/1963", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "non-padded month dates are not valid", + "data": "1998-1-20", + "valid": false + }, + { + "description": "non-padded day dates are not valid", + "data": "1998-01-1", + "valid": false + }, + { + "description": "invalid month", + "data": "1998-13-01", + "valid": false + }, + { + "description": "invalid month-day combination", + "data": "1998-04-31", + "valid": false + }, + { + "description": "2021 is not a leap year", + "data": "2021-02-29", + "valid": false + }, + { + "description": "2020 is a leap year", + "data": "2020-02-29", + "valid": true + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1963-06-1৪", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", + "data": "20230328", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", + "data": "2023-W01", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", + "data": "2023-W13-2", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", + "data": "2022W527", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/duration.json b/src/test/suite/tests/draft2020-12/optional/format/duration.json new file mode 100644 index 000000000..a09fec5ef --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/duration.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of duration strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid duration string", + "data": "P4DT12H30M5S", + "valid": true + }, + { + "description": "an invalid duration string", + "data": "PT1D", + "valid": false + }, + { + "description": "must start with P", + "data": "4DT12H30M5S", + "valid": false + }, + { + "description": "no elements present", + "data": "P", + "valid": false + }, + { + "description": "no time elements present", + "data": "P1YT", + "valid": false + }, + { + "description": "no date or time elements present", + "data": "PT", + "valid": false + }, + { + "description": "elements out of order", + "data": "P2D1Y", + "valid": false + }, + { + "description": "missing time separator", + "data": "P1D2H", + "valid": false + }, + { + "description": "time element in the date position", + "data": "P2S", + "valid": false + }, + { + "description": "four years duration", + "data": "P4Y", + "valid": true + }, + { + "description": "zero time, in seconds", + "data": "PT0S", + "valid": true + }, + { + "description": "zero time, in days", + "data": "P0D", + "valid": true + }, + { + "description": "one month duration", + "data": "P1M", + "valid": true + }, + { + "description": "one minute duration", + "data": "PT1M", + "valid": true + }, + { + "description": "one and a half days, in hours", + "data": "PT36H", + "valid": true + }, + { + "description": "one and a half days, in days and hours", + "data": "P1DT12H", + "valid": true + }, + { + "description": "two weeks", + "data": "P2W", + "valid": true + }, + { + "description": "weeks cannot be combined with other units", + "data": "P1Y2W", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "P২Y", + "valid": false + }, + { + "description": "element without unit", + "data": "P1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/ecmascript-regex.json b/src/test/suite/tests/draft2020-12/optional/format/ecmascript-regex.json new file mode 100644 index 000000000..b0648084a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/ecmascript-regex.json @@ -0,0 +1,16 @@ +[ + { + "description": "\\a is not an ECMA 262 control escape", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + }, + "tests": [ + { + "description": "when used as a pattern", + "data": "\\a", + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/src/test/suite/tests/draft2020-12/optional/format/email.json b/src/test/suite/tests/draft2020-12/optional/format/email.json new file mode 100644 index 000000000..925c2f2e3 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/email.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "a quoted string with a space in the local part is valid", + "data": "\"joe bloggs\"@example.com", + "valid": true + }, + { + "description": "a quoted string with a double dot in the local part is valid", + "data": "\"joe..bloggs\"@example.com", + "valid": true + }, + { + "description": "a quoted string with a @ in the local part is valid", + "data": "\"joe@bloggs\"@example.com", + "valid": true + }, + { + "description": "an IPv4-address-literal after the @ is valid", + "data": "joe.bloggs@[127.0.0.1]", + "valid": true + }, + { + "description": "an IPv6-address-literal after the @ is valid", + "data": "joe.bloggs@[IPv6:::1]", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "an invalid domain", + "data": "joe.bloggs@invalid=domain.com", + "valid": false + }, + { + "description": "an invalid IPv4-address-literal", + "data": "joe.bloggs@[127.0.0.300]", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/hostname.json b/src/test/suite/tests/draft2020-12/optional/format/hostname.json new file mode 100644 index 000000000..57827c4d4 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/hostname.json @@ -0,0 +1,136 @@ +[ + { + "description": "validation of host names", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/idn-email.json b/src/test/suite/tests/draft2020-12/optional/format/idn-email.json new file mode 100644 index 000000000..50f3c23ce --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/idn-email.json @@ -0,0 +1,61 @@ +[ + { + "description": "validation of an internationalized e-mail addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid idn e-mail (example@example.test in Hangul)", + "data": "실례@실례.테스트", + "valid": true + }, + { + "description": "an invalid idn e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json b/src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json new file mode 100644 index 000000000..f42ae969b --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json @@ -0,0 +1,389 @@ +[ + { + "description": "validation of internationalized host names", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name (example.test in Hangul)", + "data": "실례.테스트", + "valid": true + }, + { + "description": "illegal first char U+302E Hangul single dot tone mark", + "data": "〮실례.테스트", + "valid": false + }, + { + "description": "contains illegal char U+302E Hangul single dot tone mark", + "data": "실〮례.테스트", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", + "valid": false + }, + { + "description": "invalid label, correct Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc3492#section-7.1", + "data": "-> $1.00 <--", + "valid": false + }, + { + "description": "valid Chinese Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4", + "data": "xn--ihqwcrb4cv8a8dqg056pqjye", + "valid": true + }, + { + "description": "invalid Punycode", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "xn--X", + "valid": false + }, + { + "description": "U-label contains \"--\" in the 3rd and 4th position", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "XN--aa---o47jg78q", + "valid": false + }, + { + "description": "U-label starts with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello", + "valid": false + }, + { + "description": "U-label ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "hello-", + "valid": false + }, + { + "description": "U-label starts and ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello-", + "valid": false + }, + { + "description": "Begins with a Spacing Combining Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0903hello", + "valid": false + }, + { + "description": "Begins with a Nonspacing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0300hello", + "valid": false + }, + { + "description": "Begins with an Enclosing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0488hello", + "valid": false + }, + { + "description": "Exceptions that are PVALID, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u00df\u03c2\u0f0b\u3007", + "valid": true + }, + { + "description": "Exceptions that are PVALID, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u06fd\u06fe", + "valid": true + }, + { + "description": "Exceptions that are DISALLOWED, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u0640\u07fa", + "valid": false + }, + { + "description": "Exceptions that are DISALLOWED, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6 Note: The two combining marks (U+302E and U+302F) are in the middle and not at the start", + "data": "\u3031\u3032\u3033\u3034\u3035\u302e\u302f\u303b", + "valid": false + }, + { + "description": "MIDDLE DOT with no preceding 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "a\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing preceding", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with no following 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7a", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing following", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7", + "valid": false + }, + { + "description": "MIDDLE DOT with surrounding 'l's", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7l", + "valid": true + }, + { + "description": "Greek KERAIA not followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375S", + "valid": false + }, + { + "description": "Greek KERAIA not followed by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375", + "valid": false + }, + { + "description": "Greek KERAIA followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375\u03b2", + "valid": true + }, + { + "description": "Hebrew GERESH not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "A\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05d0\u05f3\u05d1", + "valid": true + }, + { + "description": "Hebrew GERSHAYIM not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "A\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05d0\u05f4\u05d1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "def\u30fbabc", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with no other characters", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with Hiragana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u3041", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Katakana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u30a1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u4e08", + "valid": true + }, + { + "description": "Arabic-Indic digits mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u06f0", + "valid": false + }, + { + "description": "Arabic-Indic digits not mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u0628", + "valid": true + }, + { + "description": "Extended Arabic-Indic digits not mixed with Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.9", + "data": "\u06f00", + "valid": true + }, + { + "description": "ZERO WIDTH JOINER not preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u094d\u200d\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1", + "data": "\u0915\u094d\u200c\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER not preceded by Virama but matches regexp", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1 https://www.w3.org/TR/alreq/#h_disjoining_enforcement", + "data": "\u0628\u064a\u200c\u0628\u064a", + "valid": true + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label starting with digit", + "data": "1host", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + } + ] + }, + { + "description": "validation of separators in internationalized host names", + "specification": [ + {"rfc3490": "3.1", "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)"} + ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "single dot", + "data": ".", + "valid": false + }, + { + "description": "single ideographic full stop", + "data": "\u3002", + "valid": false + }, + { + "description": "single fullwidth full stop", + "data": "\uff0e", + "valid": false + }, + { + "description": "single halfwidth ideographic full stop", + "data": "\uff61", + "valid": false + }, + { + "description": "dot as label separator", + "data": "a.b", + "valid": true + }, + { + "description": "ideographic full stop as label separator", + "data": "a\u3002b", + "valid": true + }, + { + "description": "fullwidth full stop as label separator", + "data": "a\uff0eb", + "valid": true + }, + { + "description": "halfwidth ideographic full stop as label separator", + "data": "a\uff61b", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/ipv4.json b/src/test/suite/tests/draft2020-12/optional/format/ipv4.json new file mode 100644 index 000000000..86d27bdb7 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/ipv4.json @@ -0,0 +1,92 @@ +[ + { + "description": "validation of IP addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/ipv6.json b/src/test/suite/tests/draft2020-12/optional/format/ipv6.json new file mode 100644 index 000000000..b9e570c9a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/ipv6.json @@ -0,0 +1,211 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/iri-reference.json b/src/test/suite/tests/draft2020-12/optional/format/iri-reference.json new file mode 100644 index 000000000..0c9483dc2 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/iri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of IRI References", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid protocol-relative IRI Reference", + "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid relative IRI Reference", + "data": "/âππ", + "valid": true + }, + { + "description": "an invalid IRI Reference", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "a valid IRI Reference", + "data": "âππ", + "valid": true + }, + { + "description": "a valid IRI fragment", + "data": "#ƒrägmênt", + "valid": true + }, + { + "description": "an invalid IRI fragment", + "data": "#ƒräg\\mênt", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/iri.json b/src/test/suite/tests/draft2020-12/optional/format/iri.json new file mode 100644 index 000000000..311c9ef08 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/iri.json @@ -0,0 +1,86 @@ +[ + { + "description": "validation of IRIs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI with anchor tag", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid IRI with anchor tag and parentheses", + "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", + "valid": true + }, + { + "description": "a valid IRI with URL-encoded stuff", + "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid IRI with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid IRI based on IPv6", + "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + "valid": true + }, + { + "description": "an invalid IRI based on IPv6", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": false + }, + { + "description": "an invalid relative IRI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid IRI", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "an invalid IRI though valid IRI reference", + "data": "âππ", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/json-pointer.json b/src/test/suite/tests/draft2020-12/optional/format/json-pointer.json new file mode 100644 index 000000000..71ba9b600 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/json-pointer.json @@ -0,0 +1,201 @@ +[ + { + "description": "validation of JSON-pointers (JSON String Representation)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid JSON-pointer", + "data": "/foo/bar~0/baz~1/%a", + "valid": true + }, + { + "description": "not a valid JSON-pointer (~ not escaped)", + "data": "/foo/bar~", + "valid": false + }, + { + "description": "valid JSON-pointer with empty segment", + "data": "/foo//bar", + "valid": true + }, + { + "description": "valid JSON-pointer with the last empty segment", + "data": "/foo/bar/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #1", + "data": "", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #2", + "data": "/foo", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #3", + "data": "/foo/0", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #4", + "data": "/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #5", + "data": "/a~1b", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #6", + "data": "/c%d", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #7", + "data": "/e^f", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #8", + "data": "/g|h", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #9", + "data": "/i\\j", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #10", + "data": "/k\"l", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #11", + "data": "/ ", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #12", + "data": "/m~0n", + "valid": true + }, + { + "description": "valid JSON-pointer used adding to the last array position", + "data": "/foo/-", + "valid": true + }, + { + "description": "valid JSON-pointer (- used as object member name)", + "data": "/foo/-/bar", + "valid": true + }, + { + "description": "valid JSON-pointer (multiple escaped characters)", + "data": "/~1~0~0~1~1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #1", + "data": "/~1.1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #2", + "data": "/~0.1", + "valid": true + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", + "data": "#", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", + "data": "#/", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", + "data": "#a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #1", + "data": "/~0~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #2", + "data": "/~0/~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #1", + "data": "/~2", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #2", + "data": "/~-1", + "valid": false + }, + { + "description": "not a valid JSON-pointer (multiple characters not escaped)", + "data": "/~~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", + "data": "a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", + "data": "0", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", + "data": "a/a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/regex.json b/src/test/suite/tests/draft2020-12/optional/format/regex.json new file mode 100644 index 000000000..a036c6de8 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/regex.json @@ -0,0 +1,51 @@ +[ + { + "description": "validation of regular expressions", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid regular expression", + "data": "([abc])+\\s+$", + "valid": true + }, + { + "description": "a regular expression with unclosed parens is invalid", + "data": "^(abc]", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/relative-json-pointer.json b/src/test/suite/tests/draft2020-12/optional/format/relative-json-pointer.json new file mode 100644 index 000000000..3eaf9ce2d --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/relative-json-pointer.json @@ -0,0 +1,101 @@ +[ + { + "description": "validation of Relative JSON Pointers (RJP)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid upwards RJP", + "data": "1", + "valid": true + }, + { + "description": "a valid downwards RJP", + "data": "0/foo/bar", + "valid": true + }, + { + "description": "a valid up and then down RJP, with array index", + "data": "2/0/baz/1/zip", + "valid": true + }, + { + "description": "a valid RJP taking the member or index name", + "data": "0#", + "valid": true + }, + { + "description": "an invalid RJP that is a valid JSON Pointer", + "data": "/foo/bar", + "valid": false + }, + { + "description": "negative prefix", + "data": "-1/foo/bar", + "valid": false + }, + { + "description": "explicit positive prefix", + "data": "+1/foo/bar", + "valid": false + }, + { + "description": "## is not a valid json-pointer", + "data": "0##", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus json-pointer", + "data": "01/a", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus octothorpe", + "data": "01#", + "valid": false + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "multi-digit integer prefix", + "data": "120/foo/bar", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/time.json b/src/test/suite/tests/draft2020-12/optional/format/time.json new file mode 100644 index 000000000..8967932e9 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/time.json @@ -0,0 +1,236 @@ +[ + { + "description": "validation of time strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid time string", + "data": "08:30:06Z", + "valid": true + }, + { + "description": "invalid time string with extra leading zeros", + "data": "008:030:006Z", + "valid": false + }, + { + "description": "invalid time string with no leading zero for single digit", + "data": "8:3:6Z", + "valid": false + }, + { + "description": "hour, minute, second must be two digits", + "data": "8:0030:6Z", + "valid": false + }, + { + "description": "a valid time string with leap second, Zulu", + "data": "23:59:60Z", + "valid": true + }, + { + "description": "invalid leap second, Zulu (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "invalid leap second, Zulu (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "valid leap second, zero time-offset", + "data": "23:59:60+00:00", + "valid": true + }, + { + "description": "invalid leap second, zero time-offset (wrong hour)", + "data": "22:59:60+00:00", + "valid": false + }, + { + "description": "invalid leap second, zero time-offset (wrong minute)", + "data": "23:58:60+00:00", + "valid": false + }, + { + "description": "valid leap second, positive time-offset", + "data": "01:29:60+01:30", + "valid": true + }, + { + "description": "valid leap second, large positive time-offset", + "data": "23:29:60+23:30", + "valid": true + }, + { + "description": "invalid leap second, positive time-offset (wrong hour)", + "data": "23:59:60+01:00", + "valid": false + }, + { + "description": "invalid leap second, positive time-offset (wrong minute)", + "data": "23:59:60+00:30", + "valid": false + }, + { + "description": "valid leap second, negative time-offset", + "data": "15:59:60-08:00", + "valid": true + }, + { + "description": "valid leap second, large negative time-offset", + "data": "00:29:60-23:30", + "valid": true + }, + { + "description": "invalid leap second, negative time-offset (wrong hour)", + "data": "23:59:60-01:00", + "valid": false + }, + { + "description": "invalid leap second, negative time-offset (wrong minute)", + "data": "23:59:60-00:30", + "valid": false + }, + { + "description": "a valid time string with second fraction", + "data": "23:20:50.52Z", + "valid": true + }, + { + "description": "a valid time string with precise second fraction", + "data": "08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid time string with plus offset", + "data": "08:30:06+00:20", + "valid": true + }, + { + "description": "a valid time string with minus offset", + "data": "08:30:06-08:00", + "valid": true + }, + { + "description": "hour, minute in time-offset must be two digits", + "data": "08:30:06-8:000", + "valid": false + }, + { + "description": "a valid time string with case-insensitive Z", + "data": "08:30:06z", + "valid": true + }, + { + "description": "an invalid time string with invalid hour", + "data": "24:00:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid minute", + "data": "00:60:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid second", + "data": "00:00:61Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset hour", + "data": "01:02:03+24:00", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset minute", + "data": "01:02:03+00:60", + "valid": false + }, + { + "description": "an invalid time string with invalid time with both Z and numoffset", + "data": "01:02:03Z+00:30", + "valid": false + }, + { + "description": "an invalid offset indicator", + "data": "08:30:06 PST", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "01:01:01,1111", + "valid": false + }, + { + "description": "no time offset", + "data": "12:00:00", + "valid": false + }, + { + "description": "no time offset with second fraction", + "data": "12:00:00.52", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২:00:00Z", + "valid": false + }, + { + "description": "offset not starting with plus or minus", + "data": "08:30:06#00:20", + "valid": false + }, + { + "description": "contains letters", + "data": "ab:cd:ef", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/unknown.json b/src/test/suite/tests/draft2020-12/optional/format/unknown.json new file mode 100644 index 000000000..7fc35f536 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/unknown.json @@ -0,0 +1,46 @@ +[ + { + "description": "unknown format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "unknown" + }, + "tests": [ + { + "description": "unknown formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "unknown formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "unknown formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "unknown formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "unknown formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "unknown formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "unknown formats ignore strings", + "data": "string", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/uri-reference.json b/src/test/suite/tests/draft2020-12/optional/format/uri-reference.json new file mode 100644 index 000000000..46f28e6c3 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/uri-reference.json @@ -0,0 +1,76 @@ +[ + { + "description": "validation of URI References", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid relative URI Reference", + "data": "/abc", + "valid": true + }, + { + "description": "an invalid URI Reference", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "a valid URI Reference", + "data": "abc", + "valid": true + }, + { + "description": "a valid URI fragment", + "data": "#fragment", + "valid": true + }, + { + "description": "an invalid URI fragment", + "data": "#frag\\ment", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/uri-template.json b/src/test/suite/tests/draft2020-12/optional/format/uri-template.json new file mode 100644 index 000000000..08aab829a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/uri-template.json @@ -0,0 +1,61 @@ +[ + { + "description": "format: uri-template", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", + "valid": true + }, + { + "description": "an invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false + }, + { + "description": "a valid uri-template without variables", + "data": "http://example.com/dictionary", + "valid": true + }, + { + "description": "a valid relative uri-template", + "data": "dictionary/{term:1}/{term}", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/uri.json b/src/test/suite/tests/draft2020-12/optional/format/uri.json new file mode 100644 index 000000000..84b5f15e4 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/uri.json @@ -0,0 +1,141 @@ +[ + { + "description": "validation of URIs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/format/uuid.json b/src/test/suite/tests/draft2020-12/optional/format/uuid.json new file mode 100644 index 000000000..d152643d1 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/format/uuid.json @@ -0,0 +1,116 @@ +[ + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "all upper-case", + "data": "2EB8AA08-AA98-11EA-B4AA-73B441D16380", + "valid": true + }, + { + "description": "all lower-case", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d16380", + "valid": true + }, + { + "description": "mixed case", + "data": "2eb8aa08-AA98-11ea-B4Aa-73B441D16380", + "valid": true + }, + { + "description": "all zeroes is valid", + "data": "00000000-0000-0000-0000-000000000000", + "valid": true + }, + { + "description": "wrong length", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": false + }, + { + "description": "missing section", + "data": "2eb8aa08-aa98-11ea-73b441d16380", + "valid": false + }, + { + "description": "bad characters (not hex)", + "data": "2eb8aa08-aa98-11ea-b4ga-73b441d16380", + "valid": false + }, + { + "description": "no dashes", + "data": "2eb8aa08aa9811eab4aa73b441d16380", + "valid": false + }, + { + "description": "too few dashes", + "data": "2eb8aa08aa98-11ea-b4aa73b441d16380", + "valid": false + }, + { + "description": "too many dashes", + "data": "2eb8-aa08-aa98-11ea-b4aa73b44-1d16380", + "valid": false + }, + { + "description": "dashes in the wrong spot", + "data": "2eb8aa08aa9811eab4aa73b441d16380----", + "valid": false + }, + { + "description": "valid version 4", + "data": "98d80576-482e-427f-8434-7f86890ab222", + "valid": true + }, + { + "description": "valid version 5", + "data": "99c17cbb-656f-564a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 6", + "data": "99c17cbb-656f-664a-940f-1a4568f03487", + "valid": true + }, + { + "description": "hypothetical version 15", + "data": "99c17cbb-656f-f64a-940f-1a4568f03487", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/id.json b/src/test/suite/tests/draft2020-12/optional/id.json new file mode 100644 index 000000000..0b7df4e80 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/id.json @@ -0,0 +1,53 @@ +[ + { + "description": "$id inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an $id buried in the enum", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "id_in_enum": { + "enum": [ + { + "$id": "https://localhost:1234/draft2020-12/id/my_identifier.json", + "type": "null" + } + ] + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft2020-12/id/my_identifier.json", + "type": "string" + }, + "zzz_id_in_const": { + "const": { + "$id": "https://localhost:1234/draft2020-12/id/my_identifier.json", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_enum" }, + { "$ref": "https://localhost:1234/draft2020-12/id/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$id": "https://localhost:1234/draft2020-12/id/my_identifier.json", + "type": "null" + }, + "valid": true + }, + { + "description": "match $ref to $id", + "data": "a string to match #/$defs/id_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to $id", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/no-schema.json b/src/test/suite/tests/draft2020-12/optional/no-schema.json new file mode 100644 index 000000000..676e6b53a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/no-schema.json @@ -0,0 +1,26 @@ +[ + { + "description": "validation without $schema", + "comment": "minLength is the same across all drafts", + "schema": { + "minLength": 2 + }, + "tests": [ + { + "description": "a 3-character string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a 1-character string is not valid", + "data": "a", + "valid": false + }, + { + "description": "a non-string is valid", + "data": 5, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/non-bmp-regex.json b/src/test/suite/tests/draft2020-12/optional/non-bmp-regex.json new file mode 100644 index 000000000..d2efb3ef6 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/non-bmp-regex.json @@ -0,0 +1,86 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^🐲*$" + }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/refOfUnknownKeyword.json b/src/test/suite/tests/draft2020-12/optional/refOfUnknownKeyword.json new file mode 100644 index 000000000..c2b080a1e --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/refOfUnknownKeyword.json @@ -0,0 +1,69 @@ +[ + { + "description": "reference of a root arbitrary keyword ", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unknown-keyword": {"type": "integer"}, + "properties": { + "bar": {"$ref": "#/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference of an arbitrary keyword of a sub-schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"unknown-keyword": {"type": "integer"}}, + "bar": {"$ref": "#/properties/foo/unknown-keyword"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "reference internals of known non-applicator", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "/base", + "examples": [ + { "type": "string" } + ], + "$ref": "#/examples/0" + }, + "tests": [ + { + "description": "match", + "data": "a string", + "valid": true + }, + { + "description": "mismatch", + "data": 42, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/optional/unknownKeyword.json b/src/test/suite/tests/draft2020-12/optional/unknownKeyword.json new file mode 100644 index 000000000..28b0c4ce9 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/optional/unknownKeyword.json @@ -0,0 +1,57 @@ +[ + { + "description": "$id inside an unknown keyword is not a real identifier", + "comment": "the implementation must not be confused by an $id in locations we do not know how to parse", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "id_in_unknown0": { + "not": { + "array_of_schemas": [ + { + "$id": "https://localhost:1234/draft2020-12/unknownKeyword/my_identifier.json", + "type": "null" + } + ] + } + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/draft2020-12/unknownKeyword/my_identifier.json", + "type": "string" + }, + "id_in_unknown1": { + "not": { + "object_of_schemas": { + "foo": { + "$id": "https://localhost:1234/draft2020-12/unknownKeyword/my_identifier.json", + "type": "integer" + } + } + } + } + }, + "anyOf": [ + { "$ref": "#/$defs/id_in_unknown0" }, + { "$ref": "#/$defs/id_in_unknown1" }, + { "$ref": "https://localhost:1234/draft2020-12/unknownKeyword/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "type matches second anyOf, which has a real schema in it", + "data": "a string", + "valid": true + }, + { + "description": "type matches non-schema in first anyOf", + "data": null, + "valid": false + }, + { + "description": "type matches non-schema in third anyOf", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/pattern.json b/src/test/suite/tests/draft2020-12/pattern.json new file mode 100644 index 000000000..af0b8d89b --- /dev/null +++ b/src/test/suite/tests/draft2020-12/pattern.json @@ -0,0 +1,65 @@ +[ + { + "description": "pattern validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^a*$" + }, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "a+" + }, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/patternProperties.json b/src/test/suite/tests/draft2020-12/patternProperties.json new file mode 100644 index 000000000..81829c71f --- /dev/null +++ b/src/test/suite/tests/draft2020-12/patternProperties.json @@ -0,0 +1,176 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/prefixItems.json b/src/test/suite/tests/draft2020-12/prefixItems.json new file mode 100644 index 000000000..0adfc069e --- /dev/null +++ b/src/test/suite/tests/draft2020-12/prefixItems.json @@ -0,0 +1,104 @@ +[ + { + "description": "a schema given for prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "prefixItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "additional items are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "integer"}] + }, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "prefixItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/properties.json b/src/test/suite/tests/draft2020-12/properties.json new file mode 100644 index 000000000..eb66fa8bd --- /dev/null +++ b/src/test/suite/tests/draft2020-12/properties.json @@ -0,0 +1,242 @@ +[ + { + "description": "object properties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/propertyNames.json b/src/test/suite/tests/draft2020-12/propertyNames.json new file mode 100644 index 000000000..b4780088a --- /dev/null +++ b/src/test/suite/tests/draft2020-12/propertyNames.json @@ -0,0 +1,168 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames validation with pattern", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": true + }, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": false + }, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with const", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"const": "foo"} + }, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with enum", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"enum": ["foo", "bar"]} + }, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property foo and bar is valid", + "data": {"foo": 1, "bar": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"baz": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/ref.json b/src/test/suite/tests/draft2020-12/ref.json new file mode 100644 index 000000000..a1d3efaf7 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/ref.json @@ -0,0 +1,1052 @@ +[ + { + "description": "root pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {"type": "integer"}, + {"$ref": "#/prefixItems/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/$defs/tilde~0field"}, + "slash": {"$ref": "#/$defs/slash~1field"}, + "percent": {"$ref": "#/$defs/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "a": {"type": "integer"}, + "b": {"$ref": "#/$defs/a"}, + "c": {"$ref": "#/$defs/b"} + }, + "$ref": "#/$defs/c" + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref applies alongside sibling keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/$defs/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid, maxItems valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems invalid", + "data": { "foo": [1, 2, 3] }, + "valid": false + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://json-schema.org/draft/2020-12/schema" + }, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": {"$ref": "#/$defs/is-string"} + }, + "$defs": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "$defs": { + "node": { + "$id": "http://localhost:1234/draft2020-12/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\"bar": {"$ref": "#/$defs/foo%22bar"} + }, + "$defs": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "ref creates new scope when adjacent to keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "unevaluatedProperties": false + } + }, + "properties": { + "prop1": { + "type": "string" + } + }, + "$ref": "#/$defs/A" + }, + "tests": [ + { + "description": "referenced subschema doesn't see annotations from properties", + "data": { + "prop1": "match" + }, + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/$defs/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/$defs/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-relative-uri-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-refs-absolute-uris-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "$id must be resolved against nearest parent, not just immediate parent", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/a.json", + "$defs": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "$defs": { + "y": { + "$id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $ref", + "schema": { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id1/base.json", + "$ref": "int.json", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/ref-and-id1/int.json", + "$id": "int.json", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id1-int.json", + "$id": "/draft2020-12/ref-and-id1-int.json", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $anchor and $ref", + "schema": { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id2/base.json", + "$ref": "#bigint", + "$defs": { + "bigint": { + "$comment": "canonical uri: /ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: /ref-and-id2/base.json#bigint", + "$anchor": "bigint", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", + "$id": "https://example.com/draft2020-12/ref-and-id2/", + "$anchor": "bigint", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$comment": "RFC 8141 §2.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$comment": "RFC 8141 §2.3.1", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$comment": "RFC 8141 §2.3.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "$defs": { + "bar": { + "$anchor": "something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN ref with nested pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": { + "foo": { + "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "bar", + "valid": true + }, + { + "description": "a non-string is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "ref to if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/if", + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to then", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/then", + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/else", + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/ref/absref.json", + "$defs": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "$ref": "/absref/foobar.json" + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///c:/folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "": { + "$defs": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs//$defs/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/refRemote.json b/src/test/suite/tests/draft2020-12/refRemote.json new file mode 100644 index 000000000..047ac74ca --- /dev/null +++ b/src/test/suite/tests/draft2020-12/refRemote.json @@ -0,0 +1,342 @@ +[ + { + "description": "remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/integer.json" + }, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/subSchemas.json#/$defs/integer" + }, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "anchor within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#foo" + }, + "tests": [ + { + "description": "remote anchor valid", + "data": 1, + "valid": true + }, + { + "description": "remote anchor invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/subSchemas.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/scope_change_defs1.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolder/"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/scope_change_defs2.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolderInSubschema/#/$defs/bar"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "$defs": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/object", + "type": "object", + "properties": { + "name": {"$ref": "name-defs.json#/$defs/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/schema-remote-ref-ref-defs1.json", + "$ref": "ref-and-defs.json" + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/different-id-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different URN $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/urn-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with nested absolute ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/nested-absolute-ref-to-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds detached $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/detached-ref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/required.json b/src/test/suite/tests/draft2020-12/required.json new file mode 100644 index 000000000..b7cb99a63 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/required.json @@ -0,0 +1,158 @@ +[ + { + "description": "required validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": ["__proto__", "toString", "constructor"] + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/type.json b/src/test/suite/tests/draft2020-12/type.json new file mode 100644 index 000000000..2123c408d --- /dev/null +++ b/src/test/suite/tests/draft2020-12/type.json @@ -0,0 +1,501 @@ +[ + { + "description": "integer type matches integers", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" + }, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number" + }, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + }, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object" + }, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array" + }, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "boolean" + }, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "null" + }, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["integer", "string"] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/unevaluatedItems.json b/src/test/suite/tests/draft2020-12/unevaluatedItems.json new file mode 100644 index 000000000..9614c5855 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/unevaluatedItems.json @@ -0,0 +1,829 @@ +[ + { + "description": "unevaluatedItems true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": true + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems as schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": { "type": "string" } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with valid unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with invalid unevaluated items", + "data": [42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with uniform items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { "type": "string" }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with items and prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "items": true, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": {"type": "number"}, + "unevaluatedItems": {"type": "string"} + }, + "tests": [ + { + "description": "valid under items", + "comment": "no elements are considered by unevaluatedItems", + "data": [5, 6, 7, 8], + "valid": true + }, + { + "description": "invalid under items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "allOf": [ + { + "prefixItems": [ + true, + { "type": "number" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", 42], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", 42, true], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": {"type": "boolean"}, + "anyOf": [ + { "items": {"type": "string"} }, + true + ] + }, + "tests": [ + { + "description": "with only (valid) additional items", + "data": [true, false], + "valid": true + }, + { + "description": "with no additional items", + "data": ["yes", "no"], + "valid": true + }, + { + "description": "with invalid additional item", + "data": ["yes", false], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested prefixItems and items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ], + "items": true + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with nested unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ] + }, + { "unevaluatedItems": true } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "anyOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when one schema matches and has no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "when one schema matches and has unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + }, + { + "description": "when two schemas match and has no unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "when two schemas match and has unevaluated items", + "data": ["foo", "bar", "baz", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "oneOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "not": { + "not": { + "prefixItems": [ + true, + { "const": "bar" } + ] + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "if": { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + "then": { + "prefixItems": [ + true, + true, + { "const": "then" } + ] + }, + "else": { + "prefixItems": [ + true, + true, + true, + { "const": "else" } + ] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when if matches and it has no unevaluated items", + "data": ["foo", "bar", "then"], + "valid": true + }, + { + "description": "when if matches and it has unevaluated items", + "data": ["foo", "bar", "then", "else"], + "valid": false + }, + { + "description": "when if doesn't match and it has no unevaluated items", + "data": ["foo", 42, 42, "else"], + "valid": true + }, + { + "description": "when if doesn't match and it has unevaluated items", + "data": ["foo", 42, 42, "else", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bar", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false, + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false, + "prefixItems": [ + { "type": "string" } + ], + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/unevaluated-items-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "prefixItems": [ + true, + { "type": "string" } + ] + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedItems": false, + "type": "array", + "prefixItems": [ + { "type": "string" } + ], + "$dynamicRef": "#addons", + + "$defs": { + "defaultAddons": { + "$comment": "Needed to satisfy the bookending requirement", + "$dynamicAnchor": "addons" + } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ true ] + }, + { "unevaluatedItems": false } + ] + }, + "tests": [ + { + "description": "always fails", + "data": [ 1 ], + "valid": false + } + ] + }, + { + "description": "item is evaluated in an uncle schema to unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + } + ] + }, + "tests": [ + { + "description": "no extra items", + "data": { + "foo": [ + "test" + ] + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": [ + "test", + "test" + ] + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on adjacent contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [true], + "contains": {"type": "string"}, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "second item is evaluated by contains", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "contains fails, second item is not evaluated", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "contains passes, second item is not evaluated", + "data": [ 1, 2, "foo" ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on multiple nested contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "contains": { "multipleOf": 2 } }, + { "contains": { "multipleOf": 3 } } + ], + "unevaluatedItems": { "multipleOf": 5 } + }, + "tests": [ + { + "description": "5 not evaluated, passes unevaluatedItems", + "data": [ 2, 3, 4, 5, 6 ], + "valid": true + }, + { + "description": "7 not evaluated, fails unevaluatedItems", + "data": [ 2, 3, 4, 7, 8 ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems and contains interact to control item dependency relationship", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "contains": {"const": "a"} + }, + "then": { + "if": { + "contains": {"const": "b"} + }, + "then": { + "if": { + "contains": {"const": "c"} + } + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "only a's are valid", + "data": [ "a", "a" ], + "valid": true + }, + { + "description": "a's and b's are valid", + "data": [ "a", "b", "a", "b", "a" ], + "valid": true + }, + { + "description": "a's, b's and c's are valid", + "data": [ "c", "a", "c", "c", "b", "a" ], + "valid": true + }, + { + "description": "only b's are invalid", + "data": [ "b", "b" ], + "valid": false + }, + { + "description": "only c's are invalid", + "data": [ "c", "c" ], + "valid": false + }, + { + "description": "only b's and c's are invalid", + "data": [ "c", "b", "c", "b", "c" ], + "valid": false + }, + { + "description": "only a's and c's are invalid", + "data": [ "c", "a", "c", "a", "c" ], + "valid": false + } + ] + }, + { + "description" : "unevaluatedItems with minContains = 0", + "schema" : { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"type": "string"}, + "minContains": 0, + "unevaluatedItems": false + }, + "tests" : [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "no items evaluated by contains", + "data": [0], + "valid": false + }, + { + "description": "some but not all items evaluated by contains", + "data": ["foo", 0], + "valid": false + }, + { + "description": "all items evaluated by contains", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "non-array instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "prefixItems": [{"const": "a"}] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": [ "a" ], + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": [ "b" ], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/unevaluatedProperties.json b/src/test/suite/tests/draft2020-12/unevaluatedProperties.json new file mode 100644 index 000000000..0da38f679 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/unevaluatedProperties.json @@ -0,0 +1,1591 @@ +[ + { + "description": "unevaluatedProperties true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": { + "type": "string", + "minLength": 3 + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with valid unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with invalid unevaluated properties", + "data": { + "foo": "fo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent bool additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": true, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with adjacent non-bool additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": { "type": "string" }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with only valid additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with invalid additional properties", + "data": { + "foo": "foo", + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "properties": { + "bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "patternProperties": { + "^bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "additionalProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": { + "type": "string", + "maxLength": 2 + } + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "anyOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + }, + { + "properties": { + "quux": { "const": "quux" } + }, + "required": ["quux"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when one matches and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when one matches and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "not-baz" + }, + "valid": false + }, + { + "description": "when two match and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": true + }, + { + "description": "when two match and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "quux": "not-quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "oneOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "quux": "quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "not": { + "not": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, then not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": false + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, else not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with dependentSchemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [true], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bar", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false, + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": false, + "properties": { + "foo": { "type": "string" } + }, + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/unevaluated-properties-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "properties": { + "bar": { "type": "string" } + } + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedProperties comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedProperties": false, + "properties": { + "foo": { "type": "string" } + }, + "$dynamicRef": "#addons", + + "$defs": { + "defaultAddons": { + "$comment": "Needed to satisfy the bookending requirement", + "$dynamicAnchor": "addons" + } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": true + } + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins (reverse order)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "unevaluatedProperties": false + }, + { + "properties": { + "foo": true + } + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, true with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, false with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "unevaluatedProperties": true + }, + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "property is evaluated in an uncle schema to unevaluatedProperties", + "comment": "see https://stackoverflow.com/questions/66936884/deeply-nested-unevaluatedproperties-and-their-expectations", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "properties": { + "bar": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "properties": { + "faz": { + "type": "string" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "no extra properties", + "data": { + "foo": { + "bar": "test" + } + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": { + "bar": "test", + "faz": "test" + } + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, allOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": true + }, + "unevaluatedProperties": false + } + ], + "anyOf": [ + { + "properties": { + "bar": true + } + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, anyOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": true + } + } + ], + "anyOf": [ + { + "properties": { + "bar": true + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties + single cyclic ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "x": { "$ref": "#" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is valid", + "data": {}, + "valid": true + }, + { + "description": "Single is valid", + "data": { "x": {} }, + "valid": true + }, + { + "description": "Unevaluated on 1st level is invalid", + "data": { "x": {}, "y": {} }, + "valid": false + }, + { + "description": "Nested is valid", + "data": { "x": { "x": {} } }, + "valid": true + }, + { + "description": "Unevaluated on 2nd level is invalid", + "data": { "x": { "x": {}, "y": {} } }, + "valid": false + }, + { + "description": "Deep nested is valid", + "data": { "x": { "x": { "x": {} } } }, + "valid": true + }, + { + "description": "Unevaluated on 3rd level is invalid", + "data": { "x": { "x": { "x": {}, "y": {} } } }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties + ref inside allOf / oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "one": { + "properties": { "a": true } + }, + "two": { + "required": ["x"], + "properties": { "x": true } + } + }, + "allOf": [ + { "$ref": "#/$defs/one" }, + { "properties": { "b": true } }, + { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { + "required": ["y"], + "properties": { "y": true } + } + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid (no x or y)", + "data": {}, + "valid": false + }, + { + "description": "a and b are invalid (no x or y)", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "x and y are invalid", + "data": { "x": 1, "y": 1 }, + "valid": false + }, + { + "description": "a and x are valid", + "data": { "a": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and y are valid", + "data": { "a": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x are valid", + "data": { "a": 1, "b": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and b and y are valid", + "data": { "a": 1, "b": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x and y are invalid", + "data": { "a": 1, "b": 1, "x": 1, "y": 1 }, + "valid": false + } + ] + }, + { + "description": "dynamic evalation inside nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "one": { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { "required": ["b"], "properties": { "b": true } }, + { "required": ["xx"], "patternProperties": { "x": true } }, + { "required": ["all"], "unevaluatedProperties": true } + ] + }, + "two": { + "oneOf": [ + { "required": ["c"], "properties": { "c": true } }, + { "required": ["d"], "properties": { "d": true } } + ] + } + }, + "oneOf": [ + { "$ref": "#/$defs/one" }, + { "required": ["a"], "properties": { "a": true } } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid", + "data": {}, + "valid": false + }, + { + "description": "a is valid", + "data": { "a": 1 }, + "valid": true + }, + { + "description": "b is valid", + "data": { "b": 1 }, + "valid": true + }, + { + "description": "c is valid", + "data": { "c": 1 }, + "valid": true + }, + { + "description": "d is valid", + "data": { "d": 1 }, + "valid": true + }, + { + "description": "a + b is invalid", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "a + c is invalid", + "data": { "a": 1, "c": 1 }, + "valid": false + }, + { + "description": "a + d is invalid", + "data": { "a": 1, "d": 1 }, + "valid": false + }, + { + "description": "b + c is invalid", + "data": { "b": 1, "c": 1 }, + "valid": false + }, + { + "description": "b + d is invalid", + "data": { "b": 1, "d": 1 }, + "valid": false + }, + { + "description": "c + d is invalid", + "data": { "c": 1, "d": 1 }, + "valid": false + }, + { + "description": "xx is valid", + "data": { "xx": 1 }, + "valid": true + }, + { + "description": "xx + foox is valid", + "data": { "xx": 1, "foox": 1 }, + "valid": true + }, + { + "description": "xx + foo is invalid", + "data": { "xx": 1, "foo": 1 }, + "valid": false + }, + { + "description": "xx + a is invalid", + "data": { "xx": 1, "a": 1 }, + "valid": false + }, + { + "description": "xx + b is invalid", + "data": { "xx": 1, "b": 1 }, + "valid": false + }, + { + "description": "xx + c is invalid", + "data": { "xx": 1, "c": 1 }, + "valid": false + }, + { + "description": "xx + d is invalid", + "data": { "xx": 1, "d": 1 }, + "valid": false + }, + { + "description": "all is valid", + "data": { "all": 1 }, + "valid": true + }, + { + "description": "all + foo is valid", + "data": { "all": 1, "foo": 1 }, + "valid": true + }, + { + "description": "all + a is invalid", + "data": { "all": 1, "a": 1 }, + "valid": false + } + ] + }, + { + "description": "non-object instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null valued properties", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties not affected by propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"maxLength": 1}, + "unevaluatedProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "allows only number properties", + "data": {"a": 1}, + "valid": true + }, + { + "description": "string property is invalid", + "data": {"a": "b"}, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "patternProperties": { + "foo": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": { + "foo": "a" + }, + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": { + "bar": "a" + }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar":{} + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluatedProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties doesn't see bar when foo2 is absent", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties sees bar when foo2 is present", + "data": { "foo2": "", "bar": ""}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/uniqueItems.json b/src/test/suite/tests/draft2020-12/uniqueItems.json new file mode 100644 index 000000000..4ea3bf985 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/uniqueItems.json @@ -0,0 +1,419 @@ +[ + { + "description": "uniqueItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": true + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": false + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft2020-12/vocabulary.json b/src/test/suite/tests/draft2020-12/vocabulary.json new file mode 100644 index 000000000..1acb96a93 --- /dev/null +++ b/src/test/suite/tests/draft2020-12/vocabulary.json @@ -0,0 +1,57 @@ +[ + { + "description": "schema that uses custom metaschema with with no validation vocabulary", + "schema": { + "$id": "https://schema/using/no/validation", + "$schema": "http://localhost:1234/draft2020-12/metaschema-no-validation.json", + "properties": { + "badProperty": false, + "numberProperty": { + "minimum": 10 + } + } + }, + "tests": [ + { + "description": "applicator vocabulary still works", + "data": { + "badProperty": "this property should not exist" + }, + "valid": false + }, + { + "description": "no validation: valid number", + "data": { + "numberProperty": 20 + }, + "valid": true + }, + { + "description": "no validation: invalid number, but it still validates", + "data": { + "numberProperty": 1 + }, + "valid": true + } + ] + }, + { + "description": "ignore unrecognized optional vocabulary", + "schema": { + "$schema": "http://localhost:1234/draft2020-12/metaschema-optional-vocabulary.json", + "type": "number" + }, + "tests": [ + { + "description": "string value", + "data": "foobar", + "valid": false + }, + { + "description": "number value", + "data": 20, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/additionalItems.json b/src/test/suite/tests/draft3/additionalItems.json new file mode 100644 index 000000000..ab44a2eb3 --- /dev/null +++ b/src/test/suite/tests/draft3/additionalItems.json @@ -0,0 +1,147 @@ +[ + { + "description": "additionalItems as schema", + "schema": { + "items": [], + "additionalItems": {"type": "integer"} + }, + "tests": [ + { + "description": "additional items match schema", + "data": [ 1, 2, 3, 4 ], + "valid": true + }, + { + "description": "additional items do not match schema", + "data": [ 1, 2, 3, "foo" ], + "valid": false + } + ] + }, + { + "description": "when items is schema, additionalItems does nothing", + "schema": { + "items": {}, + "additionalItems": false + }, + "tests": [ + { + "description": "all items match schema", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + } + ] + }, + { + "description": "array of items with no additionalItems permitted", + "schema": { + "items": [{}, {}, {}], + "additionalItems": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "additionalItems as false without items", + "schema": {"additionalItems": false}, + "tests": [ + { + "description": + "items defaults to empty schema so everything is valid", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "additionalItems are allowed by default", + "schema": {"items": [{"type": "integer"}]}, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators", + "schema": { + "extends": [ + { "items": [ { "type": "integer" } ] } + ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in extends are not examined", + "data": [ 1, null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with heterogeneous array", + "schema": { + "items": [{}], + "additionalItems": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with null instance elements", + "schema": { + "additionalItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/additionalProperties.json b/src/test/suite/tests/draft3/additionalProperties.json new file mode 100644 index 000000000..af7bfc6fc --- /dev/null +++ b/src/test/suite/tests/draft3/additionalProperties.json @@ -0,0 +1,147 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": {"properties": {"foo": {}, "bar": {}}}, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "extends": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in extends are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/default.json b/src/test/suite/tests/draft3/default.json similarity index 100% rename from src/test/resources/draft4/default.json rename to src/test/suite/tests/draft3/default.json diff --git a/src/test/suite/tests/draft3/dependencies.json b/src/test/suite/tests/draft3/dependencies.json new file mode 100644 index 000000000..0ffa6bf47 --- /dev/null +++ b/src/test/suite/tests/draft3/dependencies.json @@ -0,0 +1,123 @@ +[ + { + "description": "dependencies", + "schema": { + "dependencies": {"bar": "foo"} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple dependencies", + "schema": { + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "multiple dependencies subschema", + "schema": { + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/disallow.json b/src/test/suite/tests/draft3/disallow.json new file mode 100644 index 000000000..a5c9d90cc --- /dev/null +++ b/src/test/suite/tests/draft3/disallow.json @@ -0,0 +1,80 @@ +[ + { + "description": "disallow", + "schema": { + "disallow": "integer" + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "multiple disallow", + "schema": { + "disallow": ["integer", "boolean"] + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "multiple disallow subschema", + "schema": { + "disallow": + ["string", + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }] + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": "foo", + "valid": false + }, + { + "description": "other mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/divisibleBy.json b/src/test/suite/tests/draft3/divisibleBy.json new file mode 100644 index 000000000..ef7cc1489 --- /dev/null +++ b/src/test/suite/tests/draft3/divisibleBy.json @@ -0,0 +1,60 @@ +[ + { + "description": "by int", + "schema": {"divisibleBy": 2}, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": {"divisibleBy": 1.5}, + "tests": [ + { + "description": "zero is divisible by anything (except 0)", + "data": 0, + "valid": true + }, + { + "description": "4.5 is divisible by 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not divisible by 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": {"divisibleBy": 0.0001}, + "tests": [ + { + "description": "0.0075 is divisible by 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not divisible by 0.0001", + "data": 0.00751, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/enum.json b/src/test/suite/tests/draft3/enum.json new file mode 100644 index 000000000..5a1ab3b64 --- /dev/null +++ b/src/test/suite/tests/draft3/enum.json @@ -0,0 +1,118 @@ +[ + { + "description": "simple enum validation", + "schema": {"enum": [1, 2, 3]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { "enum": [6, null] }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"], "required":true} + } + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "enum": [ "hello\u0000there" ] }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/extends.json b/src/test/suite/tests/draft3/extends.json new file mode 100644 index 000000000..909bce575 --- /dev/null +++ b/src/test/suite/tests/draft3/extends.json @@ -0,0 +1,94 @@ +[ + { + "description": "extends", + "schema": { + "properties": {"bar": {"type": "integer", "required": true}}, + "extends": { + "properties": { + "foo": {"type": "string", "required": true} + } + } + }, + "tests": [ + { + "description": "extends", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch extends", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch extended", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "multiple extends", + "schema": { + "properties": {"bar": {"type": "integer", "required": true}}, + "extends" : [ + { + "properties": { + "foo": {"type": "string", "required": true} + } + }, + { + "properties": { + "baz": {"type": "null", "required": true} + } + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch first extends", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second extends", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "extends simple types", + "schema": { + "minimum": 20, + "extends": {"maximum": 30} + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch extends", + "data": 35, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/format.json b/src/test/suite/tests/draft3/format.json new file mode 100644 index 000000000..a5447c903 --- /dev/null +++ b/src/test/suite/tests/draft3/format.json @@ -0,0 +1,362 @@ +[ + { + "description": "email format", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ip-address format", + "schema": { "format": "ip-address" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "host-name format", + "schema": { "format": "host-name" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { "format": "regex" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { "format": "date" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { "format": "time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "color format", + "schema": { "format": "color" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/infinite-loop-detection.json b/src/test/suite/tests/draft3/infinite-loop-detection.json new file mode 100644 index 000000000..090f49a0b --- /dev/null +++ b/src/test/suite/tests/draft3/infinite-loop-detection.json @@ -0,0 +1,32 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "definitions": { + "int": { "type": "integer" } + }, + "properties": { + "foo": { + "$ref": "#/definitions/int" + } + }, + "extends": { + "additionalProperties": { + "$ref": "#/definitions/int" + } + } + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/items.json b/src/test/suite/tests/draft3/items.json new file mode 100644 index 000000000..e8bda2223 --- /dev/null +++ b/src/test/suite/tests/draft3/items.json @@ -0,0 +1,78 @@ +[ + { + "description": "a schema given for items", + "schema": { + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + } + ] + }, + { + "description": "items with null instance elements", + "schema": { + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "array-form items with null instance elements", + "schema": { + "items": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/maxItems.json b/src/test/suite/tests/draft3/maxItems.json similarity index 100% rename from src/test/resources/draft4/maxItems.json rename to src/test/suite/tests/draft3/maxItems.json diff --git a/src/test/suite/tests/draft3/maxLength.json b/src/test/suite/tests/draft3/maxLength.json new file mode 100644 index 000000000..b0a9ea5be --- /dev/null +++ b/src/test/suite/tests/draft3/maxLength.json @@ -0,0 +1,33 @@ +[ + { + "description": "maxLength validation", + "schema": {"maxLength": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 10, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/maximum.json b/src/test/suite/tests/draft3/maximum.json similarity index 100% rename from src/test/resources/draft4/maximum.json rename to src/test/suite/tests/draft3/maximum.json diff --git a/src/test/resources/draft4/minItems.json b/src/test/suite/tests/draft3/minItems.json similarity index 100% rename from src/test/resources/draft4/minItems.json rename to src/test/suite/tests/draft3/minItems.json diff --git a/src/test/suite/tests/draft3/minLength.json b/src/test/suite/tests/draft3/minLength.json new file mode 100644 index 000000000..6652c7509 --- /dev/null +++ b/src/test/suite/tests/draft3/minLength.json @@ -0,0 +1,33 @@ +[ + { + "description": "minLength validation", + "schema": {"minLength": 2}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/minimum.json b/src/test/suite/tests/draft3/minimum.json new file mode 100644 index 000000000..d579536e5 --- /dev/null +++ b/src/test/suite/tests/draft3/minimum.json @@ -0,0 +1,88 @@ +[ + { + "description": "minimum validation", + "schema": {"minimum": 1.1}, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "exclusiveMinimum validation", + "schema": { + "minimum": 1.1, + "exclusiveMinimum": true + }, + "tests": [ + { + "description": "above the minimum is still valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": {"minimum": -2}, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/bignum.json b/src/test/suite/tests/draft3/optional/bignum.json new file mode 100644 index 000000000..1bc8eb21d --- /dev/null +++ b/src/test/suite/tests/draft3/optional/bignum.json @@ -0,0 +1,95 @@ +[ + { + "description": "integer", + "schema": { "type": "integer" }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { "type": "number" }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { "maximum": 18446744073709551615 }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "maximum": 972783798187987123879878123.18878137, + "exclusiveMaximum": true + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { "minimum": -18446744073709551615 }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "minimum": -972783798187987123879878123.18878137, + "exclusiveMinimum": true + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/color.json b/src/test/suite/tests/draft3/optional/format/color.json new file mode 100644 index 000000000..0c0b53480 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/color.json @@ -0,0 +1,38 @@ +[ + { + "description": "validation of CSS colors", + "schema": { "format": "color" }, + "tests": [ + { + "description": "a valid CSS color name", + "data": "fuchsia", + "valid": true + }, + { + "description": "a valid six-digit CSS color code", + "data": "#CC8899", + "valid": true + }, + { + "description": "a valid three-digit CSS color code", + "data": "#C89", + "valid": true + }, + { + "description": "an invalid CSS color code", + "data": "#00332520", + "valid": false + }, + { + "description": "an invalid CSS color name", + "data": "puce", + "valid": false + }, + { + "description": "a CSS color name containing invalid characters", + "data": "light_grayish_red-violet", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/date-time.json b/src/test/suite/tests/draft3/optional/format/date-time.json new file mode 100644 index 000000000..1f1e6fb38 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/date-time.json @@ -0,0 +1,38 @@ +[ + { + "description": "validation of date-time strings", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/date.json b/src/test/suite/tests/draft3/optional/format/date.json new file mode 100644 index 000000000..796bc4634 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/date.json @@ -0,0 +1,168 @@ +[ + { + "description": "validation of date strings", + "schema": { "format": "date" }, + "tests": [ + { + "description": "a valid date string", + "data": "1963-06-19", + "valid": true + }, + { + "description": "a valid date string with 31 days in January", + "data": "2020-01-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in January", + "data": "2020-01-32", + "valid": false + }, + { + "description": "a valid date string with 28 days in February (normal)", + "data": "2021-02-28", + "valid": true + }, + { + "description": "a invalid date string with 29 days in February (normal)", + "data": "2021-02-29", + "valid": false + }, + { + "description": "a valid date string with 29 days in February (leap)", + "data": "2020-02-29", + "valid": true + }, + { + "description": "a invalid date string with 30 days in February (leap)", + "data": "2020-02-30", + "valid": false + }, + { + "description": "a valid date string with 31 days in March", + "data": "2020-03-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in March", + "data": "2020-03-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in April", + "data": "2020-04-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in April", + "data": "2020-04-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in May", + "data": "2020-05-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in May", + "data": "2020-05-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in June", + "data": "2020-06-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in June", + "data": "2020-06-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in July", + "data": "2020-07-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in July", + "data": "2020-07-32", + "valid": false + }, + { + "description": "a valid date string with 31 days in August", + "data": "2020-08-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in August", + "data": "2020-08-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in September", + "data": "2020-09-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in September", + "data": "2020-09-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in October", + "data": "2020-10-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in October", + "data": "2020-10-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in November", + "data": "2020-11-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in November", + "data": "2020-11-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in December", + "data": "2020-12-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in December", + "data": "2020-12-32", + "valid": false + }, + { + "description": "a invalid date string with invalid month", + "data": "2020-13-01", + "valid": false + }, + { + "description": "an invalid date string", + "data": "06/19/1963", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "invalidates non-padded month dates", + "data": "1998-1-20", + "valid": false + }, + { + "description": "invalidates non-padded day dates", + "data": "1998-01-1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/ecmascript-regex.json b/src/test/suite/tests/draft3/optional/format/ecmascript-regex.json new file mode 100644 index 000000000..03fe97724 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/ecmascript-regex.json @@ -0,0 +1,18 @@ +[ + { + "description": "ECMA 262 regex dialect recognition", + "schema": { "format": "regex" }, + "tests": [ + { + "description": "[^] is a valid regex", + "data": "[^]", + "valid": true + }, + { + "description": "ECMA 262 has no support for lookbehind", + "data": "(?<=foo)bar", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/email.json b/src/test/suite/tests/draft3/optional/format/email.json new file mode 100644 index 000000000..4749100a9 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/email.json @@ -0,0 +1,63 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { "format": "email" }, + "tests": [ + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/host-name.json b/src/test/suite/tests/draft3/optional/format/host-name.json new file mode 100644 index 000000000..9a75c3c20 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/host-name.json @@ -0,0 +1,68 @@ +[ + { + "description": "validation of host names", + "schema": { "format": "host-name" }, + "tests": [ + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "empty string", + "data": "", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/ip-address.json b/src/test/suite/tests/draft3/optional/format/ip-address.json new file mode 100644 index 000000000..91cac9fa3 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/ip-address.json @@ -0,0 +1,23 @@ +[ + { + "description": "validation of IP addresses", + "schema": { "format": "ip-address" }, + "tests": [ + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/ipv6.json b/src/test/suite/tests/draft3/optional/format/ipv6.json new file mode 100644 index 000000000..c3ef3790a --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/ipv6.json @@ -0,0 +1,68 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/regex.json b/src/test/suite/tests/draft3/optional/format/regex.json new file mode 100644 index 000000000..8a377638c --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/regex.json @@ -0,0 +1,18 @@ +[ + { + "description": "validation of regular expressions", + "schema": { "format": "regex" }, + "tests": [ + { + "description": "a valid regular expression", + "data": "([abc])+\\s+$", + "valid": true + }, + { + "description": "a regular expression with unclosed parens is invalid", + "data": "^(abc]", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/time.json b/src/test/suite/tests/draft3/optional/format/time.json new file mode 100644 index 000000000..36c823e6d --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/time.json @@ -0,0 +1,18 @@ +[ + { + "description": "validation of time strings", + "schema": { "format": "time" }, + "tests": [ + { + "description": "a valid time string", + "data": "08:30:06", + "valid": true + }, + { + "description": "an invalid time string", + "data": "8:30 AM", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/optional/format/uri.json b/src/test/suite/tests/draft3/optional/format/uri.json new file mode 100644 index 000000000..f024b6243 --- /dev/null +++ b/src/test/suite/tests/draft3/optional/format/uri.json @@ -0,0 +1,28 @@ +[ + { + "description": "validation of URIs", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft4/optional/non-bmp-regex.json b/src/test/suite/tests/draft3/optional/non-bmp-regex.json similarity index 100% rename from src/test/resources/draft4/optional/non-bmp-regex.json rename to src/test/suite/tests/draft3/optional/non-bmp-regex.json diff --git a/src/test/resources/draft4/optional/zeroTerminatedFloats.json b/src/test/suite/tests/draft3/optional/zeroTerminatedFloats.json similarity index 100% rename from src/test/resources/draft4/optional/zeroTerminatedFloats.json rename to src/test/suite/tests/draft3/optional/zeroTerminatedFloats.json diff --git a/src/test/resources/draft4/pattern.json b/src/test/suite/tests/draft3/pattern.json similarity index 100% rename from src/test/resources/draft4/pattern.json rename to src/test/suite/tests/draft3/pattern.json diff --git a/src/test/suite/tests/draft3/patternProperties.json b/src/test/suite/tests/draft3/patternProperties.json new file mode 100644 index 000000000..b0f2a8e45 --- /dev/null +++ b/src/test/suite/tests/draft3/patternProperties.json @@ -0,0 +1,130 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/properties.json b/src/test/suite/tests/draft3/properties.json new file mode 100644 index 000000000..cd2380115 --- /dev/null +++ b/src/test/suite/tests/draft3/properties.json @@ -0,0 +1,112 @@ +[ + { + "description": "object properties validation", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/ref.json b/src/test/suite/tests/draft3/ref.json new file mode 100644 index 000000000..609eaa465 --- /dev/null +++ b/src/test/suite/tests/draft3/ref.json @@ -0,0 +1,278 @@ +[ + { + "description": "root pointer ref", + "schema": { + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "items": [ + {"type": "integer"}, + {"$ref": "#/items/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "definitions": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/definitions/tilde~0field"}, + "slash": {"$ref": "#/definitions/slash~1field"}, + "percent": {"$ref": "#/definitions/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "definitions": { + "a": {"type": "integer"}, + "b": {"$ref": "#/definitions/a"}, + "c": {"$ref": "#/definitions/b"} + }, + "$ref": "#/definitions/c" + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref overrides any sibling keywords", + "schema": { + "definitions": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/definitions/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "remote ref valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "remote ref valid, maxItems ignored", + "data": { "foo": [ 1, 2, 3] }, + "valid": true + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "properties": { + "$ref": {"$ref": "#/definitions/is-string"} + }, + "definitions": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref prevents a sibling id from changing the base uri", + "schema": { + "id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "id": "foo.json", + "type": "number" + } + }, + "extends": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + }, + "tests": [ + { + "description": "$ref resolves to /definitions/base_foo, data does not validate", + "data": "a", + "valid": false + }, + { + "description": "$ref resolves to /definitions/base_foo, data validates", + "data": 1, + "valid": true + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": {"$ref": "http://json-schema.org/draft-03/schema#"}, + "tests": [ + { + "description": "remote ref valid", + "data": {"items": {"type": "integer"}}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"items": {"type": 1}}, + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "definitions": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/definitions/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/definitions/a_string" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/refRemote.json b/src/test/suite/tests/draft3/refRemote.json new file mode 100644 index 000000000..81a6c5116 --- /dev/null +++ b/src/test/suite/tests/draft3/refRemote.json @@ -0,0 +1,74 @@ +[ + { + "description": "remote ref", + "schema": {"$ref": "http://localhost:1234/integer.json"}, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": {"$ref": "http://localhost:1234/draft3/subSchemas.json#/definitions/integer"}, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$ref": "http://localhost:1234/draft3/subSchemas.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "change resolution scope", + "schema": { + "id": "http://localhost:1234/", + "items": { + "id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "changed scope ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "changed scope ref invalid", + "data": [["a"]], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft3/required.json b/src/test/suite/tests/draft3/required.json new file mode 100644 index 000000000..aaaf02427 --- /dev/null +++ b/src/test/suite/tests/draft3/required.json @@ -0,0 +1,53 @@ +[ + { + "description": "required validation", + "schema": { + "properties": { + "foo": {"required" : true}, + "bar": {} + } + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + } + ] + }, + { + "description": "required default validation", + "schema": { + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required explicitly false validation", + "schema": { + "properties": { + "foo": {"required": false} + } + }, + "tests": [ + { + "description": "not required if required is false", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/type.json b/src/test/suite/tests/draft3/type.json new file mode 100644 index 000000000..8447bc8e0 --- /dev/null +++ b/src/test/suite/tests/draft3/type.json @@ -0,0 +1,493 @@ +[ + { + "description": "integer type matches integers", + "schema": {"type": "integer"}, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": {"type": "number"}, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": {"type": "string"}, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": {"type": "object"}, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": {"type": "array"}, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": {"type": "boolean"}, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "a boolean is a boolean", + "data": true, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": {"type": "null"}, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "a boolean is not null", + "data": true, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "any type matches any type", + "schema": {"type": "any"}, + "tests": [ + { + "description": "any type includes integers", + "data": 1, + "valid": true + }, + { + "description": "any type includes float", + "data": 1.1, + "valid": true + }, + { + "description": "any type includes string", + "data": "foo", + "valid": true + }, + { + "description": "any type includes object", + "data": {}, + "valid": true + }, + { + "description": "any type includes array", + "data": [], + "valid": true + }, + { + "description": "any type includes boolean", + "data": true, + "valid": true + }, + { + "description": "any type includes null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": {"type": ["integer", "string"]}, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "types can include schemas", + "schema": { + "type": [ + "array", + {"type": "object"} + ] + }, + "tests": [ + { + "description": "an integer is invalid", + "data": 1, + "valid": false + }, + { + "description": "a string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is valid", + "data": {}, + "valid": true + }, + { + "description": "an array is valid", + "data": [], + "valid": true + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "applies a nested schema", + "schema": { + "type": [ + "integer", + { + "properties": { + "foo": {"type": "null"} + } + } + ] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "an object is valid only if it is fully valid", + "data": {"foo": null}, + "valid": true + }, + { + "description": "an object is invalid otherwise", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "types from separate schemas are merged", + "schema": { + "type": [ + {"type": ["string"]}, + {"type": ["array", "null"]} + ] + }, + "tests": [ + { + "description": "an integer is invalid", + "data": 1, + "valid": false + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an array is valid", + "data": [1, 2, 3], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft3/uniqueItems.json b/src/test/suite/tests/draft3/uniqueItems.json new file mode 100644 index 000000000..c48c6a064 --- /dev/null +++ b/src/test/suite/tests/draft3/uniqueItems.json @@ -0,0 +1,374 @@ +[ + { + "description": "uniqueItems validation", + "schema": {"uniqueItems": true}, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { "uniqueItems": false }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/additionalItems.json b/src/test/suite/tests/draft4/additionalItems.json new file mode 100644 index 000000000..c9e681549 --- /dev/null +++ b/src/test/suite/tests/draft4/additionalItems.json @@ -0,0 +1,183 @@ +[ + { + "description": "additionalItems as schema", + "schema": { + "items": [{}], + "additionalItems": {"type": "integer"} + }, + "tests": [ + { + "description": "additional items match schema", + "data": [ null, 2, 3, 4 ], + "valid": true + }, + { + "description": "additional items do not match schema", + "data": [ null, 2, 3, "foo" ], + "valid": false + } + ] + }, + { + "description": "when items is schema, additionalItems does nothing", + "schema": { + "items": {}, + "additionalItems": false + }, + "tests": [ + { + "description": "all items match schema", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + } + ] + }, + { + "description": "array of items with no additionalItems permitted", + "schema": { + "items": [{}, {}, {}], + "additionalItems": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "additionalItems as false without items", + "schema": {"additionalItems": false}, + "tests": [ + { + "description": + "items defaults to empty schema so everything is valid", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "additionalItems are allowed by default", + "schema": {"items": [{"type": "integer"}]}, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, valid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" } ] } + ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, null ], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, invalid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" }, { "type": "string" } ] } + ], + "items": [ {"type": "integer" } ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, "hello" ], + "valid": false + } + ] + }, + { + "description": "items validation adjusts the starting index for additionalItems", + "schema": { + "items": [ { "type": "string" } ], + "additionalItems": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "additionalItems with heterogeneous array", + "schema": { + "items": [{}], + "additionalItems": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with null instance elements", + "schema": { + "additionalItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/additionalProperties.json b/src/test/suite/tests/draft4/additionalProperties.json new file mode 100644 index 000000000..0f8e1627a --- /dev/null +++ b/src/test/suite/tests/draft4/additionalProperties.json @@ -0,0 +1,147 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": {"properties": {"foo": {}, "bar": {}}}, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/allOf.json b/src/test/suite/tests/draft4/allOf.json similarity index 100% rename from src/test/resources/draft4/allOf.json rename to src/test/suite/tests/draft4/allOf.json diff --git a/src/test/suite/tests/draft4/anyOf.json b/src/test/suite/tests/draft4/anyOf.json new file mode 100644 index 000000000..09cc3c2f6 --- /dev/null +++ b/src/test/suite/tests/draft4/anyOf.json @@ -0,0 +1,156 @@ +[ + { + "description": "anyOf", + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/default.json b/src/test/suite/tests/draft4/default.json new file mode 100644 index 000000000..289a9b66c --- /dev/null +++ b/src/test/suite/tests/draft4/default.json @@ -0,0 +1,79 @@ +[ + { + "description": "invalid type for default", + "schema": { + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/definitions.json b/src/test/suite/tests/draft4/definitions.json similarity index 100% rename from src/test/resources/draft4/definitions.json rename to src/test/suite/tests/draft4/definitions.json diff --git a/src/test/suite/tests/draft4/dependencies.json b/src/test/suite/tests/draft4/dependencies.json new file mode 100644 index 000000000..9045ddc25 --- /dev/null +++ b/src/test/suite/tests/draft4/dependencies.json @@ -0,0 +1,232 @@ +[ + { + "description": "dependencies", + "schema": { + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple dependencies", + "schema": { + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "multiple dependencies subschema", + "schema": { + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\tbar": { + "minProperties": 4 + }, + "foo'bar": {"required": ["foo\"bar"]}, + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "valid object 1", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "valid object 2", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "valid object 3", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "invalid object 1", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "invalid object 2", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "invalid object 3", + "data": { + "foo'bar": 1 + }, + "valid": false + }, + { + "description": "invalid object 4", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "properties": { + "foo": {} + }, + "dependencies": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/enum.json b/src/test/suite/tests/draft4/enum.json new file mode 100644 index 000000000..ce43acc02 --- /dev/null +++ b/src/test/suite/tests/draft4/enum.json @@ -0,0 +1,320 @@ +[ + { + "description": "simple enum validation", + "schema": {"enum": [1, 2, 3]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { "enum": [6, null] }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": {"enum": [false]}, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": {"enum": [[false]]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": {"enum": [true]}, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": {"enum": [[true]]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": {"enum": [0]}, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": {"enum": [[0]]}, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": {"enum": [1]}, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": {"enum": [[1]]}, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "enum": [ "hello\u0000there" ] }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft4/format.json b/src/test/suite/tests/draft4/format.json similarity index 100% rename from src/test/resources/draft4/format.json rename to src/test/suite/tests/draft4/format.json diff --git a/src/test/resources/draft4/infinite-loop-detection.json b/src/test/suite/tests/draft4/infinite-loop-detection.json similarity index 100% rename from src/test/resources/draft4/infinite-loop-detection.json rename to src/test/suite/tests/draft4/infinite-loop-detection.json diff --git a/src/test/suite/tests/draft4/items.json b/src/test/suite/tests/draft4/items.json new file mode 100644 index 000000000..16ea0704d --- /dev/null +++ b/src/test/suite/tests/draft4/items.json @@ -0,0 +1,227 @@ +[ + { + "description": "a schema given for items", + "schema": { + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "definitions": { + "item": { + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/sub-item" }, + { "$ref": "#/definitions/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "items with null instance elements", + "schema": { + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "array-form items with null instance elements", + "schema": { + "items": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/maxItems.json b/src/test/suite/tests/draft4/maxItems.json new file mode 100644 index 000000000..3b53a6b37 --- /dev/null +++ b/src/test/suite/tests/draft4/maxItems.json @@ -0,0 +1,28 @@ +[ + { + "description": "maxItems validation", + "schema": {"maxItems": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/maxLength.json b/src/test/suite/tests/draft4/maxLength.json new file mode 100644 index 000000000..338795943 --- /dev/null +++ b/src/test/suite/tests/draft4/maxLength.json @@ -0,0 +1,33 @@ +[ + { + "description": "maxLength validation", + "schema": {"maxLength": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/maxProperties.json b/src/test/suite/tests/draft4/maxProperties.json similarity index 100% rename from src/test/resources/draft4/maxProperties.json rename to src/test/suite/tests/draft4/maxProperties.json diff --git a/src/test/suite/tests/draft4/maximum.json b/src/test/suite/tests/draft4/maximum.json new file mode 100644 index 000000000..ccb79c6c4 --- /dev/null +++ b/src/test/suite/tests/draft4/maximum.json @@ -0,0 +1,99 @@ +[ + { + "description": "maximum validation", + "schema": {"maximum": 3.0}, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": {"maximum": 300}, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + }, + { + "description": "maximum validation (explicit false exclusivity)", + "schema": {"maximum": 3.0, "exclusiveMaximum": false}, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "exclusiveMaximum validation", + "schema": { + "maximum": 3.0, + "exclusiveMaximum": true + }, + "tests": [ + { + "description": "below the maximum is still valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/minItems.json b/src/test/suite/tests/draft4/minItems.json new file mode 100644 index 000000000..ed5118815 --- /dev/null +++ b/src/test/suite/tests/draft4/minItems.json @@ -0,0 +1,28 @@ +[ + { + "description": "minItems validation", + "schema": {"minItems": 1}, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/minLength.json b/src/test/suite/tests/draft4/minLength.json new file mode 100644 index 000000000..6652c7509 --- /dev/null +++ b/src/test/suite/tests/draft4/minLength.json @@ -0,0 +1,33 @@ +[ + { + "description": "minLength validation", + "schema": {"minLength": 2}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft4/minProperties.json b/src/test/suite/tests/draft4/minProperties.json similarity index 100% rename from src/test/resources/draft4/minProperties.json rename to src/test/suite/tests/draft4/minProperties.json diff --git a/src/test/resources/draft4/minimum.json b/src/test/suite/tests/draft4/minimum.json similarity index 100% rename from src/test/resources/draft4/minimum.json rename to src/test/suite/tests/draft4/minimum.json diff --git a/src/test/suite/tests/draft4/multipleOf.json b/src/test/suite/tests/draft4/multipleOf.json new file mode 100644 index 000000000..ed2df4a7f --- /dev/null +++ b/src/test/suite/tests/draft4/multipleOf.json @@ -0,0 +1,82 @@ +[ + { + "description": "by int", + "schema": {"multipleOf": 2}, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": {"multipleOf": 1.5}, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": {"multipleOf": 0.0001}, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": {"type": "integer", "multipleOf": 0.123456789}, + "tests": [ + { + "description": "invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": {"type": "integer", "multipleOf": 1e-8}, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/not.json b/src/test/suite/tests/draft4/not.json new file mode 100644 index 000000000..525219cf2 --- /dev/null +++ b/src/test/suite/tests/draft4/not.json @@ -0,0 +1,157 @@ +[ + { + "description": "not", + "schema": { + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { "not": {} }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "double negation", + "schema": { "not": { "not": {} } }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/oneOf.json b/src/test/suite/tests/draft4/oneOf.json new file mode 100644 index 000000000..2487f0e38 --- /dev/null +++ b/src/test/suite/tests/draft4/oneOf.json @@ -0,0 +1,230 @@ +[ + { + "description": "oneOf", + "schema": { + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "oneOf": [ + { + "properties": { + "bar": {}, + "baz": {} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/bignum.json b/src/test/suite/tests/draft4/optional/bignum.json new file mode 100644 index 000000000..1bc8eb21d --- /dev/null +++ b/src/test/suite/tests/draft4/optional/bignum.json @@ -0,0 +1,95 @@ +[ + { + "description": "integer", + "schema": { "type": "integer" }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { "type": "number" }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { "maximum": 18446744073709551615 }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "maximum": 972783798187987123879878123.18878137, + "exclusiveMaximum": true + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { "minimum": -18446744073709551615 }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "minimum": -972783798187987123879878123.18878137, + "exclusiveMinimum": true + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/ecmascript-regex.json b/src/test/suite/tests/draft4/optional/ecmascript-regex.json new file mode 100644 index 000000000..c431baca8 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/ecmascript-regex.json @@ -0,0 +1,552 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { "pattern": "\\p{Letter}cole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { "pattern": "\\wcole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { "pattern": "[a-z]cole" }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { "pattern": "^\\d+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { "pattern": "^\\p{digit}+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "type": "object", + "patternProperties": { + "\\p{Letter}cole": {} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "type": "object", + "patternProperties": { + "\\wcole": {} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "type": "object", + "patternProperties": { + "[a-z]cole": {} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\d+$": {} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\p{digit}+$": {} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/optional/float-overflow.json b/src/test/suite/tests/draft4/optional/float-overflow.json similarity index 100% rename from src/test/resources/draft4/optional/float-overflow.json rename to src/test/suite/tests/draft4/optional/float-overflow.json diff --git a/src/test/suite/tests/draft4/optional/format/date-time.json b/src/test/suite/tests/draft4/optional/format/date-time.json new file mode 100644 index 000000000..091127375 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/date-time.json @@ -0,0 +1,133 @@ +[ + { + "description": "validation of date-time strings", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/format/email.json b/src/test/suite/tests/draft4/optional/format/email.json new file mode 100644 index 000000000..84113f8a7 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/email.json @@ -0,0 +1,93 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/format/hostname.json b/src/test/suite/tests/draft4/optional/format/hostname.json new file mode 100644 index 000000000..866a61788 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/hostname.json @@ -0,0 +1,128 @@ +[ + { + "description": "validation of host names", + "schema": { "format": "hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/format/ipv4.json b/src/test/suite/tests/draft4/optional/format/ipv4.json new file mode 100644 index 000000000..9680fe620 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/ipv4.json @@ -0,0 +1,89 @@ +[ + { + "description": "validation of IP addresses", + "schema": { "format": "ipv4" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/format/ipv6.json b/src/test/suite/tests/draft4/optional/format/ipv6.json new file mode 100644 index 000000000..94368f2a0 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/ipv6.json @@ -0,0 +1,208 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft4/optional/format/unknown.json b/src/test/suite/tests/draft4/optional/format/unknown.json similarity index 100% rename from src/test/resources/draft4/optional/format/unknown.json rename to src/test/suite/tests/draft4/optional/format/unknown.json diff --git a/src/test/suite/tests/draft4/optional/format/uri.json b/src/test/suite/tests/draft4/optional/format/uri.json new file mode 100644 index 000000000..4b48d4060 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/format/uri.json @@ -0,0 +1,138 @@ +[ + { + "description": "validation of URIs", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/resources/draft4/id.json b/src/test/suite/tests/draft4/optional/id.json similarity index 100% rename from src/test/resources/draft4/id.json rename to src/test/suite/tests/draft4/optional/id.json diff --git a/src/test/suite/tests/draft4/optional/non-bmp-regex.json b/src/test/suite/tests/draft4/optional/non-bmp-regex.json new file mode 100644 index 000000000..dd67af2b2 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/non-bmp-regex.json @@ -0,0 +1,82 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { "pattern": "^🐲*$" }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/optional/zeroTerminatedFloats.json b/src/test/suite/tests/draft4/optional/zeroTerminatedFloats.json new file mode 100644 index 000000000..9b50ea277 --- /dev/null +++ b/src/test/suite/tests/draft4/optional/zeroTerminatedFloats.json @@ -0,0 +1,15 @@ +[ + { + "description": "some languages do not distinguish between different types of numeric value", + "schema": { + "type": "integer" + }, + "tests": [ + { + "description": "a float is not an integer even without fractional part", + "data": 1.0, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/pattern.json b/src/test/suite/tests/draft4/pattern.json new file mode 100644 index 000000000..92db0f971 --- /dev/null +++ b/src/test/suite/tests/draft4/pattern.json @@ -0,0 +1,59 @@ +[ + { + "description": "pattern validation", + "schema": {"pattern": "^a*$"}, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": {"pattern": "a+"}, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/patternProperties.json b/src/test/suite/tests/draft4/patternProperties.json new file mode 100644 index 000000000..51c8af3d6 --- /dev/null +++ b/src/test/suite/tests/draft4/patternProperties.json @@ -0,0 +1,135 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/properties.json b/src/test/suite/tests/draft4/properties.json new file mode 100644 index 000000000..195159e6b --- /dev/null +++ b/src/test/suite/tests/draft4/properties.json @@ -0,0 +1,205 @@ +[ + { + "description": "object properties validation", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft4/ref.json b/src/test/suite/tests/draft4/ref.json new file mode 100644 index 000000000..b53bd2abe --- /dev/null +++ b/src/test/suite/tests/draft4/ref.json @@ -0,0 +1,592 @@ +[ + { + "description": "root pointer ref", + "schema": { + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "items": [ + {"type": "integer"}, + {"$ref": "#/items/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "definitions": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/definitions/tilde~0field"}, + "slash": {"$ref": "#/definitions/slash~1field"}, + "percent": {"$ref": "#/definitions/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "definitions": { + "a": {"type": "integer"}, + "b": {"$ref": "#/definitions/a"}, + "c": {"$ref": "#/definitions/b"} + }, + "allOf": [{ "$ref": "#/definitions/c" }] + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref overrides any sibling keywords", + "schema": { + "definitions": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/definitions/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems ignored", + "data": { "foo": [ 1, 2, 3] }, + "valid": true + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "$ref prevents a sibling id from changing the base uri", + "schema": { + "id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "id": "foo.json", + "type": "number" + } + }, + "allOf": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + }, + "tests": [ + { + "description": "$ref resolves to /definitions/base_foo, data does not validate", + "data": "a", + "valid": false + }, + { + "description": "$ref resolves to /definitions/base_foo, data validates", + "data": 1, + "valid": true + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "properties": { + "$ref": {"$ref": "#/definitions/is-string"} + }, + "definitions": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "id": "http://localhost:1234/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "definitions": { + "node": { + "id": "http://localhost:1234/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "properties": { + "foo\"bar": {"$ref": "#/definitions/foo%22bar"} + }, + "definitions": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "Location-independent identifier", + "schema": { + "allOf": [{ + "$ref": "#foo" + }], + "definitions": { + "A": { + "id": "#foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "id": "http://localhost:1234/root", + "allOf": [{ + "$ref": "http://localhost:1234/nested.json#foo" + }], + "definitions": { + "A": { + "id": "nested.json", + "definitions": { + "B": { + "id": "#foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "definitions": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/definitions/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/definitions/a_string" }, + "valid": true + } + ] + }, + { + "description": "id must be resolved against nearest parent, not just immediate parent", + "schema": { + "id": "http://example.com/a.json", + "definitions": { + "x": { + "id": "http://example.com/b/c.json", + "not": { + "definitions": { + "y": { + "id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "id with file URI still resolves pointers - *nix", + "schema": { + "id": "file:///folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "id with file URI still resolves pointers - windows", + "schema": { + "id": "file:///c:/folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "definitions": { + "": { + "definitions": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions//definitions/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/refRemote.json b/src/test/suite/tests/draft4/refRemote.json new file mode 100644 index 000000000..65e45190c --- /dev/null +++ b/src/test/suite/tests/draft4/refRemote.json @@ -0,0 +1,189 @@ +[ + { + "description": "remote ref", + "schema": {"$ref": "http://localhost:1234/integer.json"}, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": {"$ref": "http://localhost:1234/draft4/subSchemas.json#/definitions/integer"}, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$ref": "http://localhost:1234/draft4/subSchemas.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "id": "http://localhost:1234/", + "items": { + "id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "id": "http://localhost:1234/scope_change_defs1.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz"} + }, + "definitions": { + "baz": { + "id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "id": "http://localhost:1234/scope_change_defs2.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz/definitions/bar"} + }, + "definitions": { + "baz": { + "id": "baseUriChangeFolderInSubschema/", + "definitions": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "id": "http://localhost:1234/object", + "type": "object", + "properties": { + "name": {"$ref": "draft4/name.json#/definitions/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$ref": "http://localhost:1234/draft4/locationIndependentIdentifier.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft4/required.json b/src/test/suite/tests/draft4/required.json new file mode 100644 index 000000000..6ccfdc2d4 --- /dev/null +++ b/src/test/suite/tests/draft4/required.json @@ -0,0 +1,135 @@ +[ + { + "description": "required validation", + "schema": { + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { "required": ["__proto__", "toString", "constructor"] }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/resources/draft4/type.json b/src/test/suite/tests/draft4/type.json similarity index 100% rename from src/test/resources/draft4/type.json rename to src/test/suite/tests/draft4/type.json diff --git a/src/test/suite/tests/draft4/uniqueItems.json b/src/test/suite/tests/draft4/uniqueItems.json new file mode 100644 index 000000000..d2730c60c --- /dev/null +++ b/src/test/suite/tests/draft4/uniqueItems.json @@ -0,0 +1,409 @@ +[ + { + "description": "uniqueItems validation", + "schema": {"uniqueItems": true}, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { "uniqueItems": false }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/additionalItems.json b/src/test/suite/tests/draft6/additionalItems.json new file mode 100644 index 000000000..2c7d15582 --- /dev/null +++ b/src/test/suite/tests/draft6/additionalItems.json @@ -0,0 +1,206 @@ +[ + { + "description": "additionalItems as schema", + "schema": { + "items": [{}], + "additionalItems": {"type": "integer"} + }, + "tests": [ + { + "description": "additional items match schema", + "data": [ null, 2, 3, 4 ], + "valid": true + }, + { + "description": "additional items do not match schema", + "data": [ null, 2, 3, "foo" ], + "valid": false + } + ] + }, + { + "description": "when items is schema, additionalItems does nothing", + "schema": { + "items": { + "type": "integer" + }, + "additionalItems": { + "type": "string" + } + }, + "tests": [ + { + "description": "valid with a array of type integers", + "data": [1,2,3], + "valid": true + }, + { + "description": "invalid with a array of mixed types", + "data": [1,"2","3"], + "valid": false + } + ] + }, + { + "description": "when items is schema, boolean additionalItems does nothing", + "schema": { + "items": {}, + "additionalItems": false + }, + "tests": [ + { + "description": "all items match schema", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + } + ] + }, + { + "description": "array of items with no additionalItems permitted", + "schema": { + "items": [{}, {}, {}], + "additionalItems": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "additionalItems as false without items", + "schema": {"additionalItems": false}, + "tests": [ + { + "description": + "items defaults to empty schema so everything is valid", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "additionalItems are allowed by default", + "schema": {"items": [{"type": "integer"}]}, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, valid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" } ] } + ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, null ], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, invalid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" }, { "type": "string" } ] } + ], + "items": [ {"type": "integer" } ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, "hello" ], + "valid": false + } + ] + }, + { + "description": "items validation adjusts the starting index for additionalItems", + "schema": { + "items": [ { "type": "string" } ], + "additionalItems": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "additionalItems with heterogeneous array", + "schema": { + "items": [{}], + "additionalItems": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with null instance elements", + "schema": { + "additionalItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/additionalProperties.json b/src/test/suite/tests/draft6/additionalProperties.json new file mode 100644 index 000000000..0f8e1627a --- /dev/null +++ b/src/test/suite/tests/draft6/additionalProperties.json @@ -0,0 +1,147 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": {"properties": {"foo": {}, "bar": {}}}, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/allOf.json b/src/test/suite/tests/draft6/allOf.json new file mode 100644 index 000000000..ec9319e14 --- /dev/null +++ b/src/test/suite/tests/draft6/allOf.json @@ -0,0 +1,294 @@ +[ + { + "description": "allOf", + "schema": { + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": {"allOf": [true, true]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": {"allOf": [true, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": {"allOf": [false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/anyOf.json b/src/test/suite/tests/draft6/anyOf.json new file mode 100644 index 000000000..ab5eb386b --- /dev/null +++ b/src/test/suite/tests/draft6/anyOf.json @@ -0,0 +1,189 @@ +[ + { + "description": "anyOf", + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": {"anyOf": [true, true]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": {"anyOf": [true, false]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": {"anyOf": [false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/boolean_schema.json b/src/test/suite/tests/draft6/boolean_schema.json new file mode 100644 index 000000000..6d40f23f2 --- /dev/null +++ b/src/test/suite/tests/draft6/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/const.json b/src/test/suite/tests/draft6/const.json new file mode 100644 index 000000000..1c2cafcc1 --- /dev/null +++ b/src/test/suite/tests/draft6/const.json @@ -0,0 +1,342 @@ +[ + { + "description": "const validation", + "schema": {"const": 2}, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": {"const": {"foo": "bar", "baz": "bax"}}, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": {"const": [{ "foo": "bar" }]}, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": {"const": null}, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": {"const": false}, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": {"const": true}, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": {"const": [false]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": {"const": [true]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": {"const": {"a": false}}, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": {"const": {"a": true}}, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": {"const": 0}, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": {"const": 1}, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": {"const": -2.0}, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": {"const": 9007199254740992}, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "const": "hello\u0000there" }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/contains.json b/src/test/suite/tests/draft6/contains.json new file mode 100644 index 000000000..bd93654f3 --- /dev/null +++ b/src/test/suite/tests/draft6/contains.json @@ -0,0 +1,144 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "contains": {"minimum": 5} + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": {"contains": true}, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": {"contains": false}, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid", + "data": "contains does not apply to strings", + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [ 2, 4, 8 ], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [ 3, 6, 9 ], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [ 6, 12 ], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [ 1, 5 ], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/default.json b/src/test/suite/tests/draft6/default.json new file mode 100644 index 000000000..289a9b66c --- /dev/null +++ b/src/test/suite/tests/draft6/default.json @@ -0,0 +1,79 @@ +[ + { + "description": "invalid type for default", + "schema": { + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/definitions.json b/src/test/suite/tests/draft6/definitions.json new file mode 100644 index 000000000..d772fde3e --- /dev/null +++ b/src/test/suite/tests/draft6/definitions.json @@ -0,0 +1,26 @@ +[ + { + "description": "validate definition against metaschema", + "schema": {"$ref": "http://json-schema.org/draft-06/schema#"}, + "tests": [ + { + "description": "valid definition schema", + "data": { + "definitions": { + "foo": {"type": "integer"} + } + }, + "valid": true + }, + { + "description": "invalid definition schema", + "data": { + "definitions": { + "foo": {"type": 1} + } + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/dependencies.json b/src/test/suite/tests/draft6/dependencies.json new file mode 100644 index 000000000..c0bd809f6 --- /dev/null +++ b/src/test/suite/tests/draft6/dependencies.json @@ -0,0 +1,286 @@ +[ + { + "description": "dependencies", + "schema": { + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "dependencies with empty array", + "schema": { + "dependencies": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependencies", + "schema": { + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "multiple dependencies subschema", + "schema": { + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "dependencies with boolean subschemas", + "schema": { + "dependencies": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\tbar": { + "minProperties": 4 + }, + "foo'bar": {"required": ["foo\"bar"]}, + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "valid object 1", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "valid object 2", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "valid object 3", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "invalid object 1", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "invalid object 2", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "invalid object 3", + "data": { + "foo'bar": 1 + }, + "valid": false + }, + { + "description": "invalid object 4", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "properties": { + "foo": {} + }, + "dependencies": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/enum.json b/src/test/suite/tests/draft6/enum.json new file mode 100644 index 000000000..ce43acc02 --- /dev/null +++ b/src/test/suite/tests/draft6/enum.json @@ -0,0 +1,320 @@ +[ + { + "description": "simple enum validation", + "schema": {"enum": [1, 2, 3]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { "enum": [6, null] }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": {"enum": [false]}, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": {"enum": [[false]]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": {"enum": [true]}, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": {"enum": [[true]]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": {"enum": [0]}, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": {"enum": [[0]]}, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": {"enum": [1]}, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": {"enum": [[1]]}, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "enum": [ "hello\u0000there" ] }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/exclusiveMaximum.json b/src/test/suite/tests/draft6/exclusiveMaximum.json new file mode 100644 index 000000000..dc3cd709d --- /dev/null +++ b/src/test/suite/tests/draft6/exclusiveMaximum.json @@ -0,0 +1,30 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/exclusiveMinimum.json b/src/test/suite/tests/draft6/exclusiveMinimum.json new file mode 100644 index 000000000..b38d7ecec --- /dev/null +++ b/src/test/suite/tests/draft6/exclusiveMinimum.json @@ -0,0 +1,30 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/format.json b/src/test/suite/tests/draft6/format.json new file mode 100644 index 000000000..2df2a9f0a --- /dev/null +++ b/src/test/suite/tests/draft6/format.json @@ -0,0 +1,326 @@ +[ + { + "description": "email format", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { "format": "ipv4" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { "format": "hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { "format": "json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { "format": "uri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { "format": "uri-template" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/infinite-loop-detection.json b/src/test/suite/tests/draft6/infinite-loop-detection.json new file mode 100644 index 000000000..f98c74fc6 --- /dev/null +++ b/src/test/suite/tests/draft6/infinite-loop-detection.json @@ -0,0 +1,36 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "definitions": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/definitions/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/definitions/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/items.json b/src/test/suite/tests/draft6/items.json new file mode 100644 index 000000000..7ed6781b6 --- /dev/null +++ b/src/test/suite/tests/draft6/items.json @@ -0,0 +1,282 @@ +[ + { + "description": "a schema given for items", + "schema": { + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": {"items": true}, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": {"items": false}, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schemas", + "schema": { + "items": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "definitions": { + "item": { + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/sub-item" }, + { "$ref": "#/definitions/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "single-form items with null instance elements", + "schema": { + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "array-form items with null instance elements", + "schema": { + "items": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/maxItems.json b/src/test/suite/tests/draft6/maxItems.json new file mode 100644 index 000000000..f0c36ab28 --- /dev/null +++ b/src/test/suite/tests/draft6/maxItems.json @@ -0,0 +1,44 @@ +[ + { + "description": "maxItems validation", + "schema": {"maxItems": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": {"maxItems": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/maxLength.json b/src/test/suite/tests/draft6/maxLength.json new file mode 100644 index 000000000..be60c5407 --- /dev/null +++ b/src/test/suite/tests/draft6/maxLength.json @@ -0,0 +1,49 @@ +[ + { + "description": "maxLength validation", + "schema": {"maxLength": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": {"maxLength": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/maxProperties.json b/src/test/suite/tests/draft6/maxProperties.json new file mode 100644 index 000000000..acec14209 --- /dev/null +++ b/src/test/suite/tests/draft6/maxProperties.json @@ -0,0 +1,70 @@ +[ + { + "description": "maxProperties validation", + "schema": {"maxProperties": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": {"maxProperties": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { "maxProperties": 0 }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/maximum.json b/src/test/suite/tests/draft6/maximum.json new file mode 100644 index 000000000..6844a39ee --- /dev/null +++ b/src/test/suite/tests/draft6/maximum.json @@ -0,0 +1,54 @@ +[ + { + "description": "maximum validation", + "schema": {"maximum": 3.0}, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": {"maximum": 300}, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/minItems.json b/src/test/suite/tests/draft6/minItems.json new file mode 100644 index 000000000..d3b18720d --- /dev/null +++ b/src/test/suite/tests/draft6/minItems.json @@ -0,0 +1,44 @@ +[ + { + "description": "minItems validation", + "schema": {"minItems": 1}, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": {"minItems": 1.0}, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/minLength.json b/src/test/suite/tests/draft6/minLength.json new file mode 100644 index 000000000..23c68fe3f --- /dev/null +++ b/src/test/suite/tests/draft6/minLength.json @@ -0,0 +1,49 @@ +[ + { + "description": "minLength validation", + "schema": {"minLength": 2}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": {"minLength": 2.0}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/minProperties.json b/src/test/suite/tests/draft6/minProperties.json new file mode 100644 index 000000000..9f74f7891 --- /dev/null +++ b/src/test/suite/tests/draft6/minProperties.json @@ -0,0 +1,54 @@ +[ + { + "description": "minProperties validation", + "schema": {"minProperties": 1}, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": {"minProperties": 1.0}, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/minimum.json b/src/test/suite/tests/draft6/minimum.json new file mode 100644 index 000000000..21ae50e0e --- /dev/null +++ b/src/test/suite/tests/draft6/minimum.json @@ -0,0 +1,69 @@ +[ + { + "description": "minimum validation", + "schema": {"minimum": 1.1}, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": {"minimum": -2}, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/multipleOf.json b/src/test/suite/tests/draft6/multipleOf.json new file mode 100644 index 000000000..e606979b7 --- /dev/null +++ b/src/test/suite/tests/draft6/multipleOf.json @@ -0,0 +1,82 @@ +[ + { + "description": "by int", + "schema": {"multipleOf": 2}, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": {"multipleOf": 1.5}, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": {"multipleOf": 0.0001}, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": {"type": "integer", "multipleOf": 0.123456789}, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": {"type": "integer", "multipleOf": 1e-8}, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/not.json b/src/test/suite/tests/draft6/not.json new file mode 100644 index 000000000..b46c4ed05 --- /dev/null +++ b/src/test/suite/tests/draft6/not.json @@ -0,0 +1,259 @@ +[ + { + "description": "not", + "schema": { + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { "not": {} }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "forbid everything with boolean schema true", + "schema": { "not": true }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "allow everything with boolean schema false", + "schema": { "not": false }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "double negation", + "schema": { "not": { "not": {} } }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/oneOf.json b/src/test/suite/tests/draft6/oneOf.json new file mode 100644 index 000000000..c30a65c0d --- /dev/null +++ b/src/test/suite/tests/draft6/oneOf.json @@ -0,0 +1,274 @@ +[ + { + "description": "oneOf", + "schema": { + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": {"oneOf": [true, true, true]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": {"oneOf": [true, false, false]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": {"oneOf": [true, true, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": {"oneOf": [false, false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/bignum.json b/src/test/suite/tests/draft6/optional/bignum.json new file mode 100644 index 000000000..94b4a4e62 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/bignum.json @@ -0,0 +1,93 @@ +[ + { + "description": "integer", + "schema": { "type": "integer" }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { "type": "number" }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { "maximum": 18446744073709551615 }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "exclusiveMaximum": 972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { "minimum": -18446744073709551615 }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "exclusiveMinimum": -972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/ecmascript-regex.json b/src/test/suite/tests/draft6/optional/ecmascript-regex.json new file mode 100644 index 000000000..c4886aaa8 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/ecmascript-regex.json @@ -0,0 +1,552 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { "pattern": "\\p{Letter}cole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { "pattern": "\\wcole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { "pattern": "[a-z]cole" }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { "pattern": "^\\d+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { "pattern": "^\\p{digit}+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "type": "object", + "patternProperties": { + "\\p{Letter}cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "type": "object", + "patternProperties": { + "\\wcole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "type": "object", + "patternProperties": { + "[a-z]cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\d+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\p{digit}+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/float-overflow.json b/src/test/suite/tests/draft6/optional/float-overflow.json new file mode 100644 index 000000000..52ff9827c --- /dev/null +++ b/src/test/suite/tests/draft6/optional/float-overflow.json @@ -0,0 +1,13 @@ +[ + { + "description": "all integers are multiples of 0.5, if overflow is handled", + "schema": {"type": "integer", "multipleOf": 0.5}, + "tests": [ + { + "description": "valid if optional overflow handling is implemented", + "data": 1e308, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/date-time.json b/src/test/suite/tests/draft6/optional/format/date-time.json new file mode 100644 index 000000000..091127375 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/date-time.json @@ -0,0 +1,133 @@ +[ + { + "description": "validation of date-time strings", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/email.json b/src/test/suite/tests/draft6/optional/format/email.json new file mode 100644 index 000000000..84113f8a7 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/email.json @@ -0,0 +1,93 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/hostname.json b/src/test/suite/tests/draft6/optional/format/hostname.json new file mode 100644 index 000000000..866a61788 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/hostname.json @@ -0,0 +1,128 @@ +[ + { + "description": "validation of host names", + "schema": { "format": "hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/ipv4.json b/src/test/suite/tests/draft6/optional/format/ipv4.json new file mode 100644 index 000000000..9680fe620 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/ipv4.json @@ -0,0 +1,89 @@ +[ + { + "description": "validation of IP addresses", + "schema": { "format": "ipv4" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/ipv6.json b/src/test/suite/tests/draft6/optional/format/ipv6.json new file mode 100644 index 000000000..94368f2a0 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/ipv6.json @@ -0,0 +1,208 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/json-pointer.json b/src/test/suite/tests/draft6/optional/format/json-pointer.json new file mode 100644 index 000000000..a0346b575 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/json-pointer.json @@ -0,0 +1,198 @@ +[ + { + "description": "validation of JSON-pointers (JSON String Representation)", + "schema": { "format": "json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid JSON-pointer", + "data": "/foo/bar~0/baz~1/%a", + "valid": true + }, + { + "description": "not a valid JSON-pointer (~ not escaped)", + "data": "/foo/bar~", + "valid": false + }, + { + "description": "valid JSON-pointer with empty segment", + "data": "/foo//bar", + "valid": true + }, + { + "description": "valid JSON-pointer with the last empty segment", + "data": "/foo/bar/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #1", + "data": "", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #2", + "data": "/foo", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #3", + "data": "/foo/0", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #4", + "data": "/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #5", + "data": "/a~1b", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #6", + "data": "/c%d", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #7", + "data": "/e^f", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #8", + "data": "/g|h", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #9", + "data": "/i\\j", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #10", + "data": "/k\"l", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #11", + "data": "/ ", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #12", + "data": "/m~0n", + "valid": true + }, + { + "description": "valid JSON-pointer used adding to the last array position", + "data": "/foo/-", + "valid": true + }, + { + "description": "valid JSON-pointer (- used as object member name)", + "data": "/foo/-/bar", + "valid": true + }, + { + "description": "valid JSON-pointer (multiple escaped characters)", + "data": "/~1~0~0~1~1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #1", + "data": "/~1.1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #2", + "data": "/~0.1", + "valid": true + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", + "data": "#", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", + "data": "#/", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", + "data": "#a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #1", + "data": "/~0~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #2", + "data": "/~0/~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #1", + "data": "/~2", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #2", + "data": "/~-1", + "valid": false + }, + { + "description": "not a valid JSON-pointer (multiple characters not escaped)", + "data": "/~~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", + "data": "a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", + "data": "0", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", + "data": "a/a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/unknown.json b/src/test/suite/tests/draft6/optional/format/unknown.json new file mode 100644 index 000000000..12339ae57 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/unknown.json @@ -0,0 +1,43 @@ +[ + { + "description": "unknown format", + "schema": { "format": "unknown" }, + "tests": [ + { + "description": "unknown formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "unknown formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "unknown formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "unknown formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "unknown formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "unknown formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "unknown formats ignore strings", + "data": "string", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/uri-reference.json b/src/test/suite/tests/draft6/optional/format/uri-reference.json new file mode 100644 index 000000000..7cdf228d8 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/uri-reference.json @@ -0,0 +1,73 @@ +[ + { + "description": "validation of URI References", + "schema": { "format": "uri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid relative URI Reference", + "data": "/abc", + "valid": true + }, + { + "description": "an invalid URI Reference", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "a valid URI Reference", + "data": "abc", + "valid": true + }, + { + "description": "a valid URI fragment", + "data": "#fragment", + "valid": true + }, + { + "description": "an invalid URI fragment", + "data": "#frag\\ment", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/uri-template.json b/src/test/suite/tests/draft6/optional/format/uri-template.json new file mode 100644 index 000000000..df355c55a --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/uri-template.json @@ -0,0 +1,58 @@ +[ + { + "description": "format: uri-template", + "schema": { "format": "uri-template" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", + "valid": true + }, + { + "description": "an invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false + }, + { + "description": "a valid uri-template without variables", + "data": "http://example.com/dictionary", + "valid": true + }, + { + "description": "a valid relative uri-template", + "data": "dictionary/{term:1}/{term}", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/format/uri.json b/src/test/suite/tests/draft6/optional/format/uri.json new file mode 100644 index 000000000..4b48d4060 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/format/uri.json @@ -0,0 +1,138 @@ +[ + { + "description": "validation of URIs", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/id.json b/src/test/suite/tests/draft6/optional/id.json new file mode 100644 index 000000000..03d30fcb4 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/id.json @@ -0,0 +1,134 @@ +[ + { + "description": "id inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an id buried in the enum", + "schema": { + "definitions": { + "id_in_enum": { + "enum": [ + { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + } + ] + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "string" + }, + "zzz_id_in_const": { + "const": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/definitions/id_in_enum" }, + { "$ref": "https://localhost:1234/id/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + }, + "valid": true + }, + { + "description": "match $ref to id", + "data": "a string to match #/definitions/id_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to id", + "data": 1, + "valid": false + } + ] + }, + { + "description": "non-schema object containing a plain-name $id property", + "schema": { + "definitions": { + "const_not_anchor": { + "const": { + "$id": "#not_a_real_anchor" + } + } + }, + "oneOf": [ + { + "const": "skip not_a_real_anchor" + }, + { + "allOf": [ + { + "not": { + "const": "skip not_a_real_anchor" + } + }, + { + "$ref": "#/definitions/const_not_anchor" + } + ] + } + ] + }, + "tests": [ + { + "description": "skip traversing definition for a valid result", + "data": "skip not_a_real_anchor", + "valid": true + }, + { + "description": "const at const_not_anchor does not match", + "data": 1, + "valid": false + } + ] + }, + { + "description": "non-schema object containing an $id property", + "schema": { + "definitions": { + "const_not_id": { + "const": { + "$id": "not_a_real_id" + } + } + }, + "oneOf": [ + { + "const":"skip not_a_real_id" + }, + { + "allOf": [ + { + "not": { + "const": "skip not_a_real_id" + } + }, + { + "$ref": "#/definitions/const_not_id" + } + ] + } + ] + }, + "tests": [ + { + "description": "skip traversing definition for a valid result", + "data": "skip not_a_real_id", + "valid": true + }, + { + "description": "const at const_not_id does not match", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/non-bmp-regex.json b/src/test/suite/tests/draft6/optional/non-bmp-regex.json new file mode 100644 index 000000000..dd67af2b2 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/non-bmp-regex.json @@ -0,0 +1,82 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { "pattern": "^🐲*$" }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/optional/unknownKeyword.json b/src/test/suite/tests/draft6/optional/unknownKeyword.json new file mode 100644 index 000000000..1f58d97e3 --- /dev/null +++ b/src/test/suite/tests/draft6/optional/unknownKeyword.json @@ -0,0 +1,56 @@ +[ + { + "description": "$id inside an unknown keyword is not a real identifier", + "comment": "the implementation must not be confused by an $id in locations we do not know how to parse", + "schema": { + "definitions": { + "id_in_unknown0": { + "not": { + "array_of_schemas": [ + { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "null" + } + ] + } + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "string" + }, + "id_in_unknown1": { + "not": { + "object_of_schemas": { + "foo": { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "integer" + } + } + } + } + }, + "anyOf": [ + { "$ref": "#/definitions/id_in_unknown0" }, + { "$ref": "#/definitions/id_in_unknown1" }, + { "$ref": "https://localhost:1234/unknownKeyword/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "type matches second anyOf, which has a real schema in it", + "data": "a string", + "valid": true + }, + { + "description": "type matches non-schema in first anyOf", + "data": null, + "valid": false + }, + { + "description": "type matches non-schema in third anyOf", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/pattern.json b/src/test/suite/tests/draft6/pattern.json new file mode 100644 index 000000000..92db0f971 --- /dev/null +++ b/src/test/suite/tests/draft6/pattern.json @@ -0,0 +1,59 @@ +[ + { + "description": "pattern validation", + "schema": {"pattern": "^a*$"}, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": {"pattern": "a+"}, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/patternProperties.json b/src/test/suite/tests/draft6/patternProperties.json new file mode 100644 index 000000000..c276e6479 --- /dev/null +++ b/src/test/suite/tests/draft6/patternProperties.json @@ -0,0 +1,171 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/properties.json b/src/test/suite/tests/draft6/properties.json new file mode 100644 index 000000000..5b971ca0e --- /dev/null +++ b/src/test/suite/tests/draft6/properties.json @@ -0,0 +1,236 @@ +[ + { + "description": "object properties validation", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/propertyNames.json b/src/test/suite/tests/draft6/propertyNames.json new file mode 100644 index 000000000..7c7b80006 --- /dev/null +++ b/src/test/suite/tests/draft6/propertyNames.json @@ -0,0 +1,154 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames validation with pattern", + "schema": { + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": {"propertyNames": true}, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": {"propertyNames": false}, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with const", + "schema": {"propertyNames": {"const": "foo"}}, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with enum", + "schema": {"propertyNames": {"enum": ["foo", "bar"]}}, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property foo and bar is valid", + "data": {"foo": 1, "bar": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"baz": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/ref.json b/src/test/suite/tests/draft6/ref.json new file mode 100644 index 000000000..379322c71 --- /dev/null +++ b/src/test/suite/tests/draft6/ref.json @@ -0,0 +1,929 @@ +[ + { + "description": "root pointer ref", + "schema": { + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "items": [ + {"type": "integer"}, + {"$ref": "#/items/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "definitions": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/definitions/tilde~0field"}, + "slash": {"$ref": "#/definitions/slash~1field"}, + "percent": {"$ref": "#/definitions/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "definitions": { + "a": {"type": "integer"}, + "b": {"$ref": "#/definitions/a"}, + "c": {"$ref": "#/definitions/b"} + }, + "allOf": [{ "$ref": "#/definitions/c" }] + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref overrides any sibling keywords", + "schema": { + "definitions": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/definitions/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems ignored", + "data": { "foo": [ 1, 2, 3] }, + "valid": true + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "$ref prevents a sibling $id from changing the base uri", + "schema": { + "$id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "$id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "$id": "foo.json", + "type": "number" + } + }, + "allOf": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "$id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + }, + "tests": [ + { + "description": "$ref resolves to /definitions/base_foo, data does not validate", + "data": "a", + "valid": false + }, + { + "description": "$ref resolves to /definitions/base_foo, data validates", + "data": 1, + "valid": true + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": {"$ref": "http://json-schema.org/draft-06/schema#"}, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "properties": { + "$ref": {"$ref": "#/definitions/is-string"} + }, + "definitions": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "allOf": [{ "$ref": "#/definitions/bool" }], + "definitions": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "allOf": [{ "$ref": "#/definitions/bool" }], + "definitions": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$id": "http://localhost:1234/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "definitions": { + "node": { + "$id": "http://localhost:1234/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "properties": { + "foo\"bar": {"$ref": "#/definitions/foo%22bar"} + }, + "definitions": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "Location-independent identifier", + "schema": { + "allOf": [{ + "$ref": "#foo" + }], + "definitions": { + "A": { + "$id": "#foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Reference an anchor with a non-relative URI", + "schema": { + "$id": "https://example.com/schema-with-anchor", + "allOf": [{ + "$ref": "https://example.com/schema-with-anchor#foo" + }], + "definitions": { + "A": { + "$id": "#foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$id": "http://localhost:1234/root", + "allOf": [{ + "$ref": "http://localhost:1234/nested.json#foo" + }], + "definitions": { + "A": { + "$id": "nested.json", + "definitions": { + "B": { + "$id": "#foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "definitions": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/definitions/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/definitions/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] + } + }, + "allOf": [ { "$ref": "schema-relative-uri-defs2.json" } ] + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] + } + }, + "allOf": [ { "$ref": "schema-refs-absolute-uris-defs2.json" } ] + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$comment": "RFC 8141 §2.2", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$comment": "RFC 8141 §2.3.1", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$comment": "RFC 8141 §2.3.2", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "definitions": { + "bar": { + "$id": "#something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$id": "http://example.com/ref/absref.json", + "definitions": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "allOf": [ + { "$ref": "/absref/foobar.json" } + ] + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$id": "file:///folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$id": "file:///c:/folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "definitions": { + "": { + "definitions": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions//definitions/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/refRemote.json b/src/test/suite/tests/draft6/refRemote.json new file mode 100644 index 000000000..49ead6d1f --- /dev/null +++ b/src/test/suite/tests/draft6/refRemote.json @@ -0,0 +1,257 @@ +[ + { + "description": "remote ref", + "schema": {"$ref": "http://localhost:1234/integer.json"}, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": {"$ref": "http://localhost:1234/draft6/subSchemas.json#/definitions/integer"}, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$ref": "http://localhost:1234/draft6/subSchemas.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$id": "http://localhost:1234/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$id": "http://localhost:1234/scope_change_defs1.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz"} + }, + "definitions": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$id": "http://localhost:1234/scope_change_defs2.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz/definitions/bar"} + }, + "definitions": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "definitions": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$id": "http://localhost:1234/object", + "type": "object", + "properties": { + "name": {"$ref": "draft6/name.json#/definitions/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to definitions", + "schema": { + "$id": "http://localhost:1234/schema-remote-ref-ref-defs1.json", + "allOf": [ + { "$ref": "draft6/ref-and-definitions.json" } + ] + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$ref": "http://localhost:1234/draft6/locationIndependentIdentifier.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$id": "http://localhost:1234/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds location-independent $id", + "schema": { + "$ref": "http://localhost:1234/draft6/detached-ref.json#/definitions/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/required.json b/src/test/suite/tests/draft6/required.json new file mode 100644 index 000000000..8d8087afd --- /dev/null +++ b/src/test/suite/tests/draft6/required.json @@ -0,0 +1,151 @@ +[ + { + "description": "required validation", + "schema": { + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { "required": ["__proto__", "toString", "constructor"] }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft6/type.json b/src/test/suite/tests/draft6/type.json new file mode 100644 index 000000000..830464702 --- /dev/null +++ b/src/test/suite/tests/draft6/type.json @@ -0,0 +1,474 @@ +[ + { + "description": "integer type matches integers", + "schema": {"type": "integer"}, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": {"type": "number"}, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": {"type": "string"}, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": {"type": "object"}, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": {"type": "array"}, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": {"type": "boolean"}, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": {"type": "null"}, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": {"type": ["integer", "string"]}, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft6/uniqueItems.json b/src/test/suite/tests/draft6/uniqueItems.json new file mode 100644 index 000000000..d2730c60c --- /dev/null +++ b/src/test/suite/tests/draft6/uniqueItems.json @@ -0,0 +1,409 @@ +[ + { + "description": "uniqueItems validation", + "schema": {"uniqueItems": true}, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { "uniqueItems": false }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/additionalItems.json b/src/test/suite/tests/draft7/additionalItems.json new file mode 100644 index 000000000..2c7d15582 --- /dev/null +++ b/src/test/suite/tests/draft7/additionalItems.json @@ -0,0 +1,206 @@ +[ + { + "description": "additionalItems as schema", + "schema": { + "items": [{}], + "additionalItems": {"type": "integer"} + }, + "tests": [ + { + "description": "additional items match schema", + "data": [ null, 2, 3, 4 ], + "valid": true + }, + { + "description": "additional items do not match schema", + "data": [ null, 2, 3, "foo" ], + "valid": false + } + ] + }, + { + "description": "when items is schema, additionalItems does nothing", + "schema": { + "items": { + "type": "integer" + }, + "additionalItems": { + "type": "string" + } + }, + "tests": [ + { + "description": "valid with a array of type integers", + "data": [1,2,3], + "valid": true + }, + { + "description": "invalid with a array of mixed types", + "data": [1,"2","3"], + "valid": false + } + ] + }, + { + "description": "when items is schema, boolean additionalItems does nothing", + "schema": { + "items": {}, + "additionalItems": false + }, + "tests": [ + { + "description": "all items match schema", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + } + ] + }, + { + "description": "array of items with no additionalItems permitted", + "schema": { + "items": [{}, {}, {}], + "additionalItems": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "additionalItems as false without items", + "schema": {"additionalItems": false}, + "tests": [ + { + "description": + "items defaults to empty schema so everything is valid", + "data": [ 1, 2, 3, 4, 5 ], + "valid": true + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + } + ] + }, + { + "description": "additionalItems are allowed by default", + "schema": {"items": [{"type": "integer"}]}, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, valid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" } ] } + ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, null ], + "valid": true + } + ] + }, + { + "description": "additionalItems does not look in applicators, invalid case", + "schema": { + "allOf": [ + { "items": [ { "type": "integer" }, { "type": "string" } ] } + ], + "items": [ {"type": "integer" } ], + "additionalItems": { "type": "boolean" } + }, + "tests": [ + { + "description": "items defined in allOf are not examined", + "data": [ 1, "hello" ], + "valid": false + } + ] + }, + { + "description": "items validation adjusts the starting index for additionalItems", + "schema": { + "items": [ { "type": "string" } ], + "additionalItems": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "additionalItems with heterogeneous array", + "schema": { + "items": [{}], + "additionalItems": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "additionalItems with null instance elements", + "schema": { + "additionalItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/additionalProperties.json b/src/test/suite/tests/draft7/additionalProperties.json new file mode 100644 index 000000000..0f8e1627a --- /dev/null +++ b/src/test/suite/tests/draft7/additionalProperties.json @@ -0,0 +1,147 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "schema": { + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "schema": { + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": + "additionalProperties can exist by itself", + "schema": { + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "schema": {"properties": {"foo": {}, "bar": {}}}, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "schema": { + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "schema": { + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/allOf.json b/src/test/suite/tests/draft7/allOf.json new file mode 100644 index 000000000..ec9319e14 --- /dev/null +++ b/src/test/suite/tests/draft7/allOf.json @@ -0,0 +1,294 @@ +[ + { + "description": "allOf", + "schema": { + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": {"allOf": [true, true]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": {"allOf": [true, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": {"allOf": [false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/anyOf.json b/src/test/suite/tests/draft7/anyOf.json new file mode 100644 index 000000000..ab5eb386b --- /dev/null +++ b/src/test/suite/tests/draft7/anyOf.json @@ -0,0 +1,189 @@ +[ + { + "description": "anyOf", + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": {"anyOf": [true, true]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": {"anyOf": [true, false]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": {"anyOf": [false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/boolean_schema.json b/src/test/suite/tests/draft7/boolean_schema.json new file mode 100644 index 000000000..6d40f23f2 --- /dev/null +++ b/src/test/suite/tests/draft7/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/const.json b/src/test/suite/tests/draft7/const.json new file mode 100644 index 000000000..1c2cafcc1 --- /dev/null +++ b/src/test/suite/tests/draft7/const.json @@ -0,0 +1,342 @@ +[ + { + "description": "const validation", + "schema": {"const": 2}, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": {"const": {"foo": "bar", "baz": "bax"}}, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": {"const": [{ "foo": "bar" }]}, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": {"const": null}, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": {"const": false}, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": {"const": true}, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": {"const": [false]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": {"const": [true]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": {"const": {"a": false}}, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": {"const": {"a": true}}, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": {"const": 0}, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": {"const": 1}, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": {"const": -2.0}, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": {"const": 9007199254740992}, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "const": "hello\u0000there" }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/contains.json b/src/test/suite/tests/draft7/contains.json new file mode 100644 index 000000000..2b1a51520 --- /dev/null +++ b/src/test/suite/tests/draft7/contains.json @@ -0,0 +1,165 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "contains": {"minimum": 5} + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": {"contains": true}, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": {"contains": false}, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid", + "data": "contains does not apply to strings", + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [ 2, 4, 8 ], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [ 3, 6, 9 ], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [ 6, 12 ], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [ 1, 5 ], + "valid": false + } + ] + }, + { + "description": "contains with false if subschema", + "schema": { + "contains": { + "if": false, + "else": true + } + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/default.json b/src/test/suite/tests/draft7/default.json new file mode 100644 index 000000000..289a9b66c --- /dev/null +++ b/src/test/suite/tests/draft7/default.json @@ -0,0 +1,79 @@ +[ + { + "description": "invalid type for default", + "schema": { + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/definitions.json b/src/test/suite/tests/draft7/definitions.json new file mode 100644 index 000000000..afe396e42 --- /dev/null +++ b/src/test/suite/tests/draft7/definitions.json @@ -0,0 +1,26 @@ +[ + { + "description": "validate definition against metaschema", + "schema": {"$ref": "http://json-schema.org/draft-07/schema#"}, + "tests": [ + { + "description": "valid definition schema", + "data": { + "definitions": { + "foo": {"type": "integer"} + } + }, + "valid": true + }, + { + "description": "invalid definition schema", + "data": { + "definitions": { + "foo": {"type": 1} + } + }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/dependencies.json b/src/test/suite/tests/draft7/dependencies.json new file mode 100644 index 000000000..c0bd809f6 --- /dev/null +++ b/src/test/suite/tests/draft7/dependencies.json @@ -0,0 +1,286 @@ +[ + { + "description": "dependencies", + "schema": { + "dependencies": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "dependencies with empty array", + "schema": { + "dependencies": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependencies", + "schema": { + "dependencies": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "multiple dependencies subschema", + "schema": { + "dependencies": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "dependencies with boolean subschemas", + "schema": { + "dependencies": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "dependencies": { + "foo\nbar": ["foo\rbar"], + "foo\tbar": { + "minProperties": 4 + }, + "foo'bar": {"required": ["foo\"bar"]}, + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "valid object 1", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "valid object 2", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "valid object 3", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "invalid object 1", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "invalid object 2", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "invalid object 3", + "data": { + "foo'bar": 1 + }, + "valid": false + }, + { + "description": "invalid object 4", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "properties": { + "foo": {} + }, + "dependencies": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/enum.json b/src/test/suite/tests/draft7/enum.json new file mode 100644 index 000000000..ce43acc02 --- /dev/null +++ b/src/test/suite/tests/draft7/enum.json @@ -0,0 +1,320 @@ +[ + { + "description": "simple enum validation", + "schema": {"enum": [1, 2, 3]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { "enum": [6, null] }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": {"enum": [false]}, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": {"enum": [[false]]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": {"enum": [true]}, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": {"enum": [[true]]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": {"enum": [0]}, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": {"enum": [[0]]}, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": {"enum": [1]}, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": {"enum": [[1]]}, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { "enum": [ "hello\u0000there" ] }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/exclusiveMaximum.json b/src/test/suite/tests/draft7/exclusiveMaximum.json new file mode 100644 index 000000000..dc3cd709d --- /dev/null +++ b/src/test/suite/tests/draft7/exclusiveMaximum.json @@ -0,0 +1,30 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/exclusiveMinimum.json b/src/test/suite/tests/draft7/exclusiveMinimum.json new file mode 100644 index 000000000..b38d7ecec --- /dev/null +++ b/src/test/suite/tests/draft7/exclusiveMinimum.json @@ -0,0 +1,30 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/format.json b/src/test/suite/tests/draft7/format.json new file mode 100644 index 000000000..e2447d60f --- /dev/null +++ b/src/test/suite/tests/draft7/format.json @@ -0,0 +1,614 @@ +[ + { + "description": "email format", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "idn-email format", + "schema": { "format": "idn-email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { "format": "regex" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { "format": "ipv4" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "idn-hostname format", + "schema": { "format": "idn-hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { "format": "hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { "format": "date" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { "format": "time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { "format": "json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "relative-json-pointer format", + "schema": { "format": "relative-json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "iri format", + "schema": { "format": "iri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "iri-reference format", + "schema": { "format": "iri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { "format": "uri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { "format": "uri-template" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/if-then-else.json b/src/test/suite/tests/draft7/if-then-else.json new file mode 100644 index 000000000..284e91912 --- /dev/null +++ b/src/test/suite/tests/draft7/if-then-else.json @@ -0,0 +1,258 @@ +[ + { + "description": "ignore if without then or else", + "schema": { + "if": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone if", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone if", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore then without if", + "schema": { + "then": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone then", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone then", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore else without if", + "schema": { + "else": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone else", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone else", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "if and then without else", + "schema": { + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid when if test fails", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if and else without then", + "schema": { + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid when if test passes", + "data": -1, + "valid": true + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "validate against correct branch, then vs else", + "schema": { + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "non-interference across combined schemas", + "schema": { + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] + }, + "tests": [ + { + "description": "valid, but would have been invalid through then", + "data": -100, + "valid": true + }, + { + "description": "valid, but would have been invalid through else", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if with boolean schema true", + "schema": { + "if": true, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema true in if always chooses the then path (valid)", + "data": "then", + "valid": true + }, + { + "description": "boolean schema true in if always chooses the then path (invalid)", + "data": "else", + "valid": false + } + ] + }, + { + "description": "if with boolean schema false", + "schema": { + "if": false, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema false in if always chooses the else path (invalid)", + "data": "then", + "valid": false + }, + { + "description": "boolean schema false in if always chooses the else path (valid)", + "data": "else", + "valid": true + } + ] + }, + { + "description": "if appears at the end when serialized (keyword processing sequence)", + "schema": { + "then": { "const": "yes" }, + "else": { "const": "other" }, + "if": { "maxLength": 4 } + }, + "tests": [ + { + "description": "yes redirects to then and passes", + "data": "yes", + "valid": true + }, + { + "description": "other redirects to else and passes", + "data": "other", + "valid": true + }, + { + "description": "no redirects to then and fails", + "data": "no", + "valid": false + }, + { + "description": "invalid redirects to else and fails", + "data": "invalid", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/infinite-loop-detection.json b/src/test/suite/tests/draft7/infinite-loop-detection.json new file mode 100644 index 000000000..f98c74fc6 --- /dev/null +++ b/src/test/suite/tests/draft7/infinite-loop-detection.json @@ -0,0 +1,36 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "definitions": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/definitions/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/definitions/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/items.json b/src/test/suite/tests/draft7/items.json new file mode 100644 index 000000000..7ed6781b6 --- /dev/null +++ b/src/test/suite/tests/draft7/items.json @@ -0,0 +1,282 @@ +[ + { + "description": "a schema given for items", + "schema": { + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "an array of schemas for items", + "schema": { + "items": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": {"items": true}, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": {"items": false}, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schemas", + "schema": { + "items": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "definitions": { + "item": { + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/sub-item" }, + { "$ref": "#/definitions/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "additionalItems": false, + "items": [ + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" }, + { "$ref": "#/definitions/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "single-form items with null instance elements", + "schema": { + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "array-form items with null instance elements", + "schema": { + "items": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/maxItems.json b/src/test/suite/tests/draft7/maxItems.json new file mode 100644 index 000000000..f0c36ab28 --- /dev/null +++ b/src/test/suite/tests/draft7/maxItems.json @@ -0,0 +1,44 @@ +[ + { + "description": "maxItems validation", + "schema": {"maxItems": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": {"maxItems": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/maxLength.json b/src/test/suite/tests/draft7/maxLength.json new file mode 100644 index 000000000..be60c5407 --- /dev/null +++ b/src/test/suite/tests/draft7/maxLength.json @@ -0,0 +1,49 @@ +[ + { + "description": "maxLength validation", + "schema": {"maxLength": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": {"maxLength": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/maxProperties.json b/src/test/suite/tests/draft7/maxProperties.json new file mode 100644 index 000000000..acec14209 --- /dev/null +++ b/src/test/suite/tests/draft7/maxProperties.json @@ -0,0 +1,70 @@ +[ + { + "description": "maxProperties validation", + "schema": {"maxProperties": 2}, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": {"maxProperties": 2.0}, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { "maxProperties": 0 }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/maximum.json b/src/test/suite/tests/draft7/maximum.json new file mode 100644 index 000000000..6844a39ee --- /dev/null +++ b/src/test/suite/tests/draft7/maximum.json @@ -0,0 +1,54 @@ +[ + { + "description": "maximum validation", + "schema": {"maximum": 3.0}, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": {"maximum": 300}, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/minItems.json b/src/test/suite/tests/draft7/minItems.json new file mode 100644 index 000000000..d3b18720d --- /dev/null +++ b/src/test/suite/tests/draft7/minItems.json @@ -0,0 +1,44 @@ +[ + { + "description": "minItems validation", + "schema": {"minItems": 1}, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": {"minItems": 1.0}, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/minLength.json b/src/test/suite/tests/draft7/minLength.json new file mode 100644 index 000000000..23c68fe3f --- /dev/null +++ b/src/test/suite/tests/draft7/minLength.json @@ -0,0 +1,49 @@ +[ + { + "description": "minLength validation", + "schema": {"minLength": 2}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": {"minLength": 2.0}, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/minProperties.json b/src/test/suite/tests/draft7/minProperties.json new file mode 100644 index 000000000..9f74f7891 --- /dev/null +++ b/src/test/suite/tests/draft7/minProperties.json @@ -0,0 +1,54 @@ +[ + { + "description": "minProperties validation", + "schema": {"minProperties": 1}, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": {"minProperties": 1.0}, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/minimum.json b/src/test/suite/tests/draft7/minimum.json new file mode 100644 index 000000000..21ae50e0e --- /dev/null +++ b/src/test/suite/tests/draft7/minimum.json @@ -0,0 +1,69 @@ +[ + { + "description": "minimum validation", + "schema": {"minimum": 1.1}, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": {"minimum": -2}, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/multipleOf.json b/src/test/suite/tests/draft7/multipleOf.json new file mode 100644 index 000000000..e606979b7 --- /dev/null +++ b/src/test/suite/tests/draft7/multipleOf.json @@ -0,0 +1,82 @@ +[ + { + "description": "by int", + "schema": {"multipleOf": 2}, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": {"multipleOf": 1.5}, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": {"multipleOf": 0.0001}, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": {"type": "integer", "multipleOf": 0.123456789}, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": {"type": "integer", "multipleOf": 1e-8}, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/not.json b/src/test/suite/tests/draft7/not.json new file mode 100644 index 000000000..b46c4ed05 --- /dev/null +++ b/src/test/suite/tests/draft7/not.json @@ -0,0 +1,259 @@ +[ + { + "description": "not", + "schema": { + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { "not": {} }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "forbid everything with boolean schema true", + "schema": { "not": true }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "allow everything with boolean schema false", + "schema": { "not": false }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "double negation", + "schema": { "not": { "not": {} } }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/oneOf.json b/src/test/suite/tests/draft7/oneOf.json new file mode 100644 index 000000000..c30a65c0d --- /dev/null +++ b/src/test/suite/tests/draft7/oneOf.json @@ -0,0 +1,274 @@ +[ + { + "description": "oneOf", + "schema": { + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": {"oneOf": [true, true, true]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": {"oneOf": [true, false, false]}, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": {"oneOf": [true, true, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": {"oneOf": [false, false, false]}, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/bignum.json b/src/test/suite/tests/draft7/optional/bignum.json new file mode 100644 index 000000000..94b4a4e62 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/bignum.json @@ -0,0 +1,93 @@ +[ + { + "description": "integer", + "schema": { "type": "integer" }, + "tests": [ + { + "description": "a bignum is an integer", + "data": 12345678910111213141516171819202122232425262728293031, + "valid": true + }, + { + "description": "a negative bignum is an integer", + "data": -12345678910111213141516171819202122232425262728293031, + "valid": true + } + ] + }, + { + "description": "number", + "schema": { "type": "number" }, + "tests": [ + { + "description": "a bignum is a number", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": true + }, + { + "description": "a negative bignum is a number", + "data": -98249283749234923498293171823948729348710298301928331, + "valid": true + } + ] + }, + { + "description": "string", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a bignum is not a string", + "data": 98249283749234923498293171823948729348710298301928331, + "valid": false + } + ] + }, + { + "description": "maximum integer comparison", + "schema": { "maximum": 18446744073709551615 }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision", + "schema": { + "exclusiveMaximum": 972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for high numbers", + "data": 972783798187987123879878123.188781371, + "valid": false + } + ] + }, + { + "description": "minimum integer comparison", + "schema": { "minimum": -18446744073709551615 }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -18446744073709551600, + "valid": true + } + ] + }, + { + "description": "float comparison with high precision on negative numbers", + "schema": { + "exclusiveMinimum": -972783798187987123879878123.18878137 + }, + "tests": [ + { + "description": "comparison works for very negative numbers", + "data": -972783798187987123879878123.188781371, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/content.json b/src/test/suite/tests/draft7/optional/content.json new file mode 100644 index 000000000..3f5a7430b --- /dev/null +++ b/src/test/suite/tests/draft7/optional/content.json @@ -0,0 +1,77 @@ +[ + { + "description": "validation of string-encoded content based on media type", + "schema": { + "contentMediaType": "application/json" + }, + "tests": [ + { + "description": "a valid JSON document", + "data": "{\"foo\": \"bar\"}", + "valid": true + }, + { + "description": "an invalid JSON document", + "data": "{:}", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary string-encoding", + "schema": { + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64 string", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "an invalid base64 string (% is not a valid character)", + "data": "eyJmb28iOi%iYmFyIn0K", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents", + "schema": { + "contentMediaType": "application/json", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document", + "data": "ezp9Cg==", + "valid": false + }, + { + "description": "an invalid base64 string that is valid JSON", + "data": "{}", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/cross-draft.json b/src/test/suite/tests/draft7/optional/cross-draft.json new file mode 100644 index 000000000..8ff537362 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/cross-draft.json @@ -0,0 +1,25 @@ +[ + { + "description": "refs to future drafts are processed as future drafts", + "schema": { + "type": "object", + "allOf": [ + { "properties": { "foo": true } }, + { "$ref": "http://localhost:1234/draft2019-09/dependentRequired.json" } + ] + }, + "tests": [ + { + "description": "missing bar is invalid", + "comment": "if the implementation is not processing the $ref as a 2019-09 schema, this test will fail", + "data": {"foo": "any value"}, + "valid": false + }, + { + "description": "present bar is valid", + "data": {"foo": "any value", "bar": "also any value"}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/ecmascript-regex.json b/src/test/suite/tests/draft7/optional/ecmascript-regex.json new file mode 100644 index 000000000..c4886aaa8 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/ecmascript-regex.json @@ -0,0 +1,552 @@ +[ + { + "description": "ECMA 262 regex $ does not match trailing newline", + "schema": { + "type": "string", + "pattern": "^abc$" + }, + "tests": [ + { + "description": "matches in Python, but not in ECMA 262", + "data": "abc\\n", + "valid": false + }, + { + "description": "matches", + "data": "abc", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex converts \\t to horizontal tab", + "schema": { + "type": "string", + "pattern": "^\\t$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\t", + "valid": false + }, + { + "description": "matches", + "data": "\u0009", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and upper letter", + "schema": { + "type": "string", + "pattern": "^\\cC$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cC", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 regex escapes control codes with \\c and lower letter", + "schema": { + "type": "string", + "pattern": "^\\cc$" + }, + "tests": [ + { + "description": "does not match", + "data": "\\cc", + "valid": false + }, + { + "description": "matches", + "data": "\u0003", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\d matches ascii digits only", + "schema": { + "type": "string", + "pattern": "^\\d$" + }, + "tests": [ + { + "description": "ASCII zero matches", + "data": "0", + "valid": true + }, + { + "description": "NKO DIGIT ZERO does not match (unlike e.g. Python)", + "data": "߀", + "valid": false + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) does not match", + "data": "\u07c0", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\D matches everything but ascii digits", + "schema": { + "type": "string", + "pattern": "^\\D$" + }, + "tests": [ + { + "description": "ASCII zero does not match", + "data": "0", + "valid": false + }, + { + "description": "NKO DIGIT ZERO matches (unlike e.g. Python)", + "data": "߀", + "valid": true + }, + { + "description": "NKO DIGIT ZERO (as \\u escape) matches", + "data": "\u07c0", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\w matches ascii letters only", + "schema": { + "type": "string", + "pattern": "^\\w$" + }, + "tests": [ + { + "description": "ASCII 'a' matches", + "data": "a", + "valid": true + }, + { + "description": "latin-1 e-acute does not match (unlike e.g. Python)", + "data": "é", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\W matches everything but ascii letters", + "schema": { + "type": "string", + "pattern": "^\\W$" + }, + "tests": [ + { + "description": "ASCII 'a' does not match", + "data": "a", + "valid": false + }, + { + "description": "latin-1 e-acute matches (unlike e.g. Python)", + "data": "é", + "valid": true + } + ] + }, + { + "description": "ECMA 262 \\s matches whitespace", + "schema": { + "type": "string", + "pattern": "^\\s$" + }, + "tests": [ + { + "description": "ASCII space matches", + "data": " ", + "valid": true + }, + { + "description": "Character tabulation matches", + "data": "\t", + "valid": true + }, + { + "description": "Line tabulation matches", + "data": "\u000b", + "valid": true + }, + { + "description": "Form feed matches", + "data": "\u000c", + "valid": true + }, + { + "description": "latin-1 non-breaking-space matches", + "data": "\u00a0", + "valid": true + }, + { + "description": "zero-width whitespace matches", + "data": "\ufeff", + "valid": true + }, + { + "description": "line feed matches (line terminator)", + "data": "\u000a", + "valid": true + }, + { + "description": "paragraph separator matches (line terminator)", + "data": "\u2029", + "valid": true + }, + { + "description": "EM SPACE matches (Space_Separator)", + "data": "\u2003", + "valid": true + }, + { + "description": "Non-whitespace control does not match", + "data": "\u0001", + "valid": false + }, + { + "description": "Non-whitespace does not match", + "data": "\u2013", + "valid": false + } + ] + }, + { + "description": "ECMA 262 \\S matches everything but whitespace", + "schema": { + "type": "string", + "pattern": "^\\S$" + }, + "tests": [ + { + "description": "ASCII space does not match", + "data": " ", + "valid": false + }, + { + "description": "Character tabulation does not match", + "data": "\t", + "valid": false + }, + { + "description": "Line tabulation does not match", + "data": "\u000b", + "valid": false + }, + { + "description": "Form feed does not match", + "data": "\u000c", + "valid": false + }, + { + "description": "latin-1 non-breaking-space does not match", + "data": "\u00a0", + "valid": false + }, + { + "description": "zero-width whitespace does not match", + "data": "\ufeff", + "valid": false + }, + { + "description": "line feed does not match (line terminator)", + "data": "\u000a", + "valid": false + }, + { + "description": "paragraph separator does not match (line terminator)", + "data": "\u2029", + "valid": false + }, + { + "description": "EM SPACE does not match (Space_Separator)", + "data": "\u2003", + "valid": false + }, + { + "description": "Non-whitespace control matches", + "data": "\u0001", + "valid": true + }, + { + "description": "Non-whitespace matches", + "data": "\u2013", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with pattern", + "schema": { "pattern": "\\p{Letter}cole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "\\w in patterns matches [A-Za-z0-9_], not unicode letters", + "schema": { "pattern": "\\wcole" }, + "tests": [ + { + "description": "ascii character in json string", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": "LES HIVERS DE MON ENFANCE ÉTAIENT DES SAISONS LONGUES, LONGUES. NOUS VIVIONS EN TROIS LIEUX: L'ÉCOLE, L'ÉGLISE ET LA PATINOIRE; MAIS LA VRAIE VIE ÉTAIT SUR LA PATINOIRE.", + "valid": false + } + ] + }, + { + "description": "pattern with ASCII ranges", + "schema": { "pattern": "[a-z]cole" }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'école, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": "Les hivers de mon enfance étaient des saisons longues, longues. Nous vivions en trois lieux: l'\u00e9cole, l'église et la patinoire; mais la vraie vie était sur la patinoire.", + "valid": false + }, + { + "description": "ascii characters match", + "data": "Les hivers de mon enfance etaient des saisons longues, longues. Nous vivions en trois lieux: l'ecole, l'eglise et la patinoire; mais la vraie vie etait sur la patinoire.", + "valid": true + } + ] + }, + { + "description": "\\d in pattern matches [0-9], not unicode digits", + "schema": { "pattern": "^\\d+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": false + } + ] + }, + { + "description": "pattern with non-ASCII digits", + "schema": { "pattern": "^\\p{digit}+$" }, + "tests": [ + { + "description": "ascii digits", + "data": "42", + "valid": true + }, + { + "description": "ascii non-digits", + "data": "-%#", + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": "৪২", + "valid": true + } + ] + }, + { + "description": "patterns always use unicode semantics with patternProperties", + "schema": { + "type": "object", + "patternProperties": { + "\\p{Letter}cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "\\w in patternProperties matches [A-Za-z0-9_], not unicode letters", + "schema": { + "type": "object", + "patternProperties": { + "\\wcole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii character in json string", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + }, + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode matching is case-sensitive", + "data": { "L'ÉCOLE": "PAS DE VRAIE VIE" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with ASCII ranges", + "schema": { + "type": "object", + "patternProperties": { + "[a-z]cole": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "literal unicode character in json string", + "data": { "l'école": "pas de vraie vie" }, + "valid": false + }, + { + "description": "unicode character in hex format in string", + "data": { "l'\u00e9cole": "pas de vraie vie" }, + "valid": false + }, + { + "description": "ascii characters match", + "data": { "l'ecole": "pas de vraie vie" }, + "valid": true + } + ] + }, + { + "description": "\\d in patternProperties matches [0-9], not unicode digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\d+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": false + } + ] + }, + { + "description": "patternProperties with non-ASCII digits", + "schema": { + "type": "object", + "patternProperties": { + "^\\p{digit}+$": true + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "ascii digits", + "data": { "42": "life, the universe, and everything" }, + "valid": true + }, + { + "description": "ascii non-digits", + "data": { "-%#": "spending the year dead for tax reasons" }, + "valid": false + }, + { + "description": "non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO)", + "data": { "৪২": "khajit has wares if you have coin" }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/float-overflow.json b/src/test/suite/tests/draft7/optional/float-overflow.json new file mode 100644 index 000000000..52ff9827c --- /dev/null +++ b/src/test/suite/tests/draft7/optional/float-overflow.json @@ -0,0 +1,13 @@ +[ + { + "description": "all integers are multiples of 0.5, if overflow is handled", + "schema": {"type": "integer", "multipleOf": 0.5}, + "tests": [ + { + "description": "valid if optional overflow handling is implemented", + "data": 1e308, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/date-time.json b/src/test/suite/tests/draft7/optional/format/date-time.json new file mode 100644 index 000000000..091127375 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/date-time.json @@ -0,0 +1,133 @@ +[ + { + "description": "validation of date-time strings", + "schema": { "format": "date-time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date-time string", + "data": "1963-06-19T08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid date-time string without second fraction", + "data": "1963-06-19T08:30:06Z", + "valid": true + }, + { + "description": "a valid date-time string with plus offset", + "data": "1937-01-01T12:00:27.87+00:20", + "valid": true + }, + { + "description": "a valid date-time string with minus offset", + "data": "1990-12-31T15:59:50.123-08:00", + "valid": true + }, + { + "description": "a valid date-time with a leap second, UTC", + "data": "1998-12-31T23:59:60Z", + "valid": true + }, + { + "description": "a valid date-time with a leap second, with minus offset", + "data": "1998-12-31T15:59:60.123-08:00", + "valid": true + }, + { + "description": "an invalid date-time past leap second, UTC", + "data": "1998-12-31T23:59:61Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "data": "1998-12-31T23:58:60Z", + "valid": false + }, + { + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "data": "1998-12-31T22:59:60Z", + "valid": false + }, + { + "description": "an invalid day in date-time string", + "data": "1990-02-31T15:59:59.123-08:00", + "valid": false + }, + { + "description": "an invalid offset in date-time string", + "data": "1990-12-31T15:59:59-24:00", + "valid": false + }, + { + "description": "an invalid closing Z after time-zone offset", + "data": "1963-06-19T08:30:06.28123+01:00Z", + "valid": false + }, + { + "description": "an invalid date-time string", + "data": "06/19/1963 08:30:06 PST", + "valid": false + }, + { + "description": "case-insensitive T and Z", + "data": "1963-06-19t08:30:06.283185z", + "valid": true + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350T01:01:01", + "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "data": "1963-06-1৪T00:00:00Z", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "data": "1963-06-11T0৪:00:00Z", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/date.json b/src/test/suite/tests/draft7/optional/format/date.json new file mode 100644 index 000000000..d723124a4 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/date.json @@ -0,0 +1,243 @@ +[ + { + "description": "validation of date strings", + "schema": { "format": "date" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid date string", + "data": "1963-06-19", + "valid": true + }, + { + "description": "a valid date string with 31 days in January", + "data": "2020-01-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in January", + "data": "2020-01-32", + "valid": false + }, + { + "description": "a valid date string with 28 days in February (normal)", + "data": "2021-02-28", + "valid": true + }, + { + "description": "a invalid date string with 29 days in February (normal)", + "data": "2021-02-29", + "valid": false + }, + { + "description": "a valid date string with 29 days in February (leap)", + "data": "2020-02-29", + "valid": true + }, + { + "description": "a invalid date string with 30 days in February (leap)", + "data": "2020-02-30", + "valid": false + }, + { + "description": "a valid date string with 31 days in March", + "data": "2020-03-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in March", + "data": "2020-03-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in April", + "data": "2020-04-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in April", + "data": "2020-04-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in May", + "data": "2020-05-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in May", + "data": "2020-05-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in June", + "data": "2020-06-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in June", + "data": "2020-06-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in July", + "data": "2020-07-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in July", + "data": "2020-07-32", + "valid": false + }, + { + "description": "a valid date string with 31 days in August", + "data": "2020-08-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in August", + "data": "2020-08-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in September", + "data": "2020-09-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in September", + "data": "2020-09-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in October", + "data": "2020-10-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in October", + "data": "2020-10-32", + "valid": false + }, + { + "description": "a valid date string with 30 days in November", + "data": "2020-11-30", + "valid": true + }, + { + "description": "a invalid date string with 31 days in November", + "data": "2020-11-31", + "valid": false + }, + { + "description": "a valid date string with 31 days in December", + "data": "2020-12-31", + "valid": true + }, + { + "description": "a invalid date string with 32 days in December", + "data": "2020-12-32", + "valid": false + }, + { + "description": "a invalid date string with invalid month", + "data": "2020-13-01", + "valid": false + }, + { + "description": "an invalid date string", + "data": "06/19/1963", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "non-padded month dates are not valid", + "data": "1998-1-20", + "valid": false + }, + { + "description": "non-padded day dates are not valid", + "data": "1998-01-1", + "valid": false + }, + { + "description": "invalid month", + "data": "1998-13-01", + "valid": false + }, + { + "description": "invalid month-day combination", + "data": "1998-04-31", + "valid": false + }, + { + "description": "2021 is not a leap year", + "data": "2021-02-29", + "valid": false + }, + { + "description": "2020 is a leap year", + "data": "2020-02-29", + "valid": true + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1963-06-1৪", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", + "data": "20230328", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", + "data": "2023-W01", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", + "data": "2023-W13-2", + "valid": false + }, + { + "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", + "data": "2022W527", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/email.json b/src/test/suite/tests/draft7/optional/format/email.json new file mode 100644 index 000000000..84113f8a7 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/email.json @@ -0,0 +1,93 @@ +[ + { + "description": "validation of e-mail addresses", + "schema": { "format": "email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "tilde in local part is valid", + "data": "te~st@example.com", + "valid": true + }, + { + "description": "tilde before local part is valid", + "data": "~test@example.com", + "valid": true + }, + { + "description": "tilde after local part is valid", + "data": "test~@example.com", + "valid": true + }, + { + "description": "dot before local part is not valid", + "data": ".test@example.com", + "valid": false + }, + { + "description": "dot after local part is not valid", + "data": "test.@example.com", + "valid": false + }, + { + "description": "two separated dots inside local part are valid", + "data": "te.s.t@example.com", + "valid": true + }, + { + "description": "two subsequent dots inside local part are not valid", + "data": "te..st@example.com", + "valid": false + }, + { + "description": "two email addresses is not valid", + "data": "user1@oceania.org, user2@oceania.org", + "valid": false + }, + { + "description": "full \"From\" header is invalid", + "data": "\"Winston Smith\" (Records Department)", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/hostname.json b/src/test/suite/tests/draft7/optional/format/hostname.json new file mode 100644 index 000000000..866a61788 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/hostname.json @@ -0,0 +1,128 @@ +[ + { + "description": "validation of host names", + "schema": { "format": "hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name", + "data": "www.example.com", + "valid": true + }, + { + "description": "a valid punycoded IDN hostname", + "data": "xn--4gbwdl.xn--wgbh1c", + "valid": true + }, + { + "description": "a host name starting with an illegal character", + "data": "-a-host-name-that-starts-with--", + "valid": false + }, + { + "description": "a host name containing illegal characters", + "data": "not_a_valid_host_name", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", + "valid": false + }, + { + "description": "starts with hyphen", + "data": "-hostname", + "valid": false + }, + { + "description": "ends with hyphen", + "data": "hostname-", + "valid": false + }, + { + "description": "starts with underscore", + "data": "_hostname", + "valid": false + }, + { + "description": "ends with underscore", + "data": "hostname_", + "valid": false + }, + { + "description": "contains underscore", + "data": "host_name", + "valid": false + }, + { + "description": "maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com", + "valid": true + }, + { + "description": "exceeds maximum label length", + "data": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl.com", + "valid": false + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "single dot", + "data": ".", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/idn-email.json b/src/test/suite/tests/draft7/optional/format/idn-email.json new file mode 100644 index 000000000..6e213745a --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/idn-email.json @@ -0,0 +1,58 @@ +[ + { + "description": "validation of an internationalized e-mail addresses", + "schema": { "format": "idn-email" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid idn e-mail (example@example.test in Hangul)", + "data": "실례@실례.테스트", + "valid": true + }, + { + "description": "an invalid idn e-mail address", + "data": "2962", + "valid": false + }, + { + "description": "a valid e-mail address", + "data": "joe.bloggs@example.com", + "valid": true + }, + { + "description": "an invalid e-mail address", + "data": "2962", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/idn-hostname.json b/src/test/suite/tests/draft7/optional/format/idn-hostname.json new file mode 100644 index 000000000..5c8cdc77b --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/idn-hostname.json @@ -0,0 +1,378 @@ +[ + { + "description": "validation of internationalized host names", + "schema": { "format": "idn-hostname" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid host name (example.test in Hangul)", + "data": "실례.테스트", + "valid": true + }, + { + "description": "illegal first char U+302E Hangul single dot tone mark", + "data": "〮실례.테스트", + "valid": false + }, + { + "description": "contains illegal char U+302E Hangul single dot tone mark", + "data": "실〮례.테스트", + "valid": false + }, + { + "description": "a host name with a component too long", + "data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트", + "valid": false + }, + { + "description": "invalid label, correct Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc3492#section-7.1", + "data": "-> $1.00 <--", + "valid": false + }, + { + "description": "valid Chinese Punycode", + "comment": "https://tools.ietf.org/html/rfc5890#section-2.3.2.1 https://tools.ietf.org/html/rfc5891#section-4.4", + "data": "xn--ihqwcrb4cv8a8dqg056pqjye", + "valid": true + }, + { + "description": "invalid Punycode", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.4 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "xn--X", + "valid": false + }, + { + "description": "U-label contains \"--\" in the 3rd and 4th position", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1 https://tools.ietf.org/html/rfc5890#section-2.3.2.1", + "data": "XN--aa---o47jg78q", + "valid": false + }, + { + "description": "U-label starts with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello", + "valid": false + }, + { + "description": "U-label ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "hello-", + "valid": false + }, + { + "description": "U-label starts and ends with a dash", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.1", + "data": "-hello-", + "valid": false + }, + { + "description": "Begins with a Spacing Combining Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0903hello", + "valid": false + }, + { + "description": "Begins with a Nonspacing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0300hello", + "valid": false + }, + { + "description": "Begins with an Enclosing Mark", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.2", + "data": "\u0488hello", + "valid": false + }, + { + "description": "Exceptions that are PVALID, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u00df\u03c2\u0f0b\u3007", + "valid": true + }, + { + "description": "Exceptions that are PVALID, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u06fd\u06fe", + "valid": true + }, + { + "description": "Exceptions that are DISALLOWED, right-to-left chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6", + "data": "\u0640\u07fa", + "valid": false + }, + { + "description": "Exceptions that are DISALLOWED, left-to-right chars", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.2 https://tools.ietf.org/html/rfc5892#section-2.6 Note: The two combining marks (U+302E and U+302F) are in the middle and not at the start", + "data": "\u3031\u3032\u3033\u3034\u3035\u302e\u302f\u303b", + "valid": false + }, + { + "description": "MIDDLE DOT with no preceding 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "a\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing preceding", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "\u00b7l", + "valid": false + }, + { + "description": "MIDDLE DOT with no following 'l'", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7a", + "valid": false + }, + { + "description": "MIDDLE DOT with nothing following", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7", + "valid": false + }, + { + "description": "MIDDLE DOT with surrounding 'l's", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.3", + "data": "l\u00b7l", + "valid": true + }, + { + "description": "Greek KERAIA not followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375S", + "valid": false + }, + { + "description": "Greek KERAIA not followed by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375", + "valid": false + }, + { + "description": "Greek KERAIA followed by Greek", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.4", + "data": "\u03b1\u0375\u03b2", + "valid": true + }, + { + "description": "Hebrew GERESH not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "A\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05f3\u05d1", + "valid": false + }, + { + "description": "Hebrew GERESH preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.5", + "data": "\u05d0\u05f3\u05d1", + "valid": true + }, + { + "description": "Hebrew GERSHAYIM not preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "A\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05f4\u05d1", + "valid": false + }, + { + "description": "Hebrew GERSHAYIM preceded by Hebrew", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.6", + "data": "\u05d0\u05f4\u05d1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "def\u30fbabc", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with no other characters", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb", + "valid": false + }, + { + "description": "KATAKANA MIDDLE DOT with Hiragana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u3041", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Katakana", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u30a1", + "valid": true + }, + { + "description": "KATAKANA MIDDLE DOT with Han", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.7", + "data": "\u30fb\u4e08", + "valid": true + }, + { + "description": "Arabic-Indic digits mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u06f0", + "valid": false + }, + { + "description": "Arabic-Indic digits not mixed with Extended Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.8", + "data": "\u0628\u0660\u0628", + "valid": true + }, + { + "description": "Extended Arabic-Indic digits not mixed with Arabic-Indic digits", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.9", + "data": "\u06f00", + "valid": true + }, + { + "description": "ZERO WIDTH JOINER not preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER not preceded by anything", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u200d\u0937", + "valid": false + }, + { + "description": "ZERO WIDTH JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.2 https://www.unicode.org/review/pr-37.pdf", + "data": "\u0915\u094d\u200d\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER preceded by Virama", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1", + "data": "\u0915\u094d\u200c\u0937", + "valid": true + }, + { + "description": "ZERO WIDTH NON-JOINER not preceded by Virama but matches regexp", + "comment": "https://tools.ietf.org/html/rfc5891#section-4.2.3.3 https://tools.ietf.org/html/rfc5892#appendix-A.1 https://www.w3.org/TR/alreq/#h_disjoining_enforcement", + "data": "\u0628\u064a\u200c\u0628\u064a", + "valid": true + }, + { + "description": "single label", + "data": "hostname", + "valid": true + }, + { + "description": "single label with hyphen", + "data": "host-name", + "valid": true + }, + { + "description": "single label with digits", + "data": "h0stn4me", + "valid": true + }, + { + "description": "single label ending with digit", + "data": "hostnam3", + "valid": true + }, + { + "description": "empty string", + "data": "", + "valid": false + } + ] + }, + { + "description": "validation of separators in internationalized host names", + "specification": [ + {"rfc3490": "3.1", "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)"} + ], + "schema": { "format": "idn-hostname" }, + "tests": [ + { + "description": "single dot", + "data": ".", + "valid": false + }, + { + "description": "single ideographic full stop", + "data": "\u3002", + "valid": false + }, + { + "description": "single fullwidth full stop", + "data": "\uff0e", + "valid": false + }, + { + "description": "single halfwidth ideographic full stop", + "data": "\uff61", + "valid": false + }, + { + "description": "dot as label separator", + "data": "a.b", + "valid": true + }, + { + "description": "ideographic full stop as label separator", + "data": "a\u3002b", + "valid": true + }, + { + "description": "fullwidth full stop as label separator", + "data": "a\uff0eb", + "valid": true + }, + { + "description": "halfwidth ideographic full stop as label separator", + "data": "a\uff61b", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/ipv4.json b/src/test/suite/tests/draft7/optional/format/ipv4.json new file mode 100644 index 000000000..9680fe620 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/ipv4.json @@ -0,0 +1,89 @@ +[ + { + "description": "validation of IP addresses", + "schema": { "format": "ipv4" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IP address", + "data": "192.168.0.1", + "valid": true + }, + { + "description": "an IP address with too many components", + "data": "127.0.0.0.1", + "valid": false + }, + { + "description": "an IP address with out-of-range values", + "data": "256.256.256.256", + "valid": false + }, + { + "description": "an IP address without 4 components", + "data": "127.0", + "valid": false + }, + { + "description": "an IP address as an integer", + "data": "0x7f000001", + "valid": false + }, + { + "description": "an IP address as an integer (decimal)", + "data": "2130706433", + "valid": false + }, + { + "description": "invalid leading zeroes, as they are treated as octals", + "comment": "see https://sick.codes/universal-netmask-npm-package-used-by-270000-projects-vulnerable-to-octal-input-data-server-side-request-forgery-remote-file-inclusion-local-file-inclusion-and-more-cve-2021-28918/", + "data": "087.10.0.1", + "valid": false + }, + { + "description": "value without leading zero is valid", + "data": "87.10.0.1", + "valid": true + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২7.0.0.1", + "valid": false + }, + { + "description": "netmask is not a part of ipv4 address", + "data": "192.168.1.0/24", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/ipv6.json b/src/test/suite/tests/draft7/optional/format/ipv6.json new file mode 100644 index 000000000..94368f2a0 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/ipv6.json @@ -0,0 +1,208 @@ +[ + { + "description": "validation of IPv6 addresses", + "schema": { "format": "ipv6" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IPv6 address", + "data": "::1", + "valid": true + }, + { + "description": "an IPv6 address with out-of-range values", + "data": "12345::", + "valid": false + }, + { + "description": "trailing 4 hex symbols is valid", + "data": "::abef", + "valid": true + }, + { + "description": "trailing 5 hex symbols is invalid", + "data": "::abcef", + "valid": false + }, + { + "description": "an IPv6 address with too many components", + "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", + "valid": false + }, + { + "description": "an IPv6 address containing illegal characters", + "data": "::laptop", + "valid": false + }, + { + "description": "no digits is valid", + "data": "::", + "valid": true + }, + { + "description": "leading colons is valid", + "data": "::42:ff:1", + "valid": true + }, + { + "description": "trailing colons is valid", + "data": "d6::", + "valid": true + }, + { + "description": "missing leading octet is invalid", + "data": ":2:3:4:5:6:7:8", + "valid": false + }, + { + "description": "missing trailing octet is invalid", + "data": "1:2:3:4:5:6:7:", + "valid": false + }, + { + "description": "missing leading octet with omitted octets later", + "data": ":2:3:4::8", + "valid": false + }, + { + "description": "single set of double colons in the middle is valid", + "data": "1:d6::42", + "valid": true + }, + { + "description": "two sets of double colons is invalid", + "data": "1::d6::42", + "valid": false + }, + { + "description": "mixed format with the ipv4 section as decimal octets", + "data": "1::d6:192.168.0.1", + "valid": true + }, + { + "description": "mixed format with double colons between the sections", + "data": "1:2::192.168.0.1", + "valid": true + }, + { + "description": "mixed format with ipv4 section with octet out of range", + "data": "1::2:192.168.256.1", + "valid": false + }, + { + "description": "mixed format with ipv4 section with a hex octet", + "data": "1::2:192.168.ff.1", + "valid": false + }, + { + "description": "mixed format with leading double colons (ipv4-mapped ipv6 address)", + "data": "::ffff:192.168.0.1", + "valid": true + }, + { + "description": "triple colons is invalid", + "data": "1:2:3:4:5:::8", + "valid": false + }, + { + "description": "8 octets", + "data": "1:2:3:4:5:6:7:8", + "valid": true + }, + { + "description": "insufficient octets without double colons", + "data": "1:2:3:4:5:6:7", + "valid": false + }, + { + "description": "no colons is invalid", + "data": "1", + "valid": false + }, + { + "description": "ipv4 is not ipv6", + "data": "127.0.0.1", + "valid": false + }, + { + "description": "ipv4 segment must have 4 octets", + "data": "1:2:3:4:1.2.3", + "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "data": "1:2:3:4:5:6:7:৪", + "valid": false + }, + { + "description": "invalid non-ASCII '৪' (a Bengali 4) in the IPv4 portion", + "data": "1:2::192.16৪.0.1", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/iri-reference.json b/src/test/suite/tests/draft7/optional/format/iri-reference.json new file mode 100644 index 000000000..c6b4c22a2 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/iri-reference.json @@ -0,0 +1,73 @@ +[ + { + "description": "validation of IRI References", + "schema": { "format": "iri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid protocol-relative IRI Reference", + "data": "//ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid relative IRI Reference", + "data": "/âππ", + "valid": true + }, + { + "description": "an invalid IRI Reference", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "a valid IRI Reference", + "data": "âππ", + "valid": true + }, + { + "description": "a valid IRI fragment", + "data": "#ƒrägmênt", + "valid": true + }, + { + "description": "an invalid IRI fragment", + "data": "#ƒräg\\mênt", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/iri.json b/src/test/suite/tests/draft7/optional/format/iri.json new file mode 100644 index 000000000..a0d12aed6 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/iri.json @@ -0,0 +1,83 @@ +[ + { + "description": "validation of IRIs", + "schema": { "format": "iri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid IRI with anchor tag", + "data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx", + "valid": true + }, + { + "description": "a valid IRI with anchor tag and parentheses", + "data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1", + "valid": true + }, + { + "description": "a valid IRI with URL-encoded stuff", + "data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid IRI with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid IRI based on IPv6", + "data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + "valid": true + }, + { + "description": "an invalid IRI based on IPv6", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": false + }, + { + "description": "an invalid relative IRI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid IRI", + "data": "\\\\WINDOWS\\filëßåré", + "valid": false + }, + { + "description": "an invalid IRI though valid IRI reference", + "data": "âππ", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/json-pointer.json b/src/test/suite/tests/draft7/optional/format/json-pointer.json new file mode 100644 index 000000000..a0346b575 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/json-pointer.json @@ -0,0 +1,198 @@ +[ + { + "description": "validation of JSON-pointers (JSON String Representation)", + "schema": { "format": "json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid JSON-pointer", + "data": "/foo/bar~0/baz~1/%a", + "valid": true + }, + { + "description": "not a valid JSON-pointer (~ not escaped)", + "data": "/foo/bar~", + "valid": false + }, + { + "description": "valid JSON-pointer with empty segment", + "data": "/foo//bar", + "valid": true + }, + { + "description": "valid JSON-pointer with the last empty segment", + "data": "/foo/bar/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #1", + "data": "", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #2", + "data": "/foo", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #3", + "data": "/foo/0", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #4", + "data": "/", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #5", + "data": "/a~1b", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #6", + "data": "/c%d", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #7", + "data": "/e^f", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #8", + "data": "/g|h", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #9", + "data": "/i\\j", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #10", + "data": "/k\"l", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #11", + "data": "/ ", + "valid": true + }, + { + "description": "valid JSON-pointer as stated in RFC 6901 #12", + "data": "/m~0n", + "valid": true + }, + { + "description": "valid JSON-pointer used adding to the last array position", + "data": "/foo/-", + "valid": true + }, + { + "description": "valid JSON-pointer (- used as object member name)", + "data": "/foo/-/bar", + "valid": true + }, + { + "description": "valid JSON-pointer (multiple escaped characters)", + "data": "/~1~0~0~1~1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #1", + "data": "/~1.1", + "valid": true + }, + { + "description": "valid JSON-pointer (escaped with fraction part) #2", + "data": "/~0.1", + "valid": true + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #1", + "data": "#", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #2", + "data": "#/", + "valid": false + }, + { + "description": "not a valid JSON-pointer (URI Fragment Identifier) #3", + "data": "#a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #1", + "data": "/~0~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (some escaped, but not all) #2", + "data": "/~0/~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #1", + "data": "/~2", + "valid": false + }, + { + "description": "not a valid JSON-pointer (wrong escape character) #2", + "data": "/~-1", + "valid": false + }, + { + "description": "not a valid JSON-pointer (multiple characters not escaped)", + "data": "/~~", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #1", + "data": "a", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #2", + "data": "0", + "valid": false + }, + { + "description": "not a valid JSON-pointer (isn't empty nor starts with /) #3", + "data": "a/a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/regex.json b/src/test/suite/tests/draft7/optional/format/regex.json new file mode 100644 index 000000000..34491770a --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/regex.json @@ -0,0 +1,48 @@ +[ + { + "description": "validation of regular expressions", + "schema": { "format": "regex" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid regular expression", + "data": "([abc])+\\s+$", + "valid": true + }, + { + "description": "a regular expression with unclosed parens is invalid", + "data": "^(abc]", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/relative-json-pointer.json b/src/test/suite/tests/draft7/optional/format/relative-json-pointer.json new file mode 100644 index 000000000..e50e505f7 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/relative-json-pointer.json @@ -0,0 +1,98 @@ +[ + { + "description": "validation of Relative JSON Pointers (RJP)", + "schema": { "format": "relative-json-pointer" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid upwards RJP", + "data": "1", + "valid": true + }, + { + "description": "a valid downwards RJP", + "data": "0/foo/bar", + "valid": true + }, + { + "description": "a valid up and then down RJP, with array index", + "data": "2/0/baz/1/zip", + "valid": true + }, + { + "description": "a valid RJP taking the member or index name", + "data": "0#", + "valid": true + }, + { + "description": "an invalid RJP that is a valid JSON Pointer", + "data": "/foo/bar", + "valid": false + }, + { + "description": "negative prefix", + "data": "-1/foo/bar", + "valid": false + }, + { + "description": "explicit positive prefix", + "data": "+1/foo/bar", + "valid": false + }, + { + "description": "## is not a valid json-pointer", + "data": "0##", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus json-pointer", + "data": "01/a", + "valid": false + }, + { + "description": "zero cannot be followed by other digits, plus octothorpe", + "data": "01#", + "valid": false + }, + { + "description": "empty string", + "data": "", + "valid": false + }, + { + "description": "multi-digit integer prefix", + "data": "120/foo/bar", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/time.json b/src/test/suite/tests/draft7/optional/format/time.json new file mode 100644 index 000000000..014ecd8d7 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/time.json @@ -0,0 +1,233 @@ +[ + { + "description": "validation of time strings", + "schema": { "format": "time" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid time string", + "data": "08:30:06Z", + "valid": true + }, + { + "description": "invalid time string with extra leading zeros", + "data": "008:030:006Z", + "valid": false + }, + { + "description": "invalid time string with no leading zero for single digit", + "data": "8:3:6Z", + "valid": false + }, + { + "description": "hour, minute, second must be two digits", + "data": "8:0030:6Z", + "valid": false + }, + { + "description": "a valid time string with leap second, Zulu", + "data": "23:59:60Z", + "valid": true + }, + { + "description": "invalid leap second, Zulu (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "invalid leap second, Zulu (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "valid leap second, zero time-offset", + "data": "23:59:60+00:00", + "valid": true + }, + { + "description": "invalid leap second, zero time-offset (wrong hour)", + "data": "22:59:60+00:00", + "valid": false + }, + { + "description": "invalid leap second, zero time-offset (wrong minute)", + "data": "23:58:60+00:00", + "valid": false + }, + { + "description": "valid leap second, positive time-offset", + "data": "01:29:60+01:30", + "valid": true + }, + { + "description": "valid leap second, large positive time-offset", + "data": "23:29:60+23:30", + "valid": true + }, + { + "description": "invalid leap second, positive time-offset (wrong hour)", + "data": "23:59:60+01:00", + "valid": false + }, + { + "description": "invalid leap second, positive time-offset (wrong minute)", + "data": "23:59:60+00:30", + "valid": false + }, + { + "description": "valid leap second, negative time-offset", + "data": "15:59:60-08:00", + "valid": true + }, + { + "description": "valid leap second, large negative time-offset", + "data": "00:29:60-23:30", + "valid": true + }, + { + "description": "invalid leap second, negative time-offset (wrong hour)", + "data": "23:59:60-01:00", + "valid": false + }, + { + "description": "invalid leap second, negative time-offset (wrong minute)", + "data": "23:59:60-00:30", + "valid": false + }, + { + "description": "a valid time string with second fraction", + "data": "23:20:50.52Z", + "valid": true + }, + { + "description": "a valid time string with precise second fraction", + "data": "08:30:06.283185Z", + "valid": true + }, + { + "description": "a valid time string with plus offset", + "data": "08:30:06+00:20", + "valid": true + }, + { + "description": "a valid time string with minus offset", + "data": "08:30:06-08:00", + "valid": true + }, + { + "description": "hour, minute in time-offset must be two digits", + "data": "08:30:06-8:000", + "valid": false + }, + { + "description": "a valid time string with case-insensitive Z", + "data": "08:30:06z", + "valid": true + }, + { + "description": "an invalid time string with invalid hour", + "data": "24:00:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid minute", + "data": "00:60:00Z", + "valid": false + }, + { + "description": "an invalid time string with invalid second", + "data": "00:00:61Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong hour)", + "data": "22:59:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid leap second (wrong minute)", + "data": "23:58:60Z", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset hour", + "data": "01:02:03+24:00", + "valid": false + }, + { + "description": "an invalid time string with invalid time numoffset minute", + "data": "01:02:03+00:60", + "valid": false + }, + { + "description": "an invalid time string with invalid time with both Z and numoffset", + "data": "01:02:03Z+00:30", + "valid": false + }, + { + "description": "an invalid offset indicator", + "data": "08:30:06 PST", + "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "01:01:01,1111", + "valid": false + }, + { + "description": "no time offset", + "data": "12:00:00", + "valid": false + }, + { + "description": "no time offset with second fraction", + "data": "12:00:00.52", + "valid": false + }, + { + "description": "invalid non-ASCII '২' (a Bengali 2)", + "data": "1২:00:00Z", + "valid": false + }, + { + "description": "offset not starting with plus or minus", + "data": "08:30:06#00:20", + "valid": false + }, + { + "description": "contains letters", + "data": "ab:cd:ef", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/unknown.json b/src/test/suite/tests/draft7/optional/format/unknown.json new file mode 100644 index 000000000..12339ae57 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/unknown.json @@ -0,0 +1,43 @@ +[ + { + "description": "unknown format", + "schema": { "format": "unknown" }, + "tests": [ + { + "description": "unknown formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "unknown formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "unknown formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "unknown formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "unknown formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "unknown formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "unknown formats ignore strings", + "data": "string", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/uri-reference.json b/src/test/suite/tests/draft7/optional/format/uri-reference.json new file mode 100644 index 000000000..7cdf228d8 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/uri-reference.json @@ -0,0 +1,73 @@ +[ + { + "description": "validation of URI References", + "schema": { "format": "uri-reference" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URI", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid relative URI Reference", + "data": "/abc", + "valid": true + }, + { + "description": "an invalid URI Reference", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "a valid URI Reference", + "data": "abc", + "valid": true + }, + { + "description": "a valid URI fragment", + "data": "#fragment", + "valid": true + }, + { + "description": "an invalid URI fragment", + "data": "#frag\\ment", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/uri-template.json b/src/test/suite/tests/draft7/optional/format/uri-template.json new file mode 100644 index 000000000..df355c55a --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/uri-template.json @@ -0,0 +1,58 @@ +[ + { + "description": "format: uri-template", + "schema": { "format": "uri-template" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", + "valid": true + }, + { + "description": "an invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false + }, + { + "description": "a valid uri-template without variables", + "data": "http://example.com/dictionary", + "valid": true + }, + { + "description": "a valid relative uri-template", + "data": "dictionary/{term:1}/{term}", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/format/uri.json b/src/test/suite/tests/draft7/optional/format/uri.json new file mode 100644 index 000000000..4b48d4060 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/format/uri.json @@ -0,0 +1,138 @@ +[ + { + "description": "validation of URIs", + "schema": { "format": "uri" }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "a valid URL with anchor tag", + "data": "http://foo.bar/?baz=qux#quux", + "valid": true + }, + { + "description": "a valid URL with anchor tag and parentheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, + { + "description": "an invalid protocol-relative URI Reference", + "data": "//foo.bar/?baz=qux#quux", + "valid": false + }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, + { + "description": "an invalid URI", + "data": "\\\\WINDOWS\\fileshare", + "valid": false + }, + { + "description": "an invalid URI though valid URI reference", + "data": "abc", + "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/id.json b/src/test/suite/tests/draft7/optional/id.json new file mode 100644 index 000000000..6be81b8da --- /dev/null +++ b/src/test/suite/tests/draft7/optional/id.json @@ -0,0 +1,114 @@ +[ + { + "description": "id inside an enum is not a real identifier", + "comment": "the implementation must not be confused by an id buried in the enum", + "schema": { + "definitions": { + "id_in_enum": { + "enum": [ + { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + } + ] + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "string" + }, + "zzz_id_in_const": { + "const": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + } + } + }, + "anyOf": [ + { "$ref": "#/definitions/id_in_enum" }, + { "$ref": "https://localhost:1234/id/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "exact match to enum, and type matches", + "data": { + "$id": "https://localhost:1234/id/my_identifier.json", + "type": "null" + }, + "valid": true + }, + { + "description": "match $ref to id", + "data": "a string to match #/definitions/id_in_enum", + "valid": true + }, + { + "description": "no match on enum or $ref to id", + "data": 1, + "valid": false + } + ] + }, + { + "description": "non-schema object containing a plain-name $id property", + "schema": { + "definitions": { + "const_not_anchor": { + "const": { + "$id": "#not_a_real_anchor" + } + } + }, + "if": { + "const": "skip not_a_real_anchor" + }, + "then": true, + "else" : { + "$ref": "#/definitions/const_not_anchor" + } + }, + "tests": [ + { + "description": "skip traversing definition for a valid result", + "data": "skip not_a_real_anchor", + "valid": true + }, + { + "description": "const at const_not_anchor does not match", + "data": 1, + "valid": false + } + ] + }, + { + "description": "non-schema object containing an $id property", + "schema": { + "definitions": { + "const_not_id": { + "const": { + "$id": "not_a_real_id" + } + } + }, + "if": { + "const": "skip not_a_real_id" + }, + "then": true, + "else" : { + "$ref": "#/definitions/const_not_id" + } + }, + "tests": [ + { + "description": "skip traversing definition for a valid result", + "data": "skip not_a_real_id", + "valid": true + }, + { + "description": "const at const_not_id does not match", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/non-bmp-regex.json b/src/test/suite/tests/draft7/optional/non-bmp-regex.json new file mode 100644 index 000000000..dd67af2b2 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/non-bmp-regex.json @@ -0,0 +1,82 @@ +[ + { + "description": "Proper UTF-16 surrogate pair handling: pattern", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { "pattern": "^🐲*$" }, + "tests": [ + { + "description": "matches empty", + "data": "", + "valid": true + }, + { + "description": "matches single", + "data": "🐲", + "valid": true + }, + { + "description": "matches two", + "data": "🐲🐲", + "valid": true + }, + { + "description": "doesn't match one", + "data": "🐉", + "valid": false + }, + { + "description": "doesn't match two", + "data": "🐉🐉", + "valid": false + }, + { + "description": "doesn't match one ASCII", + "data": "D", + "valid": false + }, + { + "description": "doesn't match two ASCII", + "data": "DD", + "valid": false + } + ] + }, + { + "description": "Proper UTF-16 surrogate pair handling: patternProperties", + "comment": "Optional because .Net doesn't correctly handle 32-bit Unicode characters", + "schema": { + "patternProperties": { + "^🐲*$": { + "type": "integer" + } + } + }, + "tests": [ + { + "description": "matches empty", + "data": { "": 1 }, + "valid": true + }, + { + "description": "matches single", + "data": { "🐲": 1 }, + "valid": true + }, + { + "description": "matches two", + "data": { "🐲🐲": 1 }, + "valid": true + }, + { + "description": "doesn't match one", + "data": { "🐲": "hello" }, + "valid": false + }, + { + "description": "doesn't match two", + "data": { "🐲🐲": "hello" }, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/optional/unknownKeyword.json b/src/test/suite/tests/draft7/optional/unknownKeyword.json new file mode 100644 index 000000000..1f58d97e3 --- /dev/null +++ b/src/test/suite/tests/draft7/optional/unknownKeyword.json @@ -0,0 +1,56 @@ +[ + { + "description": "$id inside an unknown keyword is not a real identifier", + "comment": "the implementation must not be confused by an $id in locations we do not know how to parse", + "schema": { + "definitions": { + "id_in_unknown0": { + "not": { + "array_of_schemas": [ + { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "null" + } + ] + } + }, + "real_id_in_schema": { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "string" + }, + "id_in_unknown1": { + "not": { + "object_of_schemas": { + "foo": { + "$id": "https://localhost:1234/unknownKeyword/my_identifier.json", + "type": "integer" + } + } + } + } + }, + "anyOf": [ + { "$ref": "#/definitions/id_in_unknown0" }, + { "$ref": "#/definitions/id_in_unknown1" }, + { "$ref": "https://localhost:1234/unknownKeyword/my_identifier.json" } + ] + }, + "tests": [ + { + "description": "type matches second anyOf, which has a real schema in it", + "data": "a string", + "valid": true + }, + { + "description": "type matches non-schema in first anyOf", + "data": null, + "valid": false + }, + { + "description": "type matches non-schema in third anyOf", + "data": 1, + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/pattern.json b/src/test/suite/tests/draft7/pattern.json new file mode 100644 index 000000000..92db0f971 --- /dev/null +++ b/src/test/suite/tests/draft7/pattern.json @@ -0,0 +1,59 @@ +[ + { + "description": "pattern validation", + "schema": {"pattern": "^a*$"}, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": {"pattern": "a+"}, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/patternProperties.json b/src/test/suite/tests/draft7/patternProperties.json new file mode 100644 index 000000000..c276e6479 --- /dev/null +++ b/src/test/suite/tests/draft7/patternProperties.json @@ -0,0 +1,171 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/properties.json b/src/test/suite/tests/draft7/properties.json new file mode 100644 index 000000000..5b971ca0e --- /dev/null +++ b/src/test/suite/tests/draft7/properties.json @@ -0,0 +1,236 @@ +[ + { + "description": "object properties validation", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/propertyNames.json b/src/test/suite/tests/draft7/propertyNames.json new file mode 100644 index 000000000..7c7b80006 --- /dev/null +++ b/src/test/suite/tests/draft7/propertyNames.json @@ -0,0 +1,154 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames validation with pattern", + "schema": { + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": {"propertyNames": true}, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": {"propertyNames": false}, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with const", + "schema": {"propertyNames": {"const": "foo"}}, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with enum", + "schema": {"propertyNames": {"enum": ["foo", "bar"]}}, + "tests": [ + { + "description": "object with property foo is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property foo and bar is valid", + "data": {"foo": 1, "bar": 1}, + "valid": true + }, + { + "description": "object with any other property is invalid", + "data": {"baz": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/ref.json b/src/test/suite/tests/draft7/ref.json new file mode 100644 index 000000000..82e1e1672 --- /dev/null +++ b/src/test/suite/tests/draft7/ref.json @@ -0,0 +1,1043 @@ +[ + { + "description": "root pointer ref", + "schema": { + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "items": [ + {"type": "integer"}, + {"$ref": "#/items/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "definitions": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/definitions/tilde~0field"}, + "slash": {"$ref": "#/definitions/slash~1field"}, + "percent": {"$ref": "#/definitions/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "definitions": { + "a": {"type": "integer"}, + "b": {"$ref": "#/definitions/a"}, + "c": {"$ref": "#/definitions/b"} + }, + "allOf": [{ "$ref": "#/definitions/c" }] + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref overrides any sibling keywords", + "schema": { + "definitions": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/definitions/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems ignored", + "data": { "foo": [ 1, 2, 3] }, + "valid": true + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "$ref prevents a sibling $id from changing the base uri", + "schema": { + "$id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "$id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "$id": "foo.json", + "type": "number" + } + }, + "allOf": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "$id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + }, + "tests": [ + { + "description": "$ref resolves to /definitions/base_foo, data does not validate", + "data": "a", + "valid": false + }, + { + "description": "$ref resolves to /definitions/base_foo, data validates", + "data": 1, + "valid": true + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": {"$ref": "http://json-schema.org/draft-07/schema#"}, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "properties": { + "$ref": {"$ref": "#/definitions/is-string"} + }, + "definitions": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "allOf": [{ "$ref": "#/definitions/bool" }], + "definitions": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "allOf": [{ "$ref": "#/definitions/bool" }], + "definitions": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$id": "http://localhost:1234/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "definitions": { + "node": { + "$id": "http://localhost:1234/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "properties": { + "foo\"bar": {"$ref": "#/definitions/foo%22bar"} + }, + "definitions": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "Location-independent identifier", + "schema": { + "allOf": [{ + "$ref": "#foo" + }], + "definitions": { + "A": { + "$id": "#foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Reference an anchor with a non-relative URI", + "schema": { + "$id": "https://example.com/schema-with-anchor", + "allOf": [{ + "$ref": "https://example.com/schema-with-anchor#foo" + }], + "definitions": { + "A": { + "$id": "#foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$id": "http://localhost:1234/root", + "allOf": [{ + "$ref": "http://localhost:1234/nested.json#foo" + }], + "definitions": { + "A": { + "$id": "nested.json", + "definitions": { + "B": { + "$id": "#foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "definitions": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/definitions/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/definitions/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] + } + }, + "allOf": [ { "$ref": "schema-relative-uri-defs2.json" } ] + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] + } + }, + "allOf": [ { "$ref": "schema-refs-absolute-uris-defs2.json" } ] + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "$id must be resolved against nearest parent, not just immediate parent", + "schema": { + "$id": "http://example.com/a.json", + "definitions": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "definitions": { + "y": { + "$id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$comment": "RFC 8141 §2.2", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$comment": "RFC 8141 §2.3.1", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$comment": "RFC 8141 §2.3.2", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/definitions/bar"} + }, + "definitions": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "definitions": { + "bar": { + "$id": "#something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "ref to if", + "schema": { + "allOf": [ + {"$ref": "http://example.com/ref/if"}, + { + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + } + ] + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to then", + "schema": { + "allOf": [ + {"$ref": "http://example.com/ref/then"}, + { + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + } + ] + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to else", + "schema": { + "allOf": [ + {"$ref": "http://example.com/ref/else"}, + { + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + } + ] + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$id": "http://example.com/ref/absref.json", + "definitions": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "allOf": [ + { "$ref": "/absref/foobar.json" } + ] + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$id": "file:///folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$id": "file:///c:/folder/file.json", + "definitions": { + "foo": { + "type": "number" + } + }, + "allOf": [ + { + "$ref": "#/definitions/foo" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "definitions": { + "": { + "definitions": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions//definitions/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/refRemote.json b/src/test/suite/tests/draft7/refRemote.json new file mode 100644 index 000000000..450787af6 --- /dev/null +++ b/src/test/suite/tests/draft7/refRemote.json @@ -0,0 +1,257 @@ +[ + { + "description": "remote ref", + "schema": {"$ref": "http://localhost:1234/integer.json"}, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": {"$ref": "http://localhost:1234/draft7/subSchemas.json#/definitions/integer"}, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$ref": "http://localhost:1234/draft7/subSchemas.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$id": "http://localhost:1234/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$id": "http://localhost:1234/scope_change_defs1.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz"} + }, + "definitions": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$id": "http://localhost:1234/scope_change_defs2.json", + "type" : "object", + "properties": { + "list": {"$ref": "#/definitions/baz/definitions/bar"} + }, + "definitions": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "definitions": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$id": "http://localhost:1234/object", + "type": "object", + "properties": { + "name": {"$ref": "draft7/name.json#/definitions/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to definitions", + "schema": { + "$id": "http://localhost:1234/schema-remote-ref-ref-defs1.json", + "allOf": [ + { "$ref": "draft7/ref-and-definitions.json" } + ] + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$ref": "http://localhost:1234/draft7/locationIndependentIdentifier.json#/definitions/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$id": "http://localhost:1234/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds location-independent $id", + "schema": { + "$ref": "http://localhost:1234/draft7/detached-ref.json#/definitions/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/required.json b/src/test/suite/tests/draft7/required.json new file mode 100644 index 000000000..8d8087afd --- /dev/null +++ b/src/test/suite/tests/draft7/required.json @@ -0,0 +1,151 @@ +[ + { + "description": "required validation", + "schema": { + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { "required": ["__proto__", "toString", "constructor"] }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/src/test/suite/tests/draft7/type.json b/src/test/suite/tests/draft7/type.json new file mode 100644 index 000000000..830464702 --- /dev/null +++ b/src/test/suite/tests/draft7/type.json @@ -0,0 +1,474 @@ +[ + { + "description": "integer type matches integers", + "schema": {"type": "integer"}, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": {"type": "number"}, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": {"type": "string"}, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": {"type": "object"}, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": {"type": "array"}, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": {"type": "boolean"}, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": {"type": "null"}, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": {"type": ["integer", "string"]}, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/draft7/uniqueItems.json b/src/test/suite/tests/draft7/uniqueItems.json new file mode 100644 index 000000000..d2730c60c --- /dev/null +++ b/src/test/suite/tests/draft7/uniqueItems.json @@ -0,0 +1,409 @@ +[ + { + "description": "uniqueItems validation", + "schema": {"uniqueItems": true}, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { "uniqueItems": false }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "items": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "additionalItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/src/test/suite/tests/latest b/src/test/suite/tests/latest new file mode 100644 index 000000000..9a4784dd7 --- /dev/null +++ b/src/test/suite/tests/latest @@ -0,0 +1 @@ +draft2020-12 \ No newline at end of file diff --git a/src/test/suite/tox.ini b/src/test/suite/tox.ini new file mode 100644 index 000000000..dcc0dce6d --- /dev/null +++ b/src/test/suite/tox.ini @@ -0,0 +1,9 @@ +[tox] +minversion = 1.6 +envlist = sanity +skipsdist = True + +[testenv:sanity] +# used just for validating the structure of the test case files themselves +deps = jsonschema==4.18.0a4 +commands = {envpython} bin/jsonschema_suite check