summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrassb@google.com <rassb@google.com>2024-04-10 13:08:23 -0700
committerrassb@google.com <rassb@google.com>2024-04-10 13:08:23 -0700
commitb7fbe2a0691862ec40646331047e9583896786fb (patch)
treebc8f428d44fb98a026bfb0757e88a027ada4909e
parent75ab7da657a943905eefe6170683bb834135e8e4 (diff)
parent45eda76c38a683cbf2b68b39e21a20fd149db29c (diff)
downloadjson-schema-validator-b7fbe2a0691862ec40646331047e9583896786fb.tar.gz
Merge remote-tracking branch 'origin/upstream'main
-rw-r--r--.github/workflows/ci.yml35
-rw-r--r--.gitignore21
-rw-r--r--Android.bp43
-rw-r--r--CHANGELOG.md1259
-rw-r--r--LICENSE202
-rw-r--r--METADATA15
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE13
-rw-r--r--OWNERS1
-rw-r--r--README.md572
-rw-r--r--doc/collector-context.md107
-rw-r--r--doc/compatibility.md170
-rw-r--r--doc/config.md69
-rw-r--r--doc/cust-msg.md113
-rw-r--r--doc/custom-meta-schema.md206
-rw-r--r--doc/duration.md31
-rw-r--r--doc/ecma-262.md29
-rw-r--r--doc/metaschema-validation.md20
-rw-r--r--doc/multiple-language.md70
-rw-r--r--doc/openapi-discriminators.md190
-rw-r--r--doc/quickstart.md65
-rw-r--r--doc/schema-retrieval.md158
-rw-r--r--doc/specversion.md213
-rw-r--r--doc/upgrading.md217
-rw-r--r--doc/validators.md106
-rw-r--r--doc/walk_flow.pngbin0 -> 38714 bytes
-rw-r--r--doc/walkers.md291
-rw-r--r--doc/yaml-line-numbers.md309
-rw-r--r--doc/yaml.md25
-rw-r--r--pom.xml430
-rw-r--r--src/main/java/com/networknt/org/apache/commons/validator/routines/DomainValidator.java2319
-rw-r--r--src/main/java/com/networknt/org/apache/commons/validator/routines/EmailValidator.java233
-rw-r--r--src/main/java/com/networknt/org/apache/commons/validator/routines/InetAddressValidator.java221
-rw-r--r--src/main/java/com/networknt/org/apache/commons/validator/routines/RegexValidator.java251
-rw-r--r--src/main/java/com/networknt/schema/AbsoluteIri.java198
-rw-r--r--src/main/java/com/networknt/schema/AbstractCollector.java29
-rw-r--r--src/main/java/com/networknt/schema/AbstractJsonValidator.java111
-rw-r--r--src/main/java/com/networknt/schema/AbstractKeyword.java66
-rw-r--r--src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java224
-rw-r--r--src/main/java/com/networknt/schema/AllOfValidator.java119
-rw-r--r--src/main/java/com/networknt/schema/AnnotationKeyword.java68
-rw-r--r--src/main/java/com/networknt/schema/AnyOfValidator.java211
-rw-r--r--src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java43
-rw-r--r--src/main/java/com/networknt/schema/BaseJsonValidator.java405
-rw-r--r--src/main/java/com/networknt/schema/CachedSupplier.java41
-rw-r--r--src/main/java/com/networknt/schema/Collector.java46
-rw-r--r--src/main/java/com/networknt/schema/CollectorContext.java133
-rw-r--r--src/main/java/com/networknt/schema/ConstValidator.java52
-rw-r--r--src/main/java/com/networknt/schema/ContainsValidator.java205
-rw-r--r--src/main/java/com/networknt/schema/ContentEncodingValidator.java90
-rw-r--r--src/main/java/com/networknt/schema/ContentMediaTypeValidator.java114
-rw-r--r--src/main/java/com/networknt/schema/CustomErrorMessageType.java35
-rw-r--r--src/main/java/com/networknt/schema/DefaultJsonMetaSchemaFactory.java79
-rw-r--r--src/main/java/com/networknt/schema/DependenciesValidator.java104
-rw-r--r--src/main/java/com/networknt/schema/DependentRequired.java72
-rw-r--r--src/main/java/com/networknt/schema/DependentSchemas.java83
-rw-r--r--src/main/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactory.java37
-rw-r--r--src/main/java/com/networknt/schema/DisallowUnknownKeywordFactory.java42
-rw-r--r--src/main/java/com/networknt/schema/DiscriminatorContext.java58
-rw-r--r--src/main/java/com/networknt/schema/DiscriminatorValidator.java84
-rw-r--r--src/main/java/com/networknt/schema/DynamicRefValidator.java151
-rw-r--r--src/main/java/com/networknt/schema/EnumValidator.java165
-rw-r--r--src/main/java/com/networknt/schema/ErrorMessageType.java38
-rw-r--r--src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java116
-rw-r--r--src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java124
-rw-r--r--src/main/java/com/networknt/schema/ExecutionConfig.java183
-rw-r--r--src/main/java/com/networknt/schema/ExecutionContext.java195
-rw-r--r--src/main/java/com/networknt/schema/ExecutionContextCustomizer.java33
-rw-r--r--src/main/java/com/networknt/schema/FailFastAssertionException.java77
-rw-r--r--src/main/java/com/networknt/schema/FalseValidator.java45
-rw-r--r--src/main/java/com/networknt/schema/Format.java158
-rw-r--r--src/main/java/com/networknt/schema/FormatKeyword.java66
-rw-r--r--src/main/java/com/networknt/schema/FormatValidator.java146
-rw-r--r--src/main/java/com/networknt/schema/Formats.java89
-rw-r--r--src/main/java/com/networknt/schema/IfValidator.java122
-rw-r--r--src/main/java/com/networknt/schema/InputFormat.java31
-rw-r--r--src/main/java/com/networknt/schema/InvalidSchemaException.java35
-rw-r--r--src/main/java/com/networknt/schema/InvalidSchemaRefException.java32
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator.java304
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator202012.java201
-rw-r--r--src/main/java/com/networknt/schema/JsonMetaSchema.java533
-rw-r--r--src/main/java/com/networknt/schema/JsonMetaSchemaFactory.java32
-rw-r--r--src/main/java/com/networknt/schema/JsonNodePath.java258
-rw-r--r--src/main/java/com/networknt/schema/JsonSchema.java1136
-rw-r--r--src/main/java/com/networknt/schema/JsonSchemaException.java53
-rw-r--r--src/main/java/com/networknt/schema/JsonSchemaFactory.java692
-rw-r--r--src/main/java/com/networknt/schema/JsonSchemaIdValidator.java89
-rw-r--r--src/main/java/com/networknt/schema/JsonSchemaRef.java34
-rw-r--r--src/main/java/com/networknt/schema/JsonSchemaVersion.java28
-rw-r--r--src/main/java/com/networknt/schema/JsonType.java42
-rw-r--r--src/main/java/com/networknt/schema/JsonValidator.java91
-rw-r--r--src/main/java/com/networknt/schema/Keyword.java46
-rw-r--r--src/main/java/com/networknt/schema/KeywordFactory.java31
-rw-r--r--src/main/java/com/networknt/schema/MaxItemsValidator.java62
-rw-r--r--src/main/java/com/networknt/schema/MaxLengthValidator.java58
-rw-r--r--src/main/java/com/networknt/schema/MaxPropertiesValidator.java58
-rw-r--r--src/main/java/com/networknt/schema/MaximumValidator.java126
-rw-r--r--src/main/java/com/networknt/schema/MessageSourceValidationMessage.java99
-rw-r--r--src/main/java/com/networknt/schema/MinItemsValidator.java62
-rw-r--r--src/main/java/com/networknt/schema/MinLengthValidator.java59
-rw-r--r--src/main/java/com/networknt/schema/MinMaxContainsValidator.java92
-rw-r--r--src/main/java/com/networknt/schema/MinPropertiesValidator.java58
-rw-r--r--src/main/java/com/networknt/schema/MinimumValidator.java134
-rw-r--r--src/main/java/com/networknt/schema/MultipleOfValidator.java97
-rw-r--r--src/main/java/com/networknt/schema/NonValidationKeyword.java68
-rw-r--r--src/main/java/com/networknt/schema/NotAllowedValidator.java65
-rw-r--r--src/main/java/com/networknt/schema/NotValidator.java84
-rw-r--r--src/main/java/com/networknt/schema/OneOfValidator.java253
-rw-r--r--src/main/java/com/networknt/schema/OutputFormat.java160
-rw-r--r--src/main/java/com/networknt/schema/PathType.java264
-rw-r--r--src/main/java/com/networknt/schema/PatternPropertiesValidator.java113
-rw-r--r--src/main/java/com/networknt/schema/PatternValidator.java74
-rw-r--r--src/main/java/com/networknt/schema/PrefixItemsValidator.java187
-rw-r--r--src/main/java/com/networknt/schema/PropertiesValidator.java248
-rw-r--r--src/main/java/com/networknt/schema/PropertyNamesValidator.java68
-rw-r--r--src/main/java/com/networknt/schema/ReadOnlyValidator.java53
-rw-r--r--src/main/java/com/networknt/schema/RecursiveRefValidator.java148
-rw-r--r--src/main/java/com/networknt/schema/RefValidator.java233
-rw-r--r--src/main/java/com/networknt/schema/RequiredValidator.java72
-rw-r--r--src/main/java/com/networknt/schema/SchemaId.java46
-rw-r--r--src/main/java/com/networknt/schema/SchemaLocation.java396
-rw-r--r--src/main/java/com/networknt/schema/SchemaValidatorsConfig.java626
-rw-r--r--src/main/java/com/networknt/schema/SpecVersion.java55
-rw-r--r--src/main/java/com/networknt/schema/SpecVersionDetector.java118
-rw-r--r--src/main/java/com/networknt/schema/ThresholdMixin.java25
-rw-r--r--src/main/java/com/networknt/schema/TrueValidator.java40
-rw-r--r--src/main/java/com/networknt/schema/TypeFactory.java93
-rw-r--r--src/main/java/com/networknt/schema/TypeValidator.java70
-rw-r--r--src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java211
-rw-r--r--src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java141
-rw-r--r--src/main/java/com/networknt/schema/UnionTypeValidator.java112
-rw-r--r--src/main/java/com/networknt/schema/UniqueItemsValidator.java61
-rw-r--r--src/main/java/com/networknt/schema/UnknownKeywordFactory.java51
-rw-r--r--src/main/java/com/networknt/schema/ValidationContext.java112
-rw-r--r--src/main/java/com/networknt/schema/ValidationMessage.java440
-rw-r--r--src/main/java/com/networknt/schema/ValidationMessageHandler.java151
-rw-r--r--src/main/java/com/networknt/schema/ValidationResult.java43
-rw-r--r--src/main/java/com/networknt/schema/ValidatorState.java91
-rw-r--r--src/main/java/com/networknt/schema/ValidatorTypeCode.java190
-rw-r--r--src/main/java/com/networknt/schema/Version201909.java47
-rw-r--r--src/main/java/com/networknt/schema/Version202012.java48
-rw-r--r--src/main/java/com/networknt/schema/Version4.java38
-rw-r--r--src/main/java/com/networknt/schema/Version6.java39
-rw-r--r--src/main/java/com/networknt/schema/Version7.java41
-rw-r--r--src/main/java/com/networknt/schema/Vocabularies.java57
-rw-r--r--src/main/java/com/networknt/schema/Vocabulary.java155
-rw-r--r--src/main/java/com/networknt/schema/VocabularyFactory.java30
-rw-r--r--src/main/java/com/networknt/schema/WriteOnlyValidator.java37
-rw-r--r--src/main/java/com/networknt/schema/annotation/JsonNodeAnnotation.java164
-rw-r--r--src/main/java/com/networknt/schema/annotation/JsonNodeAnnotationPredicate.java156
-rw-r--r--src/main/java/com/networknt/schema/annotation/JsonNodeAnnotations.java102
-rw-r--r--src/main/java/com/networknt/schema/format/AbstractFormat.java42
-rw-r--r--src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java31
-rw-r--r--src/main/java/com/networknt/schema/format/BaseFormat.java43
-rw-r--r--src/main/java/com/networknt/schema/format/BaseFormatJsonValidator.java55
-rw-r--r--src/main/java/com/networknt/schema/format/DateFormat.java33
-rw-r--r--src/main/java/com/networknt/schema/format/DateTimeFormat.java82
-rw-r--r--src/main/java/com/networknt/schema/format/DurationFormat.java47
-rw-r--r--src/main/java/com/networknt/schema/format/EmailFormat.java51
-rw-r--r--src/main/java/com/networknt/schema/format/IPv6AwareEmailValidator.java33
-rw-r--r--src/main/java/com/networknt/schema/format/IPv6Format.java37
-rw-r--r--src/main/java/com/networknt/schema/format/IdnEmailFormat.java35
-rw-r--r--src/main/java/com/networknt/schema/format/IdnHostnameFormat.java26
-rw-r--r--src/main/java/com/networknt/schema/format/IriFormat.java40
-rw-r--r--src/main/java/com/networknt/schema/format/IriReferenceFormat.java37
-rw-r--r--src/main/java/com/networknt/schema/format/PatternFormat.java88
-rw-r--r--src/main/java/com/networknt/schema/format/RegexFormat.java47
-rw-r--r--src/main/java/com/networknt/schema/format/TimeFormat.java92
-rw-r--r--src/main/java/com/networknt/schema/format/UriFormat.java37
-rw-r--r--src/main/java/com/networknt/schema/format/UriReferenceFormat.java34
-rw-r--r--src/main/java/com/networknt/schema/i18n/DefaultMessageSource.java42
-rw-r--r--src/main/java/com/networknt/schema/i18n/Locales.java102
-rw-r--r--src/main/java/com/networknt/schema/i18n/MessageFormatter.java30
-rw-r--r--src/main/java/com/networknt/schema/i18n/MessageSource.java61
-rw-r--r--src/main/java/com/networknt/schema/i18n/ResourceBundleMessageSource.java137
-rw-r--r--src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java174
-rw-r--r--src/main/java/com/networknt/schema/output/ListOutputUnitFormatter.java98
-rw-r--r--src/main/java/com/networknt/schema/output/OutputFlag.java47
-rw-r--r--src/main/java/com/networknt/schema/output/OutputUnit.java151
-rw-r--r--src/main/java/com/networknt/schema/output/OutputUnitData.java133
-rw-r--r--src/main/java/com/networknt/schema/output/OutputUnitKey.java87
-rw-r--r--src/main/java/com/networknt/schema/regex/JDKRegularExpression.java16
-rw-r--r--src/main/java/com/networknt/schema/regex/JoniRegularExpression.java33
-rw-r--r--src/main/java/com/networknt/schema/regex/RegularExpression.java16
-rw-r--r--src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java61
-rw-r--r--src/main/java/com/networknt/schema/resource/DefaultSchemaLoader.java73
-rw-r--r--src/main/java/com/networknt/schema/resource/InputStreamSource.java33
-rw-r--r--src/main/java/com/networknt/schema/resource/MapSchemaLoader.java68
-rw-r--r--src/main/java/com/networknt/schema/resource/MapSchemaMapper.java47
-rw-r--r--src/main/java/com/networknt/schema/resource/MetaSchemaMapper.java45
-rw-r--r--src/main/java/com/networknt/schema/resource/PrefixSchemaMapper.java25
-rw-r--r--src/main/java/com/networknt/schema/resource/SchemaLoader.java32
-rw-r--r--src/main/java/com/networknt/schema/resource/SchemaLoaders.java135
-rw-r--r--src/main/java/com/networknt/schema/resource/SchemaMapper.java33
-rw-r--r--src/main/java/com/networknt/schema/resource/SchemaMappers.java144
-rw-r--r--src/main/java/com/networknt/schema/resource/UriSchemaLoader.java110
-rw-r--r--src/main/java/com/networknt/schema/result/JsonNodeResult.java82
-rw-r--r--src/main/java/com/networknt/schema/result/JsonNodeResults.java57
-rw-r--r--src/main/java/com/networknt/schema/serialization/JsonMapperFactory.java41
-rw-r--r--src/main/java/com/networknt/schema/serialization/YamlMapperFactory.java41
-rw-r--r--src/main/java/com/networknt/schema/utils/AbsoluteIris.java172
-rw-r--r--src/main/java/com/networknt/schema/utils/CachingSupplier.java43
-rw-r--r--src/main/java/com/networknt/schema/utils/Classes.java37
-rw-r--r--src/main/java/com/networknt/schema/utils/JsonNodeUtil.java180
-rw-r--r--src/main/java/com/networknt/schema/utils/JsonNodes.java82
-rw-r--r--src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java49
-rw-r--r--src/main/java/com/networknt/schema/utils/RFC5892.java400
-rw-r--r--src/main/java/com/networknt/schema/utils/SetView.java204
-rw-r--r--src/main/java/com/networknt/schema/utils/StringChecker.java104
-rw-r--r--src/main/java/com/networknt/schema/utils/StringUtils.java32
-rw-r--r--src/main/java/com/networknt/schema/utils/UCDLoader.java43
-rw-r--r--src/main/java/com/networknt/schema/utils/UnicodeDatabase.java104
-rw-r--r--src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java46
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java37
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java48
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java36
-rw-r--r--src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java17
-rw-r--r--src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java33
-rw-r--r--src/main/java/com/networknt/schema/walk/WalkEvent.java152
-rw-r--r--src/main/java/com/networknt/schema/walk/WalkFlow.java28
-rw-r--r--src/main/java/com/networknt/schema/walk/WalkListenerRunner.java20
-rw-r--r--src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/native-image.properties2
-rw-r--r--src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/reflect-config.json2
-rw-r--r--src/main/resources/META-INF/native-image/com.networknt/json-schema-validator/resource-config.json24
-rw-r--r--src/main/resources/draft-04/schema149
-rw-r--r--src/main/resources/draft-06/schema155
-rw-r--r--src/main/resources/draft-07/schema172
-rw-r--r--src/main/resources/draft/2019-09/meta/applicator56
-rw-r--r--src/main/resources/draft/2019-09/meta/content17
-rw-r--r--src/main/resources/draft/2019-09/meta/core57
-rw-r--r--src/main/resources/draft/2019-09/meta/format14
-rw-r--r--src/main/resources/draft/2019-09/meta/meta-data37
-rw-r--r--src/main/resources/draft/2019-09/meta/validation98
-rw-r--r--src/main/resources/draft/2019-09/schema42
-rw-r--r--src/main/resources/draft/2020-12/meta/applicator48
-rw-r--r--src/main/resources/draft/2020-12/meta/content17
-rw-r--r--src/main/resources/draft/2020-12/meta/core51
-rw-r--r--src/main/resources/draft/2020-12/meta/format-annotation14
-rw-r--r--src/main/resources/draft/2020-12/meta/meta-data37
-rw-r--r--src/main/resources/draft/2020-12/meta/unevaluated15
-rw-r--r--src/main/resources/draft/2020-12/meta/validation98
-rw-r--r--src/main/resources/draft/2020-12/schema58
-rw-r--r--src/main/resources/jsv-messages.properties70
-rw-r--r--src/main/resources/jsv-messages_ar.properties70
-rw-r--r--src/main/resources/jsv-messages_cs.properties70
-rw-r--r--src/main/resources/jsv-messages_da.properties70
-rw-r--r--src/main/resources/jsv-messages_de.properties70
-rw-r--r--src/main/resources/jsv-messages_en.properties0
-rw-r--r--src/main/resources/jsv-messages_fa.properties70
-rw-r--r--src/main/resources/jsv-messages_fi.properties70
-rw-r--r--src/main/resources/jsv-messages_fr.properties70
-rw-r--r--src/main/resources/jsv-messages_he.properties70
-rw-r--r--src/main/resources/jsv-messages_hr.properties70
-rw-r--r--src/main/resources/jsv-messages_hu.properties70
-rw-r--r--src/main/resources/jsv-messages_it.properties70
-rw-r--r--src/main/resources/jsv-messages_ja.properties70
-rw-r--r--src/main/resources/jsv-messages_ko.properties70
-rw-r--r--src/main/resources/jsv-messages_nb.properties70
-rw-r--r--src/main/resources/jsv-messages_nl.properties70
-rw-r--r--src/main/resources/jsv-messages_pl.properties70
-rw-r--r--src/main/resources/jsv-messages_pt.properties70
-rw-r--r--src/main/resources/jsv-messages_ro.properties70
-rw-r--r--src/main/resources/jsv-messages_ru.properties70
-rw-r--r--src/main/resources/jsv-messages_sk.properties70
-rw-r--r--src/main/resources/jsv-messages_sv.properties70
-rw-r--r--src/main/resources/jsv-messages_th.properties70
-rw-r--r--src/main/resources/jsv-messages_tr.properties70
-rw-r--r--src/main/resources/jsv-messages_uk.properties70
-rw-r--r--src/main/resources/jsv-messages_vi.properties70
-rw-r--r--src/main/resources/jsv-messages_zh_CN.properties70
-rw-r--r--src/main/resources/jsv-messages_zh_TW.properties70
-rw-r--r--src/main/resources/ucd/RFC5892-appendix-B.txt2321
-rw-r--r--src/main/resources/ucd/extracted/DerivedJoiningType.txt573
-rw-r--r--src/test/java/com/networknt/schema/AbsoluteIriTest.java122
-rw-r--r--src/test/java/com/networknt/schema/AbstractJsonSchemaTest.java70
-rw-r--r--src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java254
-rw-r--r--src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java104
-rw-r--r--src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java88
-rw-r--r--src/test/java/com/networknt/schema/CollectorContextTest.java383
-rw-r--r--src/test/java/com/networknt/schema/ContentSchemaValidatorTest.java72
-rw-r--r--src/test/java/com/networknt/schema/CustomMessageTest.java26
-rw-r--r--src/test/java/com/networknt/schema/CustomMetaSchemaTest.java140
-rw-r--r--src/test/java/com/networknt/schema/CustomUriTest.java46
-rw-r--r--src/test/java/com/networknt/schema/CyclicDependencyTest.java41
-rw-r--r--src/test/java/com/networknt/schema/DateTimeDSTTest.java34
-rw-r--r--src/test/java/com/networknt/schema/DefaultJsonSchemaIdValidatorTest.java70
-rw-r--r--src/test/java/com/networknt/schema/DependentRequiredTest.java66
-rw-r--r--src/test/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactoryTest.java60
-rw-r--r--src/test/java/com/networknt/schema/DisallowUnknownKeywordFactoryTest.java42
-rw-r--r--src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java790
-rw-r--r--src/test/java/com/networknt/schema/DurationFormatValidatorTest.java50
-rw-r--r--src/test/java/com/networknt/schema/ExampleTest.java76
-rw-r--r--src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java51
-rw-r--r--src/test/java/com/networknt/schema/FormatValidatorTest.java240
-rw-r--r--src/test/java/com/networknt/schema/HTTPServiceSupport.java87
-rw-r--r--src/test/java/com/networknt/schema/Issue255Test.java49
-rw-r--r--src/test/java/com/networknt/schema/Issue285Test.java132
-rw-r--r--src/test/java/com/networknt/schema/Issue295Test.java34
-rw-r--r--src/test/java/com/networknt/schema/Issue313Test.java54
-rw-r--r--src/test/java/com/networknt/schema/Issue314Test.java25
-rw-r--r--src/test/java/com/networknt/schema/Issue327Test.java34
-rw-r--r--src/test/java/com/networknt/schema/Issue342Test.java38
-rw-r--r--src/test/java/com/networknt/schema/Issue347Test.java23
-rw-r--r--src/test/java/com/networknt/schema/Issue366FailFastTest.java100
-rw-r--r--src/test/java/com/networknt/schema/Issue366FailSlowTest.java104
-rw-r--r--src/test/java/com/networknt/schema/Issue375Test.java61
-rw-r--r--src/test/java/com/networknt/schema/Issue383Test.java35
-rw-r--r--src/test/java/com/networknt/schema/Issue396Test.java45
-rw-r--r--src/test/java/com/networknt/schema/Issue404Test.java35
-rw-r--r--src/test/java/com/networknt/schema/Issue406Test.java43
-rw-r--r--src/test/java/com/networknt/schema/Issue426Test.java42
-rw-r--r--src/test/java/com/networknt/schema/Issue428Test.java90
-rw-r--r--src/test/java/com/networknt/schema/Issue451Test.java95
-rw-r--r--src/test/java/com/networknt/schema/Issue456Test.java48
-rw-r--r--src/test/java/com/networknt/schema/Issue461Test.java47
-rw-r--r--src/test/java/com/networknt/schema/Issue467Test.java92
-rw-r--r--src/test/java/com/networknt/schema/Issue471Test.java90
-rw-r--r--src/test/java/com/networknt/schema/Issue475Test.java125
-rw-r--r--src/test/java/com/networknt/schema/Issue493Test.java96
-rw-r--r--src/test/java/com/networknt/schema/Issue510Test.java13
-rw-r--r--src/test/java/com/networknt/schema/Issue518Test.java28
-rw-r--r--src/test/java/com/networknt/schema/Issue532Test.java16
-rw-r--r--src/test/java/com/networknt/schema/Issue550Test.java55
-rw-r--r--src/test/java/com/networknt/schema/Issue575Test.java129
-rw-r--r--src/test/java/com/networknt/schema/Issue604Test.java21
-rw-r--r--src/test/java/com/networknt/schema/Issue606Test.java34
-rw-r--r--src/test/java/com/networknt/schema/Issue619Test.java166
-rw-r--r--src/test/java/com/networknt/schema/Issue650Test.java83
-rw-r--r--src/test/java/com/networknt/schema/Issue662Test.java60
-rw-r--r--src/test/java/com/networknt/schema/Issue664Test.java45
-rw-r--r--src/test/java/com/networknt/schema/Issue665Test.java47
-rw-r--r--src/test/java/com/networknt/schema/Issue668Test.java35
-rw-r--r--src/test/java/com/networknt/schema/Issue686Test.java68
-rw-r--r--src/test/java/com/networknt/schema/Issue687Test.java134
-rw-r--r--src/test/java/com/networknt/schema/Issue724Test.java87
-rw-r--r--src/test/java/com/networknt/schema/Issue769ContainsTest.java39
-rw-r--r--src/test/java/com/networknt/schema/Issue784Test.java81
-rw-r--r--src/test/java/com/networknt/schema/Issue792.java40
-rw-r--r--src/test/java/com/networknt/schema/Issue824Test.java69
-rw-r--r--src/test/java/com/networknt/schema/Issue832Test.java64
-rw-r--r--src/test/java/com/networknt/schema/Issue857Test.java57
-rw-r--r--src/test/java/com/networknt/schema/Issue877Test.java43
-rw-r--r--src/test/java/com/networknt/schema/Issue898Test.java31
-rw-r--r--src/test/java/com/networknt/schema/Issue927Test.java135
-rw-r--r--src/test/java/com/networknt/schema/Issue928Test.java58
-rw-r--r--src/test/java/com/networknt/schema/Issue935Test.java31
-rw-r--r--src/test/java/com/networknt/schema/Issue936Test.java40
-rw-r--r--src/test/java/com/networknt/schema/Issue939Test.java57
-rw-r--r--src/test/java/com/networknt/schema/Issue940Test.java37
-rw-r--r--src/test/java/com/networknt/schema/Issue943Test.java84
-rw-r--r--src/test/java/com/networknt/schema/ItemsValidator202012Test.java58
-rw-r--r--src/test/java/com/networknt/schema/ItemsValidatorTest.java114
-rw-r--r--src/test/java/com/networknt/schema/JsonNodeAnnotationsTest.java38
-rw-r--r--src/test/java/com/networknt/schema/JsonNodePathTest.java137
-rw-r--r--src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java71
-rw-r--r--src/test/java/com/networknt/schema/JsonSchemaTestSuiteExtrasTest.java53
-rw-r--r--src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java90
-rw-r--r--src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java134
-rw-r--r--src/test/java/com/networknt/schema/JsonWalkTest.java222
-rw-r--r--src/test/java/com/networknt/schema/LocaleTest.java186
-rw-r--r--src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java83
-rw-r--r--src/test/java/com/networknt/schema/MaximumValidatorTest.java327
-rw-r--r--src/test/java/com/networknt/schema/MessageTest.java95
-rw-r--r--src/test/java/com/networknt/schema/MetaSchemaValidationTest.java55
-rw-r--r--src/test/java/com/networknt/schema/MinimumValidatorTest.java299
-rw-r--r--src/test/java/com/networknt/schema/MultipleOfValidatorTest.java94
-rw-r--r--src/test/java/com/networknt/schema/NotAllowedValidatorTest.java25
-rw-r--r--src/test/java/com/networknt/schema/OneOfValidatorTest.java126
-rw-r--r--src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java90
-rw-r--r--src/test/java/com/networknt/schema/OutputFormatTest.java50
-rw-r--r--src/test/java/com/networknt/schema/OutputUnitTest.java342
-rw-r--r--src/test/java/com/networknt/schema/OverrideValidatorTest.java70
-rw-r--r--src/test/java/com/networknt/schema/OverwritingCustomMessageBugTest.java52
-rw-r--r--src/test/java/com/networknt/schema/PathTypeTest.java49
-rw-r--r--src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java159
-rw-r--r--src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java112
-rw-r--r--src/test/java/com/networknt/schema/PropertiesTest.java20
-rw-r--r--src/test/java/com/networknt/schema/PropertiesValidatorTest.java30
-rw-r--r--src/test/java/com/networknt/schema/PropertyNamesValidatorTest.java60
-rw-r--r--src/test/java/com/networknt/schema/ReadOnlyValidatorTest.java61
-rw-r--r--src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java34
-rw-r--r--src/test/java/com/networknt/schema/RefTest.java69
-rw-r--r--src/test/java/com/networknt/schema/RefValidatorTest.java106
-rw-r--r--src/test/java/com/networknt/schema/SchemaLocationTest.java206
-rw-r--r--src/test/java/com/networknt/schema/SelfRefTest.java32
-rw-r--r--src/test/java/com/networknt/schema/SharedConfigTest.java53
-rw-r--r--src/test/java/com/networknt/schema/SpecVersionDetectorTest.java56
-rwxr-xr-xsrc/test/java/com/networknt/schema/StringCheckerTest.java52
-rw-r--r--src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java345
-rwxr-xr-xsrc/test/java/com/networknt/schema/TypeFactoryTest.java99
-rw-r--r--src/test/java/com/networknt/schema/TypeValidatorTest.java142
-rw-r--r--src/test/java/com/networknt/schema/UnevaluatedItemsTest.java19
-rw-r--r--src/test/java/com/networknt/schema/UnevaluatedItemsValidatorTest.java84
-rw-r--r--src/test/java/com/networknt/schema/UnevaluatedPropertiesTest.java20
-rw-r--r--src/test/java/com/networknt/schema/UnevaluatedPropertiesValidatorTest.java151
-rw-r--r--src/test/java/com/networknt/schema/UnknownKeywordFactoryTest.java31
-rw-r--r--src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java75
-rw-r--r--src/test/java/com/networknt/schema/UriMappingTest.java171
-rw-r--r--src/test/java/com/networknt/schema/UrnTest.java46
-rw-r--r--src/test/java/com/networknt/schema/V4JsonSchemaTest.java104
-rw-r--r--src/test/java/com/networknt/schema/ValidationMessageTest.java36
-rw-r--r--src/test/java/com/networknt/schema/ValidatorTypeCodeTest.java50
-rw-r--r--src/test/java/com/networknt/schema/VocabularyTest.java218
-rw-r--r--src/test/java/com/networknt/schema/format/IriFormatTest.java74
-rw-r--r--src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java74
-rw-r--r--src/test/java/com/networknt/schema/format/UriFormatTest.java74
-rw-r--r--src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java74
-rw-r--r--src/test/java/com/networknt/schema/i18n/LocalesTest.java55
-rw-r--r--src/test/java/com/networknt/schema/i18n/ResourceBundleMessageSourceTest.java83
-rw-r--r--src/test/java/com/networknt/schema/output/OutputUnitDataTest.java39
-rw-r--r--src/test/java/com/networknt/schema/regex/Issue814Test.java69
-rw-r--r--src/test/java/com/networknt/schema/resource/MapSchemaLoaderTest.java61
-rw-r--r--src/test/java/com/networknt/schema/resource/MapSchemaMapperTest.java36
-rw-r--r--src/test/java/com/networknt/schema/resource/MetaSchemaMapperTest.java62
-rw-r--r--src/test/java/com/networknt/schema/resource/UriSchemaLoaderTest.java66
-rw-r--r--src/test/java/com/networknt/schema/suite/TestCase.java150
-rw-r--r--src/test/java/com/networknt/schema/suite/TestSource.java88
-rw-r--r--src/test/java/com/networknt/schema/suite/TestSpec.java269
-rw-r--r--src/test/java/com/networknt/schema/utils/AbsoluteIrisTest.java86
-rw-r--r--src/test/java/com/networknt/schema/utils/SetViewTest.java209
-rw-r--r--src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java814
-rw-r--r--src/test/resources/data/OverwritingCustomMessageBug.json19
-rw-r--r--src/test/resources/data/contains/issue769/max-contains-v7.json7
-rw-r--r--src/test/resources/data/contains/issue769/max-contains.json7
-rw-r--r--src/test/resources/data/contains/issue769/min-contains-v7.json4
-rw-r--r--src/test/resources/data/contains/issue769/min-contains.json4
-rw-r--r--src/test/resources/data/dstTimes.json8
-rw-r--r--src/test/resources/data/issue255.json6
-rw-r--r--src/test/resources/data/issue295.json1
-rw-r--r--src/test/resources/data/issue313.json8
-rw-r--r--src/test/resources/data/issue327.json5
-rw-r--r--src/test/resources/data/issue342.json4
-rw-r--r--src/test/resources/data/issue366.json24
-rw-r--r--src/test/resources/data/issue375.json10
-rw-r--r--src/test/resources/data/issue383.json11
-rw-r--r--src/test/resources/data/issue396.json10
-rw-r--r--src/test/resources/data/issue404.json3
-rw-r--r--src/test/resources/data/issue426.json9
-rw-r--r--src/test/resources/data/issue428.json248
-rw-r--r--src/test/resources/data/issue451.json12
-rw-r--r--src/test/resources/data/issue456-T2.json7
-rw-r--r--src/test/resources/data/issue456-T3.json7
-rw-r--r--src/test/resources/data/issue461-v7.json32
-rw-r--r--src/test/resources/data/issue467.json19
-rw-r--r--src/test/resources/data/issue471.json4
-rw-r--r--src/test/resources/data/issue493-invalid-1.json8
-rw-r--r--src/test/resources/data/issue493-invalid-2.json12
-rw-r--r--src/test/resources/data/issue493-valid-1.json12
-rw-r--r--src/test/resources/data/issue493-valid-2.json12
-rw-r--r--src/test/resources/data/issue500_1.json5
-rw-r--r--src/test/resources/data/issue500_2.json5
-rw-r--r--src/test/resources/data/issue606.json8
-rw-r--r--src/test/resources/data/issue627.json5
-rw-r--r--src/test/resources/data/issue664.json10
-rw-r--r--src/test/resources/data/issue668.json5
-rw-r--r--src/test/resources/data/issue832.json4
-rw-r--r--src/test/resources/data/issue898.json4
-rw-r--r--src/test/resources/data/notAllowedValidation/notAllowedJson.json6
-rw-r--r--src/test/resources/data/output-format-input.json10
-rw-r--r--src/test/resources/data/read-only-data.json4
-rw-r--r--src/test/resources/data/schemaTag.json3
-rw-r--r--src/test/resources/data/schemaTagMissing.json3
-rw-r--r--src/test/resources/data/walk-data-default.json12
-rw-r--r--src/test/resources/data/walk-data.json11
-rw-r--r--src/test/resources/draft2019-09/dependencies.json340
-rw-r--r--src/test/resources/draft2019-09/invalid-min-max-contains.json123
-rw-r--r--src/test/resources/draft2019-09/issue255.json87
-rw-r--r--src/test/resources/draft2019-09/issue375.json28
-rw-r--r--src/test/resources/draft2019-09/permissive-duration.json19
-rw-r--r--src/test/resources/draft2019-09/properties.json32
-rw-r--r--src/test/resources/draft2019-09/schemaTag.json3
-rw-r--r--src/test/resources/draft2020-12/invalid-min-max-contains.json123
-rw-r--r--src/test/resources/draft2020-12/issue495.json108
-rw-r--r--src/test/resources/draft2020-12/issue656.json163
-rw-r--r--src/test/resources/draft2020-12/issue782.json102
-rw-r--r--src/test/resources/draft2020-12/issue798.json46
-rw-r--r--src/test/resources/draft2020-12/schemaTag.json3
-rw-r--r--src/test/resources/draft4/complex.json1090
-rw-r--r--src/test/resources/draft4/enumObject.json80
-rw-r--r--src/test/resources/draft4/extra/classpath/schema.json31
-rw-r--r--src/test/resources/draft4/extra/classpath/sub-schema.json10
-rw-r--r--src/test/resources/draft4/extra/cyclic/Element.json19
-rw-r--r--src/test/resources/draft4/extra/cyclic/Extension.json19
-rw-r--r--src/test/resources/draft4/extra/cyclic/Master.json18
-rw-r--r--src/test/resources/draft4/extra/folder/folderInteger.json3
-rw-r--r--src/test/resources/draft4/extra/product/product-all-errors-data.json5
-rw-r--r--src/test/resources/draft4/extra/product/product-no-errors-data.json5
-rw-r--r--src/test/resources/draft4/extra/product/product-one-error-data.json5
-rw-r--r--src/test/resources/draft4/extra/product/product-two-errors-data.json5
-rw-r--r--src/test/resources/draft4/extra/product/product.schema.json28
-rw-r--r--src/test/resources/draft4/extra/property.json37
-rw-r--r--src/test/resources/draft4/extra/union_type.json95
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/example-schema.json3
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/invalid-schema-uri.json15
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/schema-with-ref-mapping.json10
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json10
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/uri-mapping.json10
-rw-r--r--src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json25
-rw-r--r--src/test/resources/draft4/extra/uuid.json30
-rw-r--r--src/test/resources/draft4/idRef.json53
-rw-r--r--src/test/resources/draft4/integer.json3
-rw-r--r--src/test/resources/draft4/issue258/Element.json19
-rw-r--r--src/test/resources/draft4/issue258/Extension.json19
-rw-r--r--src/test/resources/draft4/issue258/Master.json18
-rw-r--r--src/test/resources/draft4/issue425.json52
-rw-r--r--src/test/resources/draft4/refRemoteSchema.json3
-rw-r--r--src/test/resources/draft4/relativeRefRemote.json123
-rw-r--r--src/test/resources/draft4/schemaTag.json3
-rw-r--r--src/test/resources/draft4/subSchemas.json13
-rw-r--r--src/test/resources/draft6/idRef.json53
-rw-r--r--src/test/resources/draft6/schemaTag.json3
-rw-r--r--src/test/resources/draft7/idRef.json53
-rw-r--r--src/test/resources/draft7/issue386.json134
-rw-r--r--src/test/resources/draft7/issue470.json142
-rw-r--r--src/test/resources/draft7/issue491.json328
-rw-r--r--src/test/resources/draft7/issue516.json144
-rw-r--r--src/test/resources/draft7/issue590.json88
-rw-r--r--src/test/resources/draft7/issue650.json13
-rw-r--r--src/test/resources/draft7/issue653.json90
-rw-r--r--src/test/resources/draft7/issue678.json60
-rw-r--r--src/test/resources/draft7/schemaTag.json3
-rw-r--r--src/test/resources/draft7/urn/account.schema.json13
-rw-r--r--src/test/resources/draft7/urn/issue665.json26
-rw-r--r--src/test/resources/draft7/urn/issue665_external_urn_ref.json12
-rw-r--r--src/test/resources/draft7/urn/issue665_external_urn_subschema.json13
-rw-r--r--src/test/resources/draft7/urn/test.json6
-rw-r--r--src/test/resources/draft7/urn/urn.schema.json10
-rw-r--r--src/test/resources/issue686/translations.properties1
-rw-r--r--src/test/resources/issue686/translations_de.properties1
-rw-r--r--src/test/resources/issue686/translations_fr.properties1
-rw-r--r--src/test/resources/issue784/schema.json9
-rw-r--r--src/test/resources/issues/662/emptyObject.json1
-rw-r--r--src/test/resources/issues/662/objectInvalidValue.json5
-rw-r--r--src/test/resources/issues/662/schema.json26
-rw-r--r--src/test/resources/issues/662/validObject.json5
-rw-r--r--src/test/resources/jsv-messages-override.properties1
-rw-r--r--src/test/resources/logback-test.xml31
-rw-r--r--src/test/resources/multipleOfScale.json30
-rw-r--r--src/test/resources/oas/v31/dialect/base25
-rw-r--r--src/test/resources/oas/v31/meta/base87
-rw-r--r--src/test/resources/oas/v31/schema-base/2022-10-0723
-rw-r--r--src/test/resources/oas/v31/schema/2022-10-071440
-rw-r--r--src/test/resources/openapi3/discriminator.json592
-rw-r--r--src/test/resources/prefixItemsException/prefixItemsException.json16
-rw-r--r--src/test/resources/recursiveRefException/invalidRecursiveReference.json50
-rw-r--r--src/test/resources/remotes/folder/folderInteger.json3
-rw-r--r--src/test/resources/remotes/id_schema/property.json35
-rw-r--r--src/test/resources/remotes/id_schema/schema/features.json14
-rw-r--r--src/test/resources/remotes/self_ref/selfRef.json4
-rw-r--r--src/test/resources/schema/OverwritingCustomMessageBug.json31
-rw-r--r--src/test/resources/schema/contains/issue769/max-contains-v7.json12
-rw-r--r--src/test/resources/schema/contains/issue769/max-contains.json12
-rw-r--r--src/test/resources/schema/contains/issue769/min-contains-v7.json12
-rw-r--r--src/test/resources/schema/contains/issue769/min-contains.json12
-rw-r--r--src/test/resources/schema/customMessageTests/custom-message-disabled-tests.json124
-rw-r--r--src/test/resources/schema/customMessageTests/custom-message-tests.json196
-rw-r--r--src/test/resources/schema/dateTimeArray.json10
-rw-r--r--src/test/resources/schema/example-main.json18
-rw-r--r--src/test/resources/schema/example-ref.json24
-rw-r--r--src/test/resources/schema/id-relative.json4
-rw-r--r--src/test/resources/schema/issue295-v7.json24
-rw-r--r--src/test/resources/schema/issue313-2019-09.json41
-rw-r--r--src/test/resources/schema/issue313-v7.json172
-rw-r--r--src/test/resources/schema/issue314-v7.json4
-rw-r--r--src/test/resources/schema/issue327-v7.json21
-rw-r--r--src/test/resources/schema/issue342-v7.json11
-rw-r--r--src/test/resources/schema/issue347-v7.json19
-rw-r--r--src/test/resources/schema/issue366_schema.json11
-rw-r--r--src/test/resources/schema/issue383-v7.json85
-rw-r--r--src/test/resources/schema/issue396-v7.json40
-rw-r--r--src/test/resources/schema/issue404-v7.json14
-rw-r--r--src/test/resources/schema/issue426-v7.json20
-rw-r--r--src/test/resources/schema/issue451-v7.json41
-rw-r--r--src/test/resources/schema/issue456-v7.json44
-rw-r--r--src/test/resources/schema/issue467.json26
-rw-r--r--src/test/resources/schema/issue471-2019-09.json18
-rw-r--r--src/test/resources/schema/issue493.json72
-rw-r--r--src/test/resources/schema/issue500_1-v7.json18
-rw-r--r--src/test/resources/schema/issue500_2-v7.json35
-rw-r--r--src/test/resources/schema/issue518-v7.json4
-rw-r--r--src/test/resources/schema/issue575-2019-09.json14
-rw-r--r--src/test/resources/schema/issue606-v7.json73
-rw-r--r--src/test/resources/schema/issue619.json25
-rw-r--r--src/test/resources/schema/issue627-v7.json20
-rw-r--r--src/test/resources/schema/issue664-v7.json61
-rw-r--r--src/test/resources/schema/issue668-sub1.yml1
-rw-r--r--src/test/resources/schema/issue668-sub2.yaml1
-rw-r--r--src/test/resources/schema/issue668-sub31
-rw-r--r--src/test/resources/schema/issue668.yml9
-rw-r--r--src/test/resources/schema/issue687.json19
-rw-r--r--src/test/resources/schema/issue832-v7.json16
-rw-r--r--src/test/resources/schema/issue898.json17
-rw-r--r--src/test/resources/schema/issue928-v07.json14
-rw-r--r--src/test/resources/schema/issue928-v2019-09.json14
-rw-r--r--src/test/resources/schema/issue928-v2020-12.json14
-rw-r--r--src/test/resources/schema/issue936.json4
-rw-r--r--src/test/resources/schema/notAllowedValidation/notAllowedJson.json10
-rw-r--r--src/test/resources/schema/oas/v31/petstore.json1225
-rw-r--r--src/test/resources/schema/output-format-schema.json18
-rw-r--r--src/test/resources/schema/read-only-schema.json15
-rw-r--r--src/test/resources/schema/ref-main-schema-resource.json52
-rw-r--r--src/test/resources/schema/ref-main.json18
-rw-r--r--src/test/resources/schema/ref-ref.json24
-rw-r--r--src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json21
-rw-r--r--src/test/resources/schema/unevaluatedTests/unevaluated-tests.json2247
-rw-r--r--src/test/resources/schema/walk-schema-default.json101
-rw-r--r--src/test/resources/schema/walk-schema.json75
-rw-r--r--src/test/resources/selfRef.json31
-rw-r--r--src/test/resources/test-messages.properties1
-rw-r--r--src/test/resources/test-messages_fr.properties1
-rw-r--r--src/test/suite/.editorconfig1
-rw-r--r--src/test/suite/.github/CODEOWNERS2
-rw-r--r--src/test/suite/.github/workflows/ci.yml25
-rw-r--r--src/test/suite/.gitignore160
-rw-r--r--src/test/suite/CONTRIBUTING.md87
-rw-r--r--src/test/suite/LICENSE19
-rw-r--r--src/test/suite/README.md346
-rwxr-xr-xsrc/test/suite/bin/jsonschema_suite734
-rw-r--r--src/test/suite/output-test-schema.json70
-rw-r--r--src/test/suite/output-tests/README.md63
-rw-r--r--src/test/suite/output-tests/draft-next/content/general.json43
-rw-r--r--src/test/suite/output-tests/draft-next/content/readOnly.json41
-rw-r--r--src/test/suite/output-tests/draft-next/content/type.json39
-rw-r--r--src/test/suite/output-tests/draft-next/output-schema.json95
-rw-r--r--src/test/suite/output-tests/draft2019-09/content/escape.json38
-rw-r--r--src/test/suite/output-tests/draft2019-09/content/general.json34
-rw-r--r--src/test/suite/output-tests/draft2019-09/content/readOnly.json38
-rw-r--r--src/test/suite/output-tests/draft2019-09/content/type.json63
-rw-r--r--src/test/suite/output-tests/draft2019-09/output-schema.json96
-rw-r--r--src/test/suite/output-tests/draft2020-12/content/escape.json38
-rw-r--r--src/test/suite/output-tests/draft2020-12/content/general.json34
-rw-r--r--src/test/suite/output-tests/draft2020-12/content/readOnly.json37
-rw-r--r--src/test/suite/output-tests/draft2020-12/content/type.json63
-rw-r--r--src/test/suite/output-tests/draft2020-12/output-schema.json96
-rw-r--r--src/test/suite/package.json12
-rw-r--r--src/test/suite/remotes/baseUriChange/folderInteger.json3
-rw-r--r--src/test/suite/remotes/baseUriChangeFolder/folderInteger.json3
-rw-r--r--src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json3
-rw-r--r--src/test/suite/remotes/different-id-ref-string.json5
-rw-r--r--src/test/suite/remotes/draft-next/baseUriChange/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft-next/baseUriChangeFolder/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft-next/detached-dynamicref.json13
-rw-r--r--src/test/suite/remotes/draft-next/detached-ref.json13
-rw-r--r--src/test/suite/remotes/draft-next/extendible-dynamic-ref.json21
-rw-r--r--src/test/suite/remotes/draft-next/format-assertion-false.json12
-rw-r--r--src/test/suite/remotes/draft-next/format-assertion-true.json12
-rw-r--r--src/test/suite/remotes/draft-next/id/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft-next/integer.json4
-rw-r--r--src/test/suite/remotes/draft-next/locationIndependentIdentifier.json12
-rw-r--r--src/test/suite/remotes/draft-next/metaschema-no-validation.json12
-rw-r--r--src/test/suite/remotes/draft-next/metaschema-optional-vocabulary.json13
-rw-r--r--src/test/suite/remotes/draft-next/name-defs.json16
-rw-r--r--src/test/suite/remotes/draft-next/nested/foo-ref-string.json7
-rw-r--r--src/test/suite/remotes/draft-next/nested/string.json4
-rw-r--r--src/test/suite/remotes/draft-next/ref-and-defs.json12
-rw-r--r--src/test/suite/remotes/draft-next/subSchemas-defs.json11
-rw-r--r--src/test/suite/remotes/draft-next/subSchemas.json11
-rw-r--r--src/test/suite/remotes/draft-next/tree.json17
-rw-r--r--src/test/suite/remotes/draft-next/unknownKeyword/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/baseUriChange/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/dependentRequired.json7
-rw-r--r--src/test/suite/remotes/draft2019-09/detached-ref.json13
-rw-r--r--src/test/suite/remotes/draft2019-09/extendible-dynamic-ref.json21
-rw-r--r--src/test/suite/remotes/draft2019-09/id/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/ignore-prefixItems.json7
-rw-r--r--src/test/suite/remotes/draft2019-09/integer.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/locationIndependentIdentifier.json12
-rw-r--r--src/test/suite/remotes/draft2019-09/metaschema-no-validation.json12
-rw-r--r--src/test/suite/remotes/draft2019-09/metaschema-optional-vocabulary.json13
-rw-r--r--src/test/suite/remotes/draft2019-09/name-defs.json16
-rw-r--r--src/test/suite/remotes/draft2019-09/nested/foo-ref-string.json7
-rw-r--r--src/test/suite/remotes/draft2019-09/nested/string.json4
-rw-r--r--src/test/suite/remotes/draft2019-09/ref-and-defs.json12
-rw-r--r--src/test/suite/remotes/draft2019-09/subSchemas-defs.json11
-rw-r--r--src/test/suite/remotes/draft2019-09/subSchemas.json11
-rw-r--r--src/test/suite/remotes/draft2019-09/tree.json17
-rw-r--r--src/test/suite/remotes/draft2019-09/unknownKeyword/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/baseUriChange/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/detached-dynamicref.json13
-rw-r--r--src/test/suite/remotes/draft2020-12/detached-ref.json13
-rw-r--r--src/test/suite/remotes/draft2020-12/extendible-dynamic-ref.json21
-rw-r--r--src/test/suite/remotes/draft2020-12/format-assertion-false.json12
-rw-r--r--src/test/suite/remotes/draft2020-12/format-assertion-true.json12
-rw-r--r--src/test/suite/remotes/draft2020-12/id/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/integer.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/locationIndependentIdentifier.json12
-rw-r--r--src/test/suite/remotes/draft2020-12/metaschema-no-validation.json12
-rw-r--r--src/test/suite/remotes/draft2020-12/metaschema-optional-vocabulary.json13
-rw-r--r--src/test/suite/remotes/draft2020-12/name-defs.json16
-rw-r--r--src/test/suite/remotes/draft2020-12/nested/foo-ref-string.json7
-rw-r--r--src/test/suite/remotes/draft2020-12/nested/string.json4
-rw-r--r--src/test/suite/remotes/draft2020-12/prefixItems.json7
-rw-r--r--src/test/suite/remotes/draft2020-12/ref-and-defs.json12
-rw-r--r--src/test/suite/remotes/draft2020-12/subSchemas-defs.json11
-rw-r--r--src/test/suite/remotes/draft2020-12/subSchemas.json11
-rw-r--r--src/test/suite/remotes/draft2020-12/tree.json17
-rw-r--r--src/test/suite/remotes/draft2020-12/unknownKeyword/my_identifier.json4
-rw-r--r--src/test/suite/remotes/draft6/detached-ref.json13
-rw-r--r--src/test/suite/remotes/draft7/detached-ref.json13
-rw-r--r--src/test/suite/remotes/draft7/ignore-dependentRequired.json7
-rw-r--r--src/test/suite/remotes/extendible-dynamic-ref.json20
-rw-r--r--src/test/suite/remotes/id/my_identifier.json3
-rw-r--r--src/test/suite/remotes/integer.json3
-rw-r--r--src/test/suite/remotes/locationIndependentIdentifier.json11
-rw-r--r--src/test/suite/remotes/locationIndependentIdentifierDraft4.json11
-rw-r--r--src/test/suite/remotes/locationIndependentIdentifierPre2019.json11
-rw-r--r--src/test/suite/remotes/name-defs.json15
-rw-r--r--src/test/suite/remotes/name.json15
-rw-r--r--src/test/suite/remotes/nested-absolute-ref-to-string.json9
-rw-r--r--src/test/suite/remotes/nested/foo-ref-string.json6
-rw-r--r--src/test/suite/remotes/nested/string.json3
-rw-r--r--src/test/suite/remotes/ref-and-definitions.json11
-rw-r--r--src/test/suite/remotes/ref-and-defs.json11
-rw-r--r--src/test/suite/remotes/subSchemas-defs.json10
-rw-r--r--src/test/suite/remotes/subSchemas.json10
-rw-r--r--src/test/suite/remotes/tree.json16
-rw-r--r--src/test/suite/remotes/unknownKeyword/my_identifier.json3
-rw-r--r--src/test/suite/remotes/urn-ref-string.json5
-rw-r--r--src/test/suite/test-schema.json61
-rw-r--r--src/test/suite/tests/draft-next/additionalProperties.json156
-rw-r--r--src/test/suite/tests/draft-next/allOf.json312
-rw-r--r--src/test/suite/tests/draft-next/anchor.json144
-rw-r--r--src/test/suite/tests/draft-next/anyOf.json203
-rw-r--r--src/test/suite/tests/draft-next/boolean_schema.json104
-rw-r--r--src/test/suite/tests/draft-next/const.json387
-rw-r--r--src/test/suite/tests/draft-next/contains.json197
-rw-r--r--src/test/suite/tests/draft-next/content.json131
-rw-r--r--src/test/suite/tests/draft-next/default.json82
-rw-r--r--src/test/suite/tests/draft-next/defs.json21
-rw-r--r--src/test/suite/tests/draft-next/dependentRequired.json152
-rw-r--r--src/test/suite/tests/draft-next/dependentSchemas.json171
-rw-r--r--src/test/suite/tests/draft-next/dynamicRef.json646
-rw-r--r--src/test/suite/tests/draft-next/enum.json358
-rw-r--r--src/test/suite/tests/draft-next/exclusiveMaximum.json31
-rw-r--r--src/test/suite/tests/draft-next/exclusiveMinimum.json31
-rw-r--r--src/test/suite/tests/draft-next/format.json838
-rw-r--r--src/test/suite/tests/draft-next/id.json211
-rw-r--r--src/test/suite/tests/draft-next/if-then-else.json268
-rw-r--r--src/test/suite/tests/draft-next/infinite-loop-detection.json37
-rw-r--r--src/test/suite/tests/draft-next/items.json304
-rw-r--r--src/test/suite/tests/draft-next/maxContains.json102
-rw-r--r--src/test/suite/tests/draft-next/maxItems.json50
-rw-r--r--src/test/suite/tests/draft-next/maxLength.json55
-rw-r--r--src/test/suite/tests/draft-next/maxProperties.json79
-rw-r--r--src/test/suite/tests/draft-next/maximum.json60
-rw-r--r--src/test/suite/tests/draft-next/minContains.json224
-rw-r--r--src/test/suite/tests/draft-next/minItems.json50
-rw-r--r--src/test/suite/tests/draft-next/minLength.json55
-rw-r--r--src/test/suite/tests/draft-next/minProperties.json60
-rw-r--r--src/test/suite/tests/draft-next/minimum.json75
-rw-r--r--src/test/suite/tests/draft-next/multipleOf.json98
-rw-r--r--src/test/suite/tests/draft-next/not.json153
-rw-r--r--src/test/suite/tests/draft-next/oneOf.json293
-rw-r--r--src/test/suite/tests/draft-next/optional/anchor.json60
-rw-r--r--src/test/suite/tests/draft-next/optional/bignum.json110
-rw-r--r--src/test/suite/tests/draft-next/optional/dependencies-compatibility.json282
-rw-r--r--src/test/suite/tests/draft-next/optional/ecmascript-regex.json596
-rw-r--r--src/test/suite/tests/draft-next/optional/float-overflow.json17
-rw-r--r--src/test/suite/tests/draft-next/optional/format-assertion.json42
-rw-r--r--src/test/suite/tests/draft-next/optional/format/date-time.json136
-rw-r--r--src/test/suite/tests/draft-next/optional/format/date.json246
-rw-r--r--src/test/suite/tests/draft-next/optional/format/duration.json136
-rw-r--r--src/test/suite/tests/draft-next/optional/format/email.json121
-rw-r--r--src/test/suite/tests/draft-next/optional/format/hostname.json126
-rw-r--r--src/test/suite/tests/draft-next/optional/format/idn-email.json61
-rw-r--r--src/test/suite/tests/draft-next/optional/format/idn-hostname.json332
-rw-r--r--src/test/suite/tests/draft-next/optional/format/ipv4.json92
-rw-r--r--src/test/suite/tests/draft-next/optional/format/ipv6.json211
-rw-r--r--src/test/suite/tests/draft-next/optional/format/iri-reference.json76
-rw-r--r--src/test/suite/tests/draft-next/optional/format/iri.json86
-rw-r--r--src/test/suite/tests/draft-next/optional/format/json-pointer.json201
-rw-r--r--src/test/suite/tests/draft-next/optional/format/regex.json51
-rw-r--r--src/test/suite/tests/draft-next/optional/format/relative-json-pointer.json101
-rw-r--r--src/test/suite/tests/draft-next/optional/format/time.json236
-rw-r--r--src/test/suite/tests/draft-next/optional/format/uri-reference.json76
-rw-r--r--src/test/suite/tests/draft-next/optional/format/uri-template.json61
-rw-r--r--src/test/suite/tests/draft-next/optional/format/uri.json141
-rw-r--r--src/test/suite/tests/draft-next/optional/format/uuid.json116
-rw-r--r--src/test/suite/tests/draft-next/optional/id.json53
-rw-r--r--src/test/suite/tests/draft-next/optional/non-bmp-regex.json86
-rw-r--r--src/test/suite/tests/draft-next/optional/refOfUnknownKeyword.json69
-rw-r--r--src/test/suite/tests/draft-next/optional/unknownKeyword.json57
-rw-r--r--src/test/suite/tests/draft-next/pattern.json65
-rw-r--r--src/test/suite/tests/draft-next/patternProperties.json176
-rw-r--r--src/test/suite/tests/draft-next/prefixItems.json104
-rw-r--r--src/test/suite/tests/draft-next/properties.json242
-rw-r--r--src/test/suite/tests/draft-next/propertyDependencies.json161
-rw-r--r--src/test/suite/tests/draft-next/propertyNames.json85
-rw-r--r--src/test/suite/tests/draft-next/ref.json1067
-rw-r--r--src/test/suite/tests/draft-next/refRemote.json342
-rw-r--r--src/test/suite/tests/draft-next/required.json158
-rw-r--r--src/test/suite/tests/draft-next/type.json501
-rw-r--r--src/test/suite/tests/draft-next/unevaluatedItems.json792
-rw-r--r--src/test/suite/tests/draft-next/unevaluatedProperties.json1607
-rw-r--r--src/test/suite/tests/draft-next/uniqueItems.json419
-rw-r--r--src/test/suite/tests/draft-next/vocabulary.json57
-rw-r--r--src/test/suite/tests/draft2019-09/additionalItems.json221
-rw-r--r--src/test/suite/tests/draft2019-09/additionalProperties.json156
-rw-r--r--src/test/suite/tests/draft2019-09/allOf.json312
-rw-r--r--src/test/suite/tests/draft2019-09/anchor.json145
-rw-r--r--src/test/suite/tests/draft2019-09/anyOf.json203
-rw-r--r--src/test/suite/tests/draft2019-09/boolean_schema.json104
-rw-r--r--src/test/suite/tests/draft2019-09/const.json387
-rw-r--r--src/test/suite/tests/draft2019-09/contains.json176
-rw-r--r--src/test/suite/tests/draft2019-09/content.json131
-rw-r--r--src/test/suite/tests/draft2019-09/default.json82
-rw-r--r--src/test/suite/tests/draft2019-09/defs.json21
-rw-r--r--src/test/suite/tests/draft2019-09/dependentRequired.json152
-rw-r--r--src/test/suite/tests/draft2019-09/dependentSchemas.json171
-rw-r--r--src/test/suite/tests/draft2019-09/enum.json358
-rw-r--r--src/test/suite/tests/draft2019-09/exclusiveMaximum.json31
-rw-r--r--src/test/suite/tests/draft2019-09/exclusiveMinimum.json31
-rw-r--r--src/test/suite/tests/draft2019-09/format.json743
-rw-r--r--src/test/suite/tests/draft2019-09/id.json211
-rw-r--r--src/test/suite/tests/draft2019-09/if-then-else.json268
-rw-r--r--src/test/suite/tests/draft2019-09/infinite-loop-detection.json37
-rw-r--r--src/test/suite/tests/draft2019-09/items.json295
-rw-r--r--src/test/suite/tests/draft2019-09/maxContains.json102
-rw-r--r--src/test/suite/tests/draft2019-09/maxItems.json50
-rw-r--r--src/test/suite/tests/draft2019-09/maxLength.json55
-rw-r--r--src/test/suite/tests/draft2019-09/maxProperties.json79
-rw-r--r--src/test/suite/tests/draft2019-09/maximum.json60
-rw-r--r--src/test/suite/tests/draft2019-09/minContains.json224
-rw-r--r--src/test/suite/tests/draft2019-09/minItems.json50
-rw-r--r--src/test/suite/tests/draft2019-09/minLength.json55
-rw-r--r--src/test/suite/tests/draft2019-09/minProperties.json60
-rw-r--r--src/test/suite/tests/draft2019-09/minimum.json75
-rw-r--r--src/test/suite/tests/draft2019-09/multipleOf.json97
-rw-r--r--src/test/suite/tests/draft2019-09/not.json301
-rw-r--r--src/test/suite/tests/draft2019-09/oneOf.json293
-rw-r--r--src/test/suite/tests/draft2019-09/optional/anchor.json60
-rw-r--r--src/test/suite/tests/draft2019-09/optional/bignum.json110
-rw-r--r--src/test/suite/tests/draft2019-09/optional/cross-draft.json41
-rw-r--r--src/test/suite/tests/draft2019-09/optional/dependencies-compatibility.json282
-rw-r--r--src/test/suite/tests/draft2019-09/optional/ecmascript-regex.json582
-rw-r--r--src/test/suite/tests/draft2019-09/optional/float-overflow.json16
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/date-time.json136
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/date.json246
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/duration.json136
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/email.json86
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/hostname.json126
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/idn-email.json61
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json332
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/ipv4.json92
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/ipv6.json211
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/iri-reference.json76
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/iri.json86
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/json-pointer.json201
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/regex.json51
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/relative-json-pointer.json101
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/time.json236
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/unknown.json46
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/uri-reference.json76
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/uri-template.json61
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/uri.json141
-rw-r--r--src/test/suite/tests/draft2019-09/optional/format/uuid.json116
-rw-r--r--src/test/suite/tests/draft2019-09/optional/id.json53
-rw-r--r--src/test/suite/tests/draft2019-09/optional/no-schema.json26
-rw-r--r--src/test/suite/tests/draft2019-09/optional/non-bmp-regex.json86
-rw-r--r--src/test/suite/tests/draft2019-09/optional/refOfUnknownKeyword.json69
-rw-r--r--src/test/suite/tests/draft2019-09/optional/unknownKeyword.json57
-rw-r--r--src/test/suite/tests/draft2019-09/pattern.json65
-rw-r--r--src/test/suite/tests/draft2019-09/patternProperties.json176
-rw-r--r--src/test/suite/tests/draft2019-09/properties.json242
-rw-r--r--src/test/suite/tests/draft2019-09/propertyNames.json115
-rw-r--r--src/test/suite/tests/draft2019-09/recursiveRef.json408
-rw-r--r--src/test/suite/tests/draft2019-09/ref.json1067
-rw-r--r--src/test/suite/tests/draft2019-09/refRemote.json342
-rw-r--r--src/test/suite/tests/draft2019-09/required.json158
-rw-r--r--src/test/suite/tests/draft2019-09/type.json501
-rw-r--r--src/test/suite/tests/draft2019-09/unevaluatedItems.json703
-rw-r--r--src/test/suite/tests/draft2019-09/unevaluatedProperties.json1571
-rw-r--r--src/test/suite/tests/draft2019-09/uniqueItems.json419
-rw-r--r--src/test/suite/tests/draft2019-09/vocabulary.json57
-rw-r--r--src/test/suite/tests/draft2020-12/additionalProperties.json156
-rw-r--r--src/test/suite/tests/draft2020-12/allOf.json312
-rw-r--r--src/test/suite/tests/draft2020-12/anchor.json145
-rw-r--r--src/test/suite/tests/draft2020-12/anyOf.json203
-rw-r--r--src/test/suite/tests/draft2020-12/boolean_schema.json104
-rw-r--r--src/test/suite/tests/draft2020-12/const.json387
-rw-r--r--src/test/suite/tests/draft2020-12/contains.json176
-rw-r--r--src/test/suite/tests/draft2020-12/content.json131
-rw-r--r--src/test/suite/tests/draft2020-12/default.json82
-rw-r--r--src/test/suite/tests/draft2020-12/defs.json21
-rw-r--r--src/test/suite/tests/draft2020-12/dependentRequired.json152
-rw-r--r--src/test/suite/tests/draft2020-12/dependentSchemas.json171
-rw-r--r--src/test/suite/tests/draft2020-12/dynamicRef.json760
-rw-r--r--src/test/suite/tests/draft2020-12/enum.json358
-rw-r--r--src/test/suite/tests/draft2020-12/exclusiveMaximum.json31
-rw-r--r--src/test/suite/tests/draft2020-12/exclusiveMinimum.json31
-rw-r--r--src/test/suite/tests/draft2020-12/format.json838
-rw-r--r--src/test/suite/tests/draft2020-12/id.json211
-rw-r--r--src/test/suite/tests/draft2020-12/if-then-else.json268
-rw-r--r--src/test/suite/tests/draft2020-12/infinite-loop-detection.json37
-rw-r--r--src/test/suite/tests/draft2020-12/items.json304
-rw-r--r--src/test/suite/tests/draft2020-12/maxContains.json102
-rw-r--r--src/test/suite/tests/draft2020-12/maxItems.json50
-rw-r--r--src/test/suite/tests/draft2020-12/maxLength.json55
-rw-r--r--src/test/suite/tests/draft2020-12/maxProperties.json79
-rw-r--r--src/test/suite/tests/draft2020-12/maximum.json60
-rw-r--r--src/test/suite/tests/draft2020-12/minContains.json224
-rw-r--r--src/test/suite/tests/draft2020-12/minItems.json50
-rw-r--r--src/test/suite/tests/draft2020-12/minLength.json55
-rw-r--r--src/test/suite/tests/draft2020-12/minProperties.json60
-rw-r--r--src/test/suite/tests/draft2020-12/minimum.json75
-rw-r--r--src/test/suite/tests/draft2020-12/multipleOf.json97
-rw-r--r--src/test/suite/tests/draft2020-12/not.json301
-rw-r--r--src/test/suite/tests/draft2020-12/oneOf.json293
-rw-r--r--src/test/suite/tests/draft2020-12/optional/anchor.json60
-rw-r--r--src/test/suite/tests/draft2020-12/optional/bignum.json110
-rw-r--r--src/test/suite/tests/draft2020-12/optional/cross-draft.json18
-rw-r--r--src/test/suite/tests/draft2020-12/optional/dependencies-compatibility.json282
-rw-r--r--src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json598
-rw-r--r--src/test/suite/tests/draft2020-12/optional/float-overflow.json17
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format-assertion.json42
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/date-time.json136
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/date.json246
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/duration.json136
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/email.json121
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/hostname.json126
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/idn-email.json61
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json332
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/ipv4.json92
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/ipv6.json211
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/iri-reference.json76
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/iri.json86
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/json-pointer.json201
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/regex.json51
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/relative-json-pointer.json101
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/time.json236
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/unknown.json46
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/uri-reference.json76
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/uri-template.json61
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/uri.json141
-rw-r--r--src/test/suite/tests/draft2020-12/optional/format/uuid.json116
-rw-r--r--src/test/suite/tests/draft2020-12/optional/id.json53
-rw-r--r--src/test/suite/tests/draft2020-12/optional/no-schema.json26
-rw-r--r--src/test/suite/tests/draft2020-12/optional/non-bmp-regex.json86
-rw-r--r--src/test/suite/tests/draft2020-12/optional/refOfUnknownKeyword.json69
-rw-r--r--src/test/suite/tests/draft2020-12/optional/unknownKeyword.json57
-rw-r--r--src/test/suite/tests/draft2020-12/pattern.json65
-rw-r--r--src/test/suite/tests/draft2020-12/patternProperties.json176
-rw-r--r--src/test/suite/tests/draft2020-12/prefixItems.json104
-rw-r--r--src/test/suite/tests/draft2020-12/properties.json242
-rw-r--r--src/test/suite/tests/draft2020-12/propertyNames.json85
-rw-r--r--src/test/suite/tests/draft2020-12/ref.json1067
-rw-r--r--src/test/suite/tests/draft2020-12/refRemote.json342
-rw-r--r--src/test/suite/tests/draft2020-12/required.json158
-rw-r--r--src/test/suite/tests/draft2020-12/type.json501
-rw-r--r--src/test/suite/tests/draft2020-12/unevaluatedItems.json799
-rw-r--r--src/test/suite/tests/draft2020-12/unevaluatedProperties.json1568
-rw-r--r--src/test/suite/tests/draft2020-12/uniqueItems.json419
-rw-r--r--src/test/suite/tests/draft2020-12/vocabulary.json57
-rw-r--r--src/test/suite/tests/draft3/additionalItems.json147
-rw-r--r--src/test/suite/tests/draft3/additionalProperties.json147
-rw-r--r--src/test/suite/tests/draft3/default.json79
-rw-r--r--src/test/suite/tests/draft3/dependencies.json123
-rw-r--r--src/test/suite/tests/draft3/disallow.json80
-rw-r--r--src/test/suite/tests/draft3/divisibleBy.json60
-rw-r--r--src/test/suite/tests/draft3/enum.json118
-rw-r--r--src/test/suite/tests/draft3/extends.json94
-rw-r--r--src/test/suite/tests/draft3/format.json362
-rw-r--r--src/test/suite/tests/draft3/infinite-loop-detection.json32
-rw-r--r--src/test/suite/tests/draft3/items.json78
-rw-r--r--src/test/suite/tests/draft3/maxItems.json28
-rw-r--r--src/test/suite/tests/draft3/maxLength.json33
-rw-r--r--src/test/suite/tests/draft3/maximum.json99
-rw-r--r--src/test/suite/tests/draft3/minItems.json28
-rw-r--r--src/test/suite/tests/draft3/minLength.json33
-rw-r--r--src/test/suite/tests/draft3/minimum.json88
-rw-r--r--src/test/suite/tests/draft3/optional/bignum.json95
-rw-r--r--src/test/suite/tests/draft3/optional/ecmascript-regex.json18
-rw-r--r--src/test/suite/tests/draft3/optional/format/color.json38
-rw-r--r--src/test/suite/tests/draft3/optional/format/date-time.json38
-rw-r--r--src/test/suite/tests/draft3/optional/format/date.json168
-rw-r--r--src/test/suite/tests/draft3/optional/format/email.json53
-rw-r--r--src/test/suite/tests/draft3/optional/format/host-name.json63
-rw-r--r--src/test/suite/tests/draft3/optional/format/ip-address.json23
-rw-r--r--src/test/suite/tests/draft3/optional/format/ipv6.json68
-rw-r--r--src/test/suite/tests/draft3/optional/format/regex.json18
-rw-r--r--src/test/suite/tests/draft3/optional/format/time.json18
-rw-r--r--src/test/suite/tests/draft3/optional/format/uri.json28
-rw-r--r--src/test/suite/tests/draft3/optional/non-bmp-regex.json82
-rw-r--r--src/test/suite/tests/draft3/optional/zeroTerminatedFloats.json15
-rw-r--r--src/test/suite/tests/draft3/pattern.json59
-rw-r--r--src/test/suite/tests/draft3/patternProperties.json130
-rw-r--r--src/test/suite/tests/draft3/properties.json112
-rw-r--r--src/test/suite/tests/draft3/ref.json278
-rw-r--r--src/test/suite/tests/draft3/refRemote.json74
-rw-r--r--src/test/suite/tests/draft3/required.json53
-rw-r--r--src/test/suite/tests/draft3/type.json493
-rw-r--r--src/test/suite/tests/draft3/uniqueItems.json374
-rw-r--r--src/test/suite/tests/draft4/additionalItems.json183
-rw-r--r--src/test/suite/tests/draft4/additionalProperties.json147
-rw-r--r--src/test/suite/tests/draft4/allOf.json261
-rw-r--r--src/test/suite/tests/draft4/anyOf.json156
-rw-r--r--src/test/suite/tests/draft4/default.json79
-rw-r--r--src/test/suite/tests/draft4/definitions.json26
-rw-r--r--src/test/suite/tests/draft4/dependencies.json232
-rw-r--r--src/test/suite/tests/draft4/enum.json320
-rw-r--r--src/test/suite/tests/draft4/format.json218
-rw-r--r--src/test/suite/tests/draft4/infinite-loop-detection.json36
-rw-r--r--src/test/suite/tests/draft4/items.json227
-rw-r--r--src/test/suite/tests/draft4/maxItems.json28
-rw-r--r--src/test/suite/tests/draft4/maxLength.json33
-rw-r--r--src/test/suite/tests/draft4/maxProperties.json54
-rw-r--r--src/test/suite/tests/draft4/maximum.json99
-rw-r--r--src/test/suite/tests/draft4/minItems.json28
-rw-r--r--src/test/suite/tests/draft4/minLength.json33
-rw-r--r--src/test/suite/tests/draft4/minProperties.json38
-rw-r--r--src/test/suite/tests/draft4/minimum.json114
-rw-r--r--src/test/suite/tests/draft4/multipleOf.json82
-rw-r--r--src/test/suite/tests/draft4/not.json157
-rw-r--r--src/test/suite/tests/draft4/oneOf.json230
-rw-r--r--src/test/suite/tests/draft4/optional/bignum.json95
-rw-r--r--src/test/suite/tests/draft4/optional/ecmascript-regex.json552
-rw-r--r--src/test/suite/tests/draft4/optional/float-overflow.json13
-rw-r--r--src/test/suite/tests/draft4/optional/format/date-time.json133
-rw-r--r--src/test/suite/tests/draft4/optional/format/email.json83
-rw-r--r--src/test/suite/tests/draft4/optional/format/hostname.json118
-rw-r--r--src/test/suite/tests/draft4/optional/format/ipv4.json89
-rw-r--r--src/test/suite/tests/draft4/optional/format/ipv6.json208
-rw-r--r--src/test/suite/tests/draft4/optional/format/unknown.json43
-rw-r--r--src/test/suite/tests/draft4/optional/format/uri.json138
-rw-r--r--src/test/suite/tests/draft4/optional/id.json53
-rw-r--r--src/test/suite/tests/draft4/optional/non-bmp-regex.json82
-rw-r--r--src/test/suite/tests/draft4/optional/zeroTerminatedFloats.json15
-rw-r--r--src/test/suite/tests/draft4/pattern.json59
-rw-r--r--src/test/suite/tests/draft4/patternProperties.json135
-rw-r--r--src/test/suite/tests/draft4/properties.json205
-rw-r--r--src/test/suite/tests/draft4/ref.json592
-rw-r--r--src/test/suite/tests/draft4/refRemote.json189
-rw-r--r--src/test/suite/tests/draft4/required.json135
-rw-r--r--src/test/suite/tests/draft4/type.json469
-rw-r--r--src/test/suite/tests/draft4/uniqueItems.json409
-rw-r--r--src/test/suite/tests/draft6/additionalItems.json206
-rw-r--r--src/test/suite/tests/draft6/additionalProperties.json147
-rw-r--r--src/test/suite/tests/draft6/allOf.json294
-rw-r--r--src/test/suite/tests/draft6/anyOf.json189
-rw-r--r--src/test/suite/tests/draft6/boolean_schema.json104
-rw-r--r--src/test/suite/tests/draft6/const.json342
-rw-r--r--src/test/suite/tests/draft6/contains.json144
-rw-r--r--src/test/suite/tests/draft6/default.json79
-rw-r--r--src/test/suite/tests/draft6/definitions.json26
-rw-r--r--src/test/suite/tests/draft6/dependencies.json286
-rw-r--r--src/test/suite/tests/draft6/enum.json320
-rw-r--r--src/test/suite/tests/draft6/exclusiveMaximum.json30
-rw-r--r--src/test/suite/tests/draft6/exclusiveMinimum.json30
-rw-r--r--src/test/suite/tests/draft6/format.json326
-rw-r--r--src/test/suite/tests/draft6/infinite-loop-detection.json36
-rw-r--r--src/test/suite/tests/draft6/items.json282
-rw-r--r--src/test/suite/tests/draft6/maxItems.json44
-rw-r--r--src/test/suite/tests/draft6/maxLength.json49
-rw-r--r--src/test/suite/tests/draft6/maxProperties.json70
-rw-r--r--src/test/suite/tests/draft6/maximum.json54
-rw-r--r--src/test/suite/tests/draft6/minItems.json44
-rw-r--r--src/test/suite/tests/draft6/minLength.json49
-rw-r--r--src/test/suite/tests/draft6/minProperties.json54
-rw-r--r--src/test/suite/tests/draft6/minimum.json69
-rw-r--r--src/test/suite/tests/draft6/multipleOf.json82
-rw-r--r--src/test/suite/tests/draft6/not.json259
-rw-r--r--src/test/suite/tests/draft6/oneOf.json274
-rw-r--r--src/test/suite/tests/draft6/optional/bignum.json93
-rw-r--r--src/test/suite/tests/draft6/optional/ecmascript-regex.json552
-rw-r--r--src/test/suite/tests/draft6/optional/float-overflow.json13
-rw-r--r--src/test/suite/tests/draft6/optional/format/date-time.json133
-rw-r--r--src/test/suite/tests/draft6/optional/format/email.json83
-rw-r--r--src/test/suite/tests/draft6/optional/format/hostname.json118
-rw-r--r--src/test/suite/tests/draft6/optional/format/ipv4.json89
-rw-r--r--src/test/suite/tests/draft6/optional/format/ipv6.json208
-rw-r--r--src/test/suite/tests/draft6/optional/format/json-pointer.json198
-rw-r--r--src/test/suite/tests/draft6/optional/format/unknown.json43
-rw-r--r--src/test/suite/tests/draft6/optional/format/uri-reference.json73
-rw-r--r--src/test/suite/tests/draft6/optional/format/uri-template.json58
-rw-r--r--src/test/suite/tests/draft6/optional/format/uri.json138
-rw-r--r--src/test/suite/tests/draft6/optional/id.json134
-rw-r--r--src/test/suite/tests/draft6/optional/non-bmp-regex.json82
-rw-r--r--src/test/suite/tests/draft6/optional/unknownKeyword.json56
-rw-r--r--src/test/suite/tests/draft6/pattern.json59
-rw-r--r--src/test/suite/tests/draft6/patternProperties.json171
-rw-r--r--src/test/suite/tests/draft6/properties.json236
-rw-r--r--src/test/suite/tests/draft6/propertyNames.json107
-rw-r--r--src/test/suite/tests/draft6/ref.json929
-rw-r--r--src/test/suite/tests/draft6/refRemote.json257
-rw-r--r--src/test/suite/tests/draft6/required.json151
-rw-r--r--src/test/suite/tests/draft6/type.json474
-rw-r--r--src/test/suite/tests/draft6/uniqueItems.json409
-rw-r--r--src/test/suite/tests/draft7/additionalItems.json206
-rw-r--r--src/test/suite/tests/draft7/additionalProperties.json147
-rw-r--r--src/test/suite/tests/draft7/allOf.json294
-rw-r--r--src/test/suite/tests/draft7/anyOf.json189
-rw-r--r--src/test/suite/tests/draft7/boolean_schema.json104
-rw-r--r--src/test/suite/tests/draft7/const.json342
-rw-r--r--src/test/suite/tests/draft7/contains.json165
-rw-r--r--src/test/suite/tests/draft7/default.json79
-rw-r--r--src/test/suite/tests/draft7/definitions.json26
-rw-r--r--src/test/suite/tests/draft7/dependencies.json286
-rw-r--r--src/test/suite/tests/draft7/enum.json320
-rw-r--r--src/test/suite/tests/draft7/exclusiveMaximum.json30
-rw-r--r--src/test/suite/tests/draft7/exclusiveMinimum.json30
-rw-r--r--src/test/suite/tests/draft7/format.json614
-rw-r--r--src/test/suite/tests/draft7/if-then-else.json258
-rw-r--r--src/test/suite/tests/draft7/infinite-loop-detection.json36
-rw-r--r--src/test/suite/tests/draft7/items.json282
-rw-r--r--src/test/suite/tests/draft7/maxItems.json44
-rw-r--r--src/test/suite/tests/draft7/maxLength.json49
-rw-r--r--src/test/suite/tests/draft7/maxProperties.json70
-rw-r--r--src/test/suite/tests/draft7/maximum.json54
-rw-r--r--src/test/suite/tests/draft7/minItems.json44
-rw-r--r--src/test/suite/tests/draft7/minLength.json49
-rw-r--r--src/test/suite/tests/draft7/minProperties.json54
-rw-r--r--src/test/suite/tests/draft7/minimum.json69
-rw-r--r--src/test/suite/tests/draft7/multipleOf.json82
-rw-r--r--src/test/suite/tests/draft7/not.json259
-rw-r--r--src/test/suite/tests/draft7/oneOf.json274
-rw-r--r--src/test/suite/tests/draft7/optional/bignum.json93
-rw-r--r--src/test/suite/tests/draft7/optional/content.json77
-rw-r--r--src/test/suite/tests/draft7/optional/cross-draft.json25
-rw-r--r--src/test/suite/tests/draft7/optional/ecmascript-regex.json552
-rw-r--r--src/test/suite/tests/draft7/optional/float-overflow.json13
-rw-r--r--src/test/suite/tests/draft7/optional/format/date-time.json133
-rw-r--r--src/test/suite/tests/draft7/optional/format/date.json243
-rw-r--r--src/test/suite/tests/draft7/optional/format/email.json83
-rw-r--r--src/test/suite/tests/draft7/optional/format/hostname.json118
-rw-r--r--src/test/suite/tests/draft7/optional/format/idn-email.json58
-rw-r--r--src/test/suite/tests/draft7/optional/format/idn-hostname.json324
-rw-r--r--src/test/suite/tests/draft7/optional/format/ipv4.json89
-rw-r--r--src/test/suite/tests/draft7/optional/format/ipv6.json208
-rw-r--r--src/test/suite/tests/draft7/optional/format/iri-reference.json73
-rw-r--r--src/test/suite/tests/draft7/optional/format/iri.json83
-rw-r--r--src/test/suite/tests/draft7/optional/format/json-pointer.json198
-rw-r--r--src/test/suite/tests/draft7/optional/format/regex.json48
-rw-r--r--src/test/suite/tests/draft7/optional/format/relative-json-pointer.json98
-rw-r--r--src/test/suite/tests/draft7/optional/format/time.json233
-rw-r--r--src/test/suite/tests/draft7/optional/format/unknown.json43
-rw-r--r--src/test/suite/tests/draft7/optional/format/uri-reference.json73
-rw-r--r--src/test/suite/tests/draft7/optional/format/uri-template.json58
-rw-r--r--src/test/suite/tests/draft7/optional/format/uri.json138
-rw-r--r--src/test/suite/tests/draft7/optional/id.json114
-rw-r--r--src/test/suite/tests/draft7/optional/non-bmp-regex.json82
-rw-r--r--src/test/suite/tests/draft7/optional/unknownKeyword.json56
-rw-r--r--src/test/suite/tests/draft7/pattern.json59
-rw-r--r--src/test/suite/tests/draft7/patternProperties.json171
-rw-r--r--src/test/suite/tests/draft7/properties.json236
-rw-r--r--src/test/suite/tests/draft7/propertyNames.json107
-rw-r--r--src/test/suite/tests/draft7/ref.json1043
-rw-r--r--src/test/suite/tests/draft7/refRemote.json257
-rw-r--r--src/test/suite/tests/draft7/required.json151
-rw-r--r--src/test/suite/tests/draft7/type.json474
-rw-r--r--src/test/suite/tests/draft7/uniqueItems.json409
-rw-r--r--src/test/suite/tox.ini9
1156 files changed, 141423 insertions, 0 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..8b79675
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [ 8, 11, 17, 20 ]
+ name: Java ${{ matrix.java }}
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
+ cache: 'maven'
+
+ - name: Build with Maven
+ run: mvn clean verify
+
+ - name: Upload coverage to Codecov
+ if: matrix.java == '8'
+ uses: codecov/codecov-action@v3.1.1
+ with:
+ fail_ci_if_error: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..56e3442
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+target/
+bower_components/
+node_modules/
+dist/
+.idea/
+.tmp/
+.project
+.classpath
+.settings
+.metadata/
+*.iml
+*.ipr
+*.iws
+*.log
+*.tmp
+*.zip
+*.bak
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+/.gradle/
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..7591473
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["external_json-schema-validator_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "external_json-schema-validator_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
+
+java_library_host {
+ name: "json-schema-validator",
+ visibility: [":__subpackages__"],
+ libs: [
+ "jackson-core",
+ ],
+ srcs: [
+ "src/main/**/*.java",
+ ],
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..be3acf9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1259 @@
+# Change Log
+All notable changes to this project will be documented in this file.
+
+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]
+
+### Added
+
+### Changed
+
+## 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
+
+### Changed
+
+- fixes #525 Leap seconds are handled even better Thanks @aznan2 and @Matti Hansson
+- fixes #524 Fix handling of leap seconds in date-time validation
+- fixes #523 synched ipv4 and ipv6 and fix some gaps for the IP format
+- fixes #522 synch the official test suite for draft v4 from schema.org
+- fixes #509 NPE with oneOf and custom URI Fetcher or Factory
+- fixes #508 Make date-time validation align with RFC3339 Thanks @aznan2 and @Matti Hansson
+- fixes #519 Preserve # suffix during metaschema URI normalization Thanks @pondzix
+- fixes #516 fix the additionalProperties in oneOf failed test cases
+- fixes #505 AdditionalPropertiesOneOfFails test Thanks @huubfleuren
+- fixes #510 try to reproduce the issue but failed
+- fixes #511 Add German validation messages. Thanks @rustermi
+- fixes #500 Support fragment references using $anchor @Whathecode
+
+## 1.0.66 - 2022-01-24
+
+### Changed
+
+- fixes #496 Improve type validation of integrals. Thanks @christi-square
+- fixes #497 Support fragment references using $anchor @carolkao
+
+## 1.0.65 - 2022-01-07
+
+### Changed
+
+- fixes #492 Sort ValidationMessage by its type. Thanks @jsu216
+- fixes #490 Handle the situation when context class loader is null. Thanks @vti and @Viacheslav Tykhanovskyi
+- fixes #489 Fix flakiness in CollectorContextTest. Thanks @pthariensflame
+- upgrade to logback 1.2.7 to resolve some x-ray warnnings
+- upgrade to undertow 2.2.14 to resolve some x-ray warnnings.
+- fixes #488 Fix violations of Sonar rule 2142. Thanks @khaes-kth
+- fixes #477 apply default in objects and arrays. Thanks @SiemelNaran
+- fixes #485 FailFast should not cause exception on if. Thanks @gareth-robinson
+- fixes #483 Add Java Syntax Highlighting to specversion.md. Thanks @JLLeitschuh
+- fixes #482 upgrade to joni 2.1.41 to resolve a security concern
+
+## 1.0.64 - 2021-11-10
+
+### Changed
+
+- fixes #480 Time format validation supports milliseconds. Thanks @@MatusSivak
+- fixes #479 Add dependentRequired and dependentSchemas validators. Thanks @@kmalski
+
+## 1.0.63 - 2021-10-21
+
+### Changed
+
+- fixes #470 OneOfValidator give incorrect message when the wrong json element is not the first one in the list. Thanks @jsu216
+- fixes #472 fix i18n doesn't work with locale CHINA. Thanks @wyzfzu
+
+
+## 1.0.62 - 2021-10-16
+
+### 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.
+
+## 1.0.61 - 2021-10-09
+
+### Changed
+
+- fixes #461 1.0.60 Expects type To Be Array. Thanks @bartoszm
+- fixes #459 Correcting the ref listeners config in WalkEvent class when fetching the getRefSchema. Thanks @prashanthjos
+
+## 1.0.60 - 2021-09-22
+
+### Changed
+
+- fixes #451 walk method for AnyOfValidator not implemented. Thanks @bartoszm
+- fixes #450 changed from isIntegralNumber to canConvertToExactIntegral to support. Thanks @mohsin-sq
+- fixes #449 Refactor JSON Schema Test Suite tests. Thanks @olegshtch
+- fixes #448 Test CI with JDK 11. Thanks @olegshtch
+- fixes #447 Bump JUnit version to 5.7.2. Thanks @olegshtch
+
+## 1.0.59 - 2021-09-11
+
+### Changed
+
+- fixes #445 JsonValidator: mark preloadJsonSchema as default. Thanks @DaNizz97
+- fixes #443 $ref caching issue. Thanks @prashanthjos
+- fixes #426 Adding custom ValidatorTypeCodes. Thanks @adilath18
+
+## 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
+
+### Changed
+
+- fixes #436 Relaxation of the discriminator validation. Thanks FWiesner
+- fixes #435 Added exampleSetFlag to nonValidationKeyword. Thanks @ShubhamRwt
+- fixes #428 A schema with nullable oneOf does not work as expect. Thanks @rongyj
+- fixes #429 Update collector-context.md. Thanks @Petapath
+- fixes #425 Cannot distinguish the "TextNode" and the "ArrayNode" with single value for oneOf. Thanks @rongyj
+
+## 1.0.57 - 2021-07-09
+
+### Added
+
+### Changed
+
+- fixes #423 make sure additionalPropertiesSchema is not null in AdditionalPropertiesValidator. Thanks @flozano
+- fixes #421 Wrong validation of MultipleOfValidator. Thanks @ubergrohman
+- fixes #418 201909 false flag keywords additonalItems and then. Thanks @pgalbraith
+
+
+## 1.0.56 - 2021-07-02
+
+### Added
+
+### Changed
+
+- fixes #416 Circular $ref occurrences with schema.initializeValidators() lead to StackOverflowError. Thanks @FWiesner
+- fixes #414 Simplify the uri format validation regexp. Thanks @vmaurin
+
+## 1.0.55 - 2021-06-23
+
+### Added
+
+### Changed
+
+- fixes #411 uri format regexp is fixed to support empty fragment and query string. Thanks @vmaurin
+
+## 1.0.54 - 2021-06-22
+
+### Added
+
+### Changed
+
+- fixes #408 uri format regexp is validating invalid URI. Thanks @vmaurin
+- fixes #406 Behavior change of $ref resolution. Thanks @FWiesner
+
+## 1.0.53 - 2021-05-19
+
+### Added
+
+### Changed
+
+- fixes #400 Introduce forceHttps flag in JsonSchemaFactory.Builder. Thanks @hisener
+
+## 1.0.52 - 2021-04-13
+
+### Added
+
+### Changed
+
+- fixes #398 Two issues with OpenAPI 3 discriminators. Thanks @FWiesner
+- fixes #396 Implement propertyNames in terms full schema validation. Thanks @JonasProgrammer
+
+## 1.0.51 - 2021-03-30
+
+### Added
+
+### Changed
+
+- fixes #392 NPE due to concurrency bug. Thanks @Keymaster65
+- fixes #391 override default EmailValidator, if set custom email format. Thanks @whirosan
+- fixes #390 Add discriminator support. Thanks @FWiesner
+
+## 1.0.50 - 2021-03-18
+
+### Added
+
+### Changed
+
+- 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 #378 Upgrade Jackson to 2.12.1 and Undertow to 2.2.4.Final
+
+## 1.0.49 - 2021-02-17
+
+### Added
+
+### Changed
+
+- fixes #375 PropertyNames to return validator value on error. Thanks @Eivyses
+- fixes #335 Fixed parallel processing. @Thanks @mweber03
+
+## 1.0.48 - 2021-02-04
+
+### Added
+
+### Changed
+
+- fixes #326 pattern validation for propertyNames. @Thanks @LeifRilbeATG
+- fixes #366 Fast fail issue with One Of Validator. Thanks @Krishna-capone
+
+## 1.0.47 - 2021-01-16
+
+### Added
+
+### Changed
+
+- fixes #368 Fixing Walk Listeners Issues. @Thanks prashanthjos
+- fixes #363 Date-time validation fails depending on local time zone. Thanks @ennoruijters
+
+## 1.0.46 - 2020-12-30
+
+### Added
+
+### Changed
+
+- fixes #362 Date-time validation fails depending on local time zone Thanks @ennoruijters
+- fixes #361 Validation of oneOf depends on schema order @Thanks ennoruijters
+- fixes #360 add four project links to the README.md
+- fixes #354 OneOf validator is not throwing valid error if any of the child nodes has invalid schemas Thanks @prubdeploy
+- fixes #351 Add anchor and deprecated as NonValidationKeywords for v2019-09 draft Thanks @anicolasgar
+- fixes #340 YAML source location handling Thanks @ascertrobw
+
+## 1.0.45 - 2020-11-21
+
+### Added
+
+### Changed
+
+- fixes #350 Add builder method that accepts iterable Thanks @wheelerlaw
+- fixes #347 NPE at JsonSchema.combineCurrentUriWithIds(JsonSchema.java:90) Thanks @wheelerlaw
+- fixes #346 Update docs about javaSemantics flag Thanks @oguzhanunlu
+- fixes #345 optimize imports in the src folder
+- fixes #343 Improve type validation of numeric values Thanks @oguzhanunlu
+- fixes #341 Add contentMediaType, contentEncoding and examples as a NonValidationKeyword Thanks @jonnybbb
+- fixes #337 JSON Schema Walk Changes Thanks @prashanthjos
+
+## 1.0.44 - 2020-10-20
+
+### Added
+
+### Changed
+- fixes #336 Adding walk capabilities to networknt. Thanks @prashanthjos
+- fixes #332 Bump junit from 4.12 to 4.13.1
+- fixes #329 JRuby Joni dependency and its dependencies
+- fixes #328 Add $comment as a NonValidationKeyword for v7 and v2019 drafts. Thanks @kmalski
+- fixes #324 Generate module-info, fix build on JDK11 Thanks @handcraftedbits
+- fixes #323 FIX: potential duplicate log entry due to race condition Thanks @kkonrad
+- fixes #319 resolve a java doc warning in CollectorContext
+
+## 1.0.43 - 2020-08-10
+
+### Added
+
+### Changed
+
+- fixes #317 Compatible with Jackson 2.9.x. Thanks @pan3793
+- fixes #315 implement propertyNames validator for v6, v7 and v2019-09
+
+## 1.0.42 - 2020-06-30
+
+### Added
+
+### Changed
+
+- fixes #311 Split the PatternValidator into 2 classes. Thanks @Buuhuu
+
+## 1.0.41 - 2020-06-25
+
+### Added
+
+### Changed
+
+- fixes #307 Make runtime dependency to org.jruby.joni:joni optional. Thanks @Buuhuu
+- fixes #305 Automatically determine schema version from schema file. Thanks @Subhajitdas298
+- fixes #297 ValidationContext using is not correct in UUIDValidator. Thanks @qiunju
+
+
+## 1.0.40 - 2020-05-27
+
+### Added
+
+### Changed
+
+- fixes #294 fixes unknownMetaSchema error with normalized URI
+
+## 1.0.39 - 2020-04-28
+
+### Added
+
+### Changed
+
+- fixes #289 Adding getAll method on CollectorContext class. Thanks @prashanthjos
+
+## 1.0.38 - 2020-04-12
+
+### Added
+
+### Changed
+
+- fixes #281 EmailValidator use ValidatorTypeCode Datetime
+
+## 1.0.37 - 2020-04-06
+
+### Added
+
+### Changed
+
+- fixes #280 NullPointerException in regex pattern validation if no SchemaValidatorsConfig is passed. Thanks @waizuwolf
+
+## 1.0.36 - 2020-03-22
+
+### Added
+
+### Changed
+
+- fixes #273 make the getInstance() deprecated
+- fixes #258 Cyclic dependencies result in StackOverflowError. Thanks @francesc79
+
+## 1.0.35 - 2020-03-13
+
+### Added
+
+### Changed
+
+- fixes #272 Use ECMA-262 validator when requested. Thanks @eirnym
+
+## 1.0.34 - 2020-03-12
+
+### Added
+
+### Changed
+
+- fixes #268 Collector Context changes to handle simple Objects. Thanks @prashanthjos
+- fixes #266 reformat the code and resolve javadoc warnnings
+
+## 1.0.33 - 2020-03-09
+
+### Added
+
+### Changed
+
+- fixes #264 Handling JSONPointer (URI fragment identifier) with no base uri. Thanks @rzukowski
+- fixes #255 Dereferencing subschemas by $id with $ref in the same file does not seem to work. Thanks @rzukowski
+
+## 1.0.32 - 2020-03-07
+
+### Added
+
+### Changed
+
+- fixes #260 Changes for adding collector context. Thanks @prashanthjos
+
+## 1.0.31 - 2020-02-21
+
+### Added
+
+### Changed
+
+- fixes #226 Implements contains. Thanks @Asamsig
+
+## 1.0.30 - 2020-02-11
+
+### Added
+
+### Changed
+
+- fixes #244 Android 6 support. Thanks @msattel
+- fixes #247 Resolve schema id from the schema document (for v6 and above). Thanks @martin-sladecek
+- fixes #243 Improve accuracy of rounding with multipleOf. Thanks @seamusv
+- fixes #242 add customized fetcher and meta schema doc
+
+## 1.0.29 - 2019-12-16
+
+### Added
+
+### Changed
+
+- 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
+- enable const validator test for v6
+- fixes #224 support boolean schema for the item validator
+- fixes #222 add document for URL mapping
+
+## 1.0.28 - 2019-11-25
+
+### Added
+
+### Changed
+
+- fixes #219 Fix for oneOf when not all properties are matched. Thanks @aznan2
+
+## 1.0.27 - 2019-11-18
+
+### Added
+
+### Changed
+
+- fixes #216 Fix remote ref to follow redirects. Thanks @andersonf
+- fixes #214 the if-then-else.json is failed in test for V7 and V2019-09. Thanks @andersonf
+- fixes #54 support for draft V6, V7 and V2019-09
+- fixes #211 move the current test cases from tests to draft4 folder in the resource
+
+## 1.0.26 - 2019-11-07
+
+### Added
+
+### Changed
+
+- fixes #208 error when same ref name in different ref files. Thanks @andersonf
+
+## 1.0.25 - 2019-11-06
+
+### Added
+
+### Changed
+
+- fixes #206 IF-THEN-ELSE Conditional (Draft 7). Thanks @andersonf
+
+## 1.0.24 - 2019-10-28
+
+### Added
+
+### Changed
+
+- fixes #203 String for Number should fail with the default SchemaValidatorsConfig
+
+## 1.0.23 - 2019-10-28
+
+### Added
+
+### Changed
+
+- fixes #199 More than a million validation errors crashes the application. Thanks @khiftikhar
+
+
+## 1.0.22 - 2019-10-22
+
+### Added
+
+### Changed
+
+- fixes #200 Use with obfuscation.Thanks @complex1ty
+
+## 1.0.21 - 2019-10-17
+
+### Added
+
+### Changed
+
+- fixes #192 upgrade jackson to 2.9.10
+- fixes #190 OneOfValidator cannot validate object with multiple properties.Thanks @ddobrin
+- fixes #188 couldnot validate the email format in json schema
+- fixes #187 SchemaValidatorsConfig not propagated
+
+## 1.0.20 - 2019-09-10
+
+### Added
+
+### Changed
+
+- fixes #183 Validation error when field is nullable and consumer sends in a null value. Thanks @ddobrin
+- fixes #185 Validation issue in oneOf when elements have optional fields. Thanks @ddobrin
+
+## 1.0.19 - 2019-08-13
+
+### Added
+
+### Changed
+
+- fixes #182 Jackson-databind vulnerability version update
+- fixes #180 Stack overflow when using recursive references, $ref. Thanks @davidvisiedo
+- fixes #96 stackOverflowError loading schema file. Thanks @davidvisiedo
+- fixes #44 Validator hang on validation. Thanks @davidvisiedo
+- fixes #28 Validator hangs on large json data files. Thanks @davidvisiedo
+- fixes #13 Cannot get the validation result with self-reference schema. Thanks @davidvisiedo
+- fixes #177 OneOf Validator Incorrectly Failing. Thanks @jawaff
+
+## 1.0.18 - 2019-07-29
+
+### Added
+
+### Changed
+
+- fixes #173 AnyOfValidator ignores all previous validations errors if any of the type does not match. Thanks @grssam
+
+## 1.0.17 - 2019-07-20
+
+### Added
+
+### Changed
+
+- fixes #174 Insights into performance gains of tuning min/max validators. Thanks @kosty
+- fixes #171 Support minimum/maximum on quoted numerals. Thanks @kosty
+
+## 1.0.16 - 2019-06-24
+
+### Added
+
+### Changed
+
+- fixes #166 Allow using URN and not just URLs. Thanks @jawaff
+
+## 1.0.15 - 2019-06-14
+
+### Added
+
+### Changed
+
+- fixes #160 when schema type is integer but max/min value is a float point number. Thanks @BalloonWen
+
+## 1.0.14 - 2019-06-06
+
+### Added
+
+### Changed
+
+- fixes #163 update typeLoose to false as before merging the PR 141
+- fixes #162 bump up java version to 1.8
+- fixes #141 Improved Ref Validator. Thanks @jawaff
+
+## 1.0.13 - 2019-06-05
+
+### Added
+
+### Changed
+
+- fixes #158 date-time format should consider colon in timezone optional. Thanks @chuwy
+
+## 1.0.12 - 2019-05-30
+
+### Added
+
+### Changed
+
+- fixes #155 Fix date-time validation. Thanks @jiachen1120
+
+## 1.0.11 - 2019-05-28
+
+### Added
+
+### Changed
+
+- fixes #151 add validation for string type uuid. Thanks @chenyan71
+
+## 1.0.10 - 2019-05-22
+
+### Added
+
+### Changed
+
+- fixes #138 validation of date fields. Thanks @jiachen1120
+
+## 1.0.9 - 2019-05-21
+
+### Added
+
+### Changed
+
+- fixes #147 Fails to validate MIN and MAX when number type is converted to BigInteger. Thanks @jiachen1120
+
+## 1.0.8 - 2019-05-17
+
+### Added
+
+### Changed
+
+- fixes #145 Fix bug parsing array query params when only one item present. Thanks @jiachen1120
+- fixes #142 validation for enum object type. Thanks @jiachen1120
+- fixes #136 Maps of URLs can have performance impacts. Thanks @rhwood
+- fixes #134 $ref external schema references do not use URL mappings. Thanks @rhwood
+
+## 1.0.7 - 2019-04-29
+
+### Added
+
+### Changed
+
+- fixes #140 Convert double to BigDecimal in MultipleOfValidator to make the validation more accurate. Thanks @jiachen1120
+
+## 1.0.6 - 2019-04-10
+
+### Added
+
+### Changed
+
+- fixes #132 minimum/maximum validation of integral numbers prone to overflow. Thanks @kosty
+- fixes #123 Add link to Javadocs. Thanks @rhwood
+
+## 1.0.5 - 2019-04-01
+
+### Added
+
+### Changed
+
+- fixes #127 update license copyright and add NOTICE
+- fixes #125 feat: Add URL mappings. Thanks @rhwood
+
+## 1.0.4 - 2019-03-14
+
+### Added
+
+### Changed
+
+- fixes #119 Almost JSON-spec compliant validation of numeric values. Thanks @kosty
+- fixes #120 Update the version in the README.md file. Thanks @chenyan71
+
+## 1.0.3 - 2019-02-10
+
+### Added
+
+### Changed
+
+- fixes #116 Fail to validate numeric and Integer in TypeValidator. Thanks @jiachen1120
+
+## 1.0.2 - 2019-02-05
+
+### Added
+
+### Changed
+
+- fixes #114 LocalDateTime validation error. Thanks @chenyan71
+- fixes #113 Fixed validation for path parameters and query parameters. Thanks @jiachen1120
+
+## 1.0.1 - 2019-01-10
+
+### Added
+
+### Changed
+- fixes #112 AnyOfValidator: only return expectedTypeList if not empty. Thanks @c14s
+- fixes #111 Validation failure for optional field in a schema - in the PropertiesValidator. Thanks @ddobrin
+
+## 0.1.26 - 2018-12-24
+
+### Added
+
+### Changed
+- fixes #110 Validation Error when using OneOf in OpenAPI specs. Thanks @ddobrin
+
+## 0.1.25 - 2018-12-12
+
+### Added
+
+### Changed
+- fixes #108 v0.1.24 error on array union type. Thanks @nitin456
+- fixes #107 Fix for perfomance issue Thanks @nitin456
+- fixes #106 Fix for enable loose type validator for REST Thanks @BalloonWen
+
+## 0.1.24 - 2018-11-21
+
+### Added
+
+### Changed
+- fixes #105 temporary fix to performance issue. Thanks @nitin456
+
+## 0.1.23 - 2018-10-02
+
+### Added
+
+### Changed
+- fixes #103 Boolean type validation for the string type is incorrect
+
+## 0.1.22 - 2018-09-11
+
+### Added
+
+### Changed
+- fixes #101 enhance TypeValidator trying to convert type from TEXT
+
+## 0.1.21 - 2018-08-14
+
+### Added
+
+### Changed
+- fixes #94 Fix min/max error message of integer fields displayed as doubles. Thanks @NicholasAzar
+- fixes #93 Adding support for nullable fields. Thanks @NicholasAzar
+
+## 0.1.20 - 2018-07-30
+
+### Added
+
+### Changed
+- fixes #85 Update version in maven dependnecy sample. Thanks @banterCZ
+- fixes #89 Added example for custom keywords in tests. Thanks @Klas Kalaß
+- fixes #90 Remove unused dependency to slf4j-ext due to security issue. Thanks @Thorbias
+- fixes #91 update one test case to ensure compatibility of Java 6
+- fixes #92 rollback type validator for null value as it is against spec.
+
+## 0.1.19 - 2018-04-07
+
+### Added
+
+### Changed
+- fixes #84 remove Java 8 optional to ensure that this library can be Java 6 compatible. Thanks @johnygeorge
+- fixes #81 java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/Optional. Thanks @johnygeorge
+- fixes #83 upgrade to undertow 1.4.23.Final in sync with other repo
+
+## 0.1.18 - 2018-04-04
+
+### Added
+
+### Changed
+- Fixes #80 upgrade to jackson 2.9.5 and undertow 1.4.20.Final
+- Fixes #77 One of was broken - it did not fail when there were no valid schemas. Thanks @kkalass
+- Fixes #76 Make remaining JsonSchema constructors public. Thanks @kkalass
+
+## 0.1.17 - 2018-03-09
+
+### Added
+
+### Changed
+- Fixes #72 build JAR with OSGi support. Thanks @lichtin
+- Fixes #71 Github Quickstart section out-of-date. Thanks @lichtin
+
+## 0.1.16 - 2018-03-03
+
+### Added
+
+### Changed
+- Fixes #62 Correct behavior when both allOf and type are present. Thanks @ehrmann
+- Fixes #70 Minor optimizations. Thanks @ehrmann
+
+## 0.1.15 - 2018-02-16
+
+### Added
+
+### Changed
+- Fixes #65 enhance day validation regex for date format. Thanks @chenyan71
+
+
+## 0.1.14 - 2018-02-14
+
+### Added
+
+### Changed
+- Fixes #64 Add simple tests for ValidatorTypeCode. Thanks @ehrmann
+- Fixes #61 Restore validator type code from value. Thanks @ehrmann
+
+## 0.1.13 - 2017-12-10
+
+### Added
+
+### Changed
+- Fixes #53 Optimization for OneOf. Thanks @kkalass
+- Fixes #52 References that cannot be resolved should be treated as an error. Thanks @kkalass
+- Fixes #51 Resolve sub schema node only if really needed. Thanks @kkalass
+
+## 0.1.12 - 2017-11-23
+
+### Added
+
+### Changed
+- Fixes #50 Support custom meta schemas with custom keywords and formats. Thanks @kkalass
+- 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
+
+
+## 0.1.11 - 2017-10-18
+### Added
+- Fixes #43 Load reference schemas from classpath is supported. Thanks @kenwa
+
+### Changed
+
+## 0.1.10 - 2017-07-22
+### Added
+
+### Changed
+- Release the library in Java 6 as there are still developer using it. Thanks @basinilya
+
+## 0.1.9 - 2017-07-03
+### Added
+
+### Changed
+- Fixes #37 adding relative $ref url. Thanks @eskabetxe
+
+## 0.1.8 - 2017-06-17
+### Added
+
+### Changed
+- Recursive load fix #36 Thanks @thekensta
+
+## 0.1.7 - 2017-04-26
+### Added
+
+### Changed
+- Fixes #25 Enable Undertow server to test remote schemas
+- Add test with id schema as url Thanks @eskabetxe
+- If schema not valid to oneOf, added all errors. Thanks @eskabetxe
+
+## 0.1.6 - 2017-04-03
+### Added
+
+### Changed
+- Fixes #20 added default messages to empty messages on ValidatorTypeCode. Thanks @eskabetxe
+- Fixes #22 only check subschema if distinct from schema, and minor changes. Thanks @eskabetxe
+- Fixes #24 update dependencies versions. Thanks @eskabetxe
+
+## 0.1.5 - 2017-03-25
+### Added
+
+### Changed
+- Fixes #19 make undertow test scope
+
+## 0.1.4 - 2017-02-06
+### Added
+
+### Changed
+- Fixes #6 Match subsequence instead of entire input sequence. Thanks @mspiegel
+
+## 0.1.3 - 2016-11-03
+### Added
+
+### Changed
+- Sycn with official test suites and documented failed test cases.
+- Fixes #4 MinLength and MaxLength validator for unicode string. Thanks for @dola to point me to the right direction.
+
+## 0.1.2 - 2016-10-20
+### Added
+
+### Changed
+- Broken escaping in pattern for uris [#1](https://github.com/networknt/json-schema-validator/issues/1)
+
+
+## 0.1.1 - 2016-08-16
+### Added
+- First version
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ 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 "[]"
+ 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
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ 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.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..ec791ed
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,15 @@
+name: "json-schema-validator"
+description:
+ "This is a Java implementation of the JSON Schema Core Draft v4, v6, v7, "
+ "v2019-09 and v2020-12 specification for JSON schema validation."
+
+third_party {
+ identifier {
+ type: "Git"
+ value: "https://github.com/networknt/json-schema-validator"
+ primary_source: true
+ }
+ version: "1.4.0"
+ last_upgrade_date { year: 2024 month: 3 day: 22 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..79b3f09
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,13 @@
+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
+// ------------------------------------------------------------------
+
+Apache Commons Validator
+Copyright 2001-2023 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2e8f086
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/core:main:/janitors/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c1297f0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,572 @@
+[Stack Overflow](https://stackoverflow.com/questions/tagged/light-4j) |
+[Google Group](https://groups.google.com/forum/#!forum/light-4j) |
+[Gitter Chat](https://gitter.im/networknt/json-schema-validator) |
+[Subreddit](https://www.reddit.com/r/lightapi/) |
+[Youtube](https://www.youtube.com/channel/UCHCRMWJVXw8iB7zKxF55Byw) |
+[Documentation](https://doc.networknt.com/library/json-schema-validator/) |
+[Contribution Guide](https://doc.networknt.com/contribute/) |
+
+[![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](http://json-schema.org/latest/json-schema-core.html) specification for JSON schema validation. This implementation supports [Customizing Meta-Schemas, Vocabularies, Keywords and Formats](doc/custom-meta-schema.md).
+
+In addition, it also works for OpenAPI 3.0 request/response validation with some [configuration flags](doc/config.md). 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 spec](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-json-schema-validator)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/dialects/draft2020-12)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/dialects/draft2019-09)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/dialects/draft7)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/dialects/draft6)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-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
+
+This should be the fastest Java JSON Schema Validator implementation.
+
+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).
+
+Note that the benchmark results are highly dependent on the input data workloads and schemas used for the validation.
+
+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
+
+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.
+
+##### 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
+```
+
+#### 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:4703 (100.0%) o:2369 (100.0%)<br>fail: r:0 (0.0%) o:1 (0.0%) | | pass: r:600 (100.0%) o:251 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:796 (100.0%) o:318 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:880 (100.0%) o:541 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1201 (100.0%) o:625 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1226 (100.0%) o:634 (99.8%)<br>fail: r:0 (0.0%) o:1 (0.2%) |
+
+* Note that this uses the ECMA 262 Validator option turned on for the `pattern` 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.
+
+#### 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.
+
+#### Minimal Dependencies
+
+Following the design principle of the Light Platform, this library has minimal dependencies to ensure there are no dependency conflicts when using it.
+
+##### Required Dependencies
+
+The following are the dependencies that will automatically be included when this library is included.
+
+```xml
+<dependency>
+ <!-- Used for logging -->
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${version.slf4j}</version>
+</dependency>
+
+<dependency>
+ <!-- Used to process JSON -->
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${version.jackson}</version>
+</dependency>
+
+<dependency>
+ <!-- Used to process YAML -->
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>${version.jackson}</version>
+</dependency>
+
+<dependency>
+ <!-- Used to validate RFC 3339 date and date-time -->
+ <groupId>com.ethlo.time</groupId>
+ <artifactId>itu</artifactId>
+ <version>${version.itu}</version>
+</dependency>
+```
+
+##### 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
+<!-- This is required when setting setEcma262Validator(true) -->
+<dependency>
+ <!-- Used to validate ECMA 262 regular expressions -->
+ <groupId>org.jruby.joni</groupId>
+ <artifactId>joni</artifactId>
+ <version>${version.joni}</version>
+ <optional>true</optional>
+</dependency>
+```
+
+##### 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
+<dependency>
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ </exclusion>
+ </exclusions>
+</dependency>
+```
+
+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
+<dependency>
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.ethlo.time</groupId>
+ <artifactId>itu</artifactId>
+ </exclusion>
+ </exclusions>
+</dependency>
+```
+
+#### 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.
+
+## 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/)
+
+## Usage
+
+### Adding the dependency
+
+This package is available on Maven central.
+
+#### Maven:
+
+```xml
+<dependency>
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <version>1.4.0</version>
+</dependency>
+```
+
+#### Gradle:
+
+```java
+dependencies {
+ implementation(group: 'com.networknt', name: 'json-schema-validator', version: '1.4.0');
+}
+```
+
+### 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<String, String> 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<String, String> 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 2012-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 config = new SchemaValidatorsConfig();
+// By default JSON Path is used for reporting the instance location and evaluation path
+config.setPathType(PathType.JSON_POINTER);
+// By default the JDK regular expression implementation which is not ECMA 262 compliant is used
+// Note that setting this to true requires including the optional joni dependency
+// config.setEcma262Validator(true);
+
+// 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<ValidationMessage> assertions = schema.validate(input, InputFormat.JSON, executionContext -> {
+ // By default since Draft 2019-09 the format keyword only generates annotations and not assertions
+ executionContext.getConfig().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 config = new SchemaValidatorsConfig();
+// By default JSON Path is used for reporting the instance location and evaluation path
+config.setPathType(PathType.JSON_POINTER);
+// By default the JDK regular expression implementation which is not ECMA 262 compliant is used
+// Note that setting this to true requires including the optional joni dependency
+// config.setEcma262Validator(true);
+
+// 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<ValidationMessage> assertions = schema.validate(input, InputFormat.JSON, executionContext -> {
+ // By default since Draft 2019-09 the format keyword only generates annotations and not assertions
+ executionContext.getConfig().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.
+| Instance Node | The `JsonNode` pointed to by the Instance Location.
+| 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
+
+
+#### 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 = new SchemaValidatorsConfig();
+config.setPathType(PathType.JSON_POINTER);
+config.setFormatAssertionsEnabled(true);
+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`
+
+### Schema Validators Configuration
+
+| Name | Description | Default Value
+|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------
+| `pathType` | The path type to use for reporting the instance location and evaluation path. Set to `PathType.JSON_POINTER` to use JSON Pointer. | `PathType.DEFAULT`
+| `ecma262Validator` | Whether to use the ECMA 262 `joni` library to validate the `pattern` keyword. This requires the dependency to be manually added to the project or a `ClassNotFoundException` will be thrown. | `false`
+| `executionContextCustomizer` | This can be used to customize the `ExecutionContext` generated by the `JsonSchema` for each validation run. | `null`
+| `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`
+| `messageSource` | This is used to retrieve the locale specific messages. | `DefaultMessageSource.getInstance()`
+| `locale` | The locale to use for generating messages in the `ValidationMessage`. | `Locale.getDefault()`
+| `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`
+
+## 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 automatically preload all the validators needed and resolve references. 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.
+
+The `JsonSchema` created from the factory 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.
+
+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.
+
+
+## [Quick Start](doc/quickstart.md)
+
+## [Customizing Schema Retrieval](doc/schema-retrieval.md)
+
+## [Customizing Meta-Schemas, Vocabularies, Keywords and Formats](doc/custom-meta-schema.md)
+
+## [Validators](doc/validators.md)
+
+## [Configuration](doc/config.md)
+
+## [Specification Version](doc/specversion.md)
+
+## [YAML Validation](doc/yaml.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)
+
+## [Multiple Language](doc/multiple-language.md)
+
+## [MetaSchema Validation](doc/metaschema-validation.md)
+
+## [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.
+
+If you have a project using this library, please submit a PR to add your project below.
+
+## 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)
+
+[@prashanth-chaitanya](https://github.com/prashanth-chaitanya)
+
+[@fdutton](https://github.com/fdutton)
+
+[@valfirst](https://github.com/valfirst)
+
+[@BalloonWen](https://github.com/BalloonWen)
+
+[@jiachen1120](https://github.com/jiachen1120)
+
+[@ddobrin](https://github.com/ddobrin)
+
+[@eskabetxe](https://github.com/eskabetxe)
+
+[@ehrmann](https://github.com/ehrmann)
+
+[@prashanthjos](https://github.com/prashanthjos)
+
+[@Subhajitdas298](https://github.com/Subhajitdas298)
+
+[@FWiesner](https://github.com/FWiesner)
+
+[@rhwood](https://github.com/rhwood)
+
+[@jawaff](https://github.com/jawaff)
+
+[@nitin1891](https://github.com/nitin1891)
+
+
+For all contributors, please visit https://github.com/networknt/json-schema-validator/graphs/contributors
+
+If you are a contributor, please join the [GitHub Sponsors](https://github.com/sponsors) and switch the link to your sponsors dashboard via a PR.
+
+## Sponsors
+
+
+### Individual Sponsors
+
+
+### Corporation Sponsors
+
+
+
diff --git a/doc/collector-context.md b/doc/collector-context.md
new file mode 100644
index 0000000..38e74e5
--- /dev/null
+++ b/doc/collector-context.md
@@ -0,0 +1,107 @@
+### CollectorContext
+
+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.
+
+The `CollectorContext` and `Collector` classes are designed to work with this use case.
+
+#### How to use CollectorContext
+
+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.
+
+```java
+CollectorContext collectorContext = executionContext.getCollectorContext();
+collectorContext.add(SAMPLE_COLLECTOR_NAME, new Collector<List<String>>() {
+ @Override
+ public List<String> collect() {
+ List<String> references = new ArrayList<String>();
+ 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")
+```
+
+To use the `CollectorContext` while validating, the `validateAndCollect` method has to be invoked on the `JsonSchema` class.
+This method returns a `ValidationResult` that contains the errors encountered during validation and a `ExecutionContext` instance that contains the `CollectorContext`.
+Objects constructed by collectors or directly added to `CollectorContext` can be retrieved from `CollectorContext` by using the name they were added with.
+
+To collect across multiple validation runs, the `CollectorContext` needs to be explicitly reused by passing the `ExecutionContext` as a parameter to the validation.
+
+```java
+ValidationResult validationResult = jsonSchema.validateAndCollect(jsonNode);
+ExecutionContext executionContext = validationResult.getExecutionContext();
+CollectorContext collectorContext = executionContext.getCollectorContext();
+List<String> contextValue = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
+
+// Do something with contextValue
+...
+
+// To collect more information for subsequent runs reuse the context
+validationResult = jsonSchema.validateAndCollect(executionContext, jsonNode);
+```
+
+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 the framework at the end of validation to return the data that was collected.
+
+```java
+class CustomCollector implements Collector<List<String>> {
+
+ List<String> returnList = new ArrayList<>();
+
+ private Map<String, String> referenceMap = null;
+
+ public CustomCollector() {
+ referenceMap = getDatasourceMap();
+ }
+
+ @Override
+ public List<String> collect() {
+ return returnList;
+ }
+
+ @Override
+ public void combine(Object object) {
+ returnList.add(referenceMap.get((String) object));
+ }
+}
+
+CollectorContext collectorContext = executionContext.getCollectorContext();
+if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
+ collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector());
+}
+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`.
+
+```java
+// Returns Collector before validation is done.
+Collector<List<String>> collector = collectorContext.get(SAMPLE_COLLECTOR);
+
+// Returns data collected by Collector after the validation is done.
+List<String> 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.
+
+```java
+CollectorContext collectorContext = executionContext.getCollectorContext();
+// If collector name is not added to context add one.
+if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
+ collectorContext.add(SAMPLE_COLLECTOR, new ArrayList<String>());
+}
+// In this case we are adding a list to CollectorContext.
+List<String> returnList = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
+
+``` \ No newline at end of file
diff --git a/doc/compatibility.md b/doc/compatibility.md
new file mode 100644
index 0000000..135c38d
--- /dev/null
+++ b/doc/compatibility.md
@@ -0,0 +1,170 @@
+## Compatibility with JSON Schema versions
+
+[![Supported spec](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-json-schema-validator)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/dialects/draft2020-12)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/dialects/draft2019-09)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/dialects/draft7)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/dialects/draft6)
+[![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fjava-json-schema-validator%2Fcompliance%2Fdraft4.json)](https://bowtie.report/#/dialects/draft4)
+
+The `pattern` 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.
+
+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 29 Jan 2024 using version 1.3.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:4703 (100.0%) o:2369 (100.0%)<br>fail: r:0 (0.0%) o:1 (0.0%) | | pass: r:600 (100.0%) o:251 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:796 (100.0%) o:318 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:880 (100.0%) o:541 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1201 (100.0%) o:625 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1226 (100.0%) o:634 (99.8%)<br>fail: r:0 (0.0%) o:1 (0.2%) |
+
+### Legend
+
+| 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 | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 |
+| $dynamicAnchor | 🚫 | 🚫 | 🚫 | 🚫 | 🟢 |
+| $dynamicRef | 🚫 | 🚫 | 🚫 | 🚫 | 🟢 |
+| $id | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| $recursiveAnchor | 🚫 | 🚫 | 🚫 | 🟢 | 🚫 |
+| $recursiveRef | 🚫 | 🚫 | 🚫 | 🟢 | 🚫 |
+| $ref | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| $vocabulary | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 |
+| additionalItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| additionalProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| allOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| anyOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
+| const | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 |
+| contains | 🚫 | 🟢 | 🟢 | 🟢 | 🟢 |
+| contentEncoding | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 |
+| contentMediaType | 🚫 | 🚫 | 🟢 | 🟢 | 🟢 |
+| contentSchema | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 |
+| definitions | 🟢 | 🟢 | 🟢 | 🚫 | 🚫 |
+| defs | 🚫 | 🚫 | 🚫 | 🟢 | 🟢 |
+| 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 [joni](https://github.com/jruby/joni). This can be configured by setting `setEcma262Validator` to `true`.
+
+This also requires adding the `joni` dependency.
+
+```xml
+<dependency>
+ <!-- Used to validate ECMA 262 regular expressions -->
+ <groupId>org.jruby.joni</groupId>
+ <artifactId>joni</artifactId>
+ <version>${version.joni}</version>
+</dependency>
+```
+
+#### 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. \ No newline at end of file
diff --git a/doc/config.md b/doc/config.md
new file mode 100644
index 0000000..c4ab329
--- /dev/null
+++ b/doc/config.md
@@ -0,0 +1,69 @@
+### 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.
+
+#### How to use config
+
+When you create a `JsonSchema` instance from the `JsonSchemaFactory`, you can pass an object of SchemaValidatorsConfig as the second parameter.
+
+```java
+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. 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.
+
+* strictness
+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. 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).
+
+* 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).
+
+* 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). By default, path expressions use a
+`PathType.LEGACY` format which is close to JSONPath but does not escape reserved characters.
diff --git a/doc/cust-msg.md b/doc/cust-msg.md
new file mode 100644
index 0000000..c6c9613
--- /dev/null
+++ b/doc/cust-msg.md
@@ -0,0 +1,113 @@
+# Custom Message Support
+Users can add their own custom messages for schema validation using the instructions in this page.
+
+The json schema itself has a place for the customised 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": {
+ "firstName": {
+ "type": "string",
+ "description": "The person's first name."
+ },
+ "foo": {
+ "type": "array",
+ "maxItems": 3
+ }
+ },
+ "message": {
+ "maxItems" : "MaxItem must be 3 only",
+ "type" : "Invalid type"
+ }
+}
+```
+### Example 2 :
+To keep custom messages distinct for each type, one can even give them in each property.
+```json
+{
+ "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"
+ }
+ }
+ }
+}
+```
+### Example 3 :
+For the keywords `required` and `dependencies`, different messages can be specified for different properties.
+
+```json
+{
+ "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"
+ }
+ }
+}
+```
+### 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.
+
+```json
+{
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "number"
+ },
+ "bar": {
+ "type": "string"
+ }
+ },
+ "required": ["foo", "bar"],
+ "message": {
+ "type" : "should be an object",
+ "required": {
+ "foo" : "{0}: ''foo'' is required",
+ "bar" : "{0}: ''bar'' is required"
+ }
+ }
+}
+```
+
+## Format
+```json
+"message": {
+ [validationType] : [customMessage]
+ }
+```
+Users can express custom message in the **'message'** field.
+The **'validation type'** should be the key and the **'custom message'** should be the value.
+
+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 [PR1](https://github.com/networknt/json-schema-validator/pull/438) and [PR2](https://github.com/networknt/json-schema-validator/pull/632)
diff --git a/doc/custom-meta-schema.md b/doc/custom-meta-schema.md
new file mode 100644
index 0000000..7854052
--- /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<ValidationMessage> 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 0000000..f19dd96
--- /dev/null
+++ b/doc/duration.md
@@ -0,0 +1,31 @@
+## 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</li>
+* 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 = new SchemaValidatorsConfig();
+config.setStrict("duration", false);
+JsonSchema jsonSchema = JsonSchemaFactory.getInstance().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
new file mode 100644
index 0000000..b7869a2
--- /dev/null
+++ b/doc/ecma-262.md
@@ -0,0 +1,29 @@
+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.
+
+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.
+
+### Which one to choose?
+
+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`.
+
+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.
+
+### How to switch?
+
+Here is the test case that shows how to pass a config object to use the ECMA-262 library.
+
+```java
+@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<ValidationMessage> errors = schema.validate(node);
+ Assert.assertEquals(errors.size(), 0);
+}
+```
+
+
diff --git a/doc/metaschema-validation.md b/doc/metaschema-validation.md
new file mode 100644
index 0000000..5c49ddf
--- /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<ObjectNode, Set<SchemaValidationMessage>> 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/multiple-language.md b/doc/multiple-language.md
new file mode 100644
index 0000000..9288150
--- /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<ValidationMessage> 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
new file mode 100644
index 0000000..80134e6
--- /dev/null
+++ b/doc/openapi-discriminators.md
@@ -0,0 +1,190 @@
+[//]: # (Copyright 2021, Oracle and/or its affiliates.)
+
+## OpenAPI 3.x discriminator support
+
+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() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true); // defaults to false
+ }
+ }
+ ```
+2. Use the configured `SchemaValidatorsConfig` with the `JsonSchemaFactory` when creating the `JsonSchema`
+ ```java
+ class Demo{
+ void demo() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true); // defaults to false
+ 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
+
+## Scope of Support
+
+Discriminators are unfortunately somewhat vague in their definition, especially in regard to JSON Schema validation. So, only
+those parts that are indisputable are considered at this moment.
+
+### Supported:
+
+* Polymorphism using `allOf` and `anyOf` with implicit and explicit `mapping`
+* `discriminator` on base types and types derived
+ thereof `A(with base discriminator) -> B(with optional additive discriminator) -> C(with optional additive discriminator)`
+
+### Not supported:
+
+* `propertyName` redefinition is prohibited on additive discriminators
+* `mapping` key redefinition is also prohibited on additive discriminators
+* the specification indicates that inline properties should be ignored.
+ So, this example would respect `foo`
+ ```yaml
+ allOf:
+ - $ref: otherSchema
+ - type: object
+ properties:
+ foo:
+ type: string
+ required: ["foo"]
+ ```
+ while
+ ```yaml
+ properties:
+ foo:
+ type: string
+ required: ["foo"]
+ allOf:
+ - $ref: otherSchema
+ ```
+ should ignore `foo`. **Ignoring `foo` in the second example is currently not implemented**
+* You won't get a warning if your `discriminator` uses a field for `propertyName` that is not `required`
+
+## Schema Examples
+
+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)
+
+#### Example:
+
+```json
+{
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type"
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
+```
+
+#### Here the default mapping key for `BedRoom` is overridden with `bed` from `Room`
+
+```json
+{
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type",
+ "mapping": {
+ "bed": "#/components/schemas/BedRoom"
+ }
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
+```
diff --git a/doc/quickstart.md b/doc/quickstart.md
new file mode 100644
index 0000000..73c7be4
--- /dev/null
+++ b/doc/quickstart.md
@@ -0,0 +1,65 @@
+## Quick Start
+
+To use the validator, we need to have the `JsonSchema` loaded and cached.
+
+For simplicity the following test loads a schema from a `String` or `JsonNode`. Note that loading a schema in this manner is not recommended as a relative `$ref` will not be properly resolved as there is no base IRI.
+
+The preferred method of loading a schema is by using a `SchemaLocation` and by configuring the appropriate `SchemaMapper` and `SchemaLoader` on the `JsonSchemaFactory`.
+
+```java
+package com.example;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+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.*;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+public class SampleTest {
+ @Test
+ void schemaFromString() throws JsonMappingException, JsonProcessingException {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ /*
+ * 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],\"enumErrorCode\":\"Not in the list\"}");
+ Set<ValidationMessage> errors = schemaFromString.validate("7", InputFormat.JSON);
+ assertEquals(1, errors.size());
+ }
+
+ @Test
+ void schemaFromJsonNode() throws JsonMappingException, JsonProcessingException {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ 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 V4 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<ValidationMessage> errors = schemaFromNode.validate("{\"id\": \"2\"}", InputFormat.JSON);
+ assertEquals(1, errors.size());
+ }
+}
+```
diff --git a/doc/schema-retrieval.md b/doc/schema-retrieval.md
new file mode 100644
index 0000000..43842e5
--- /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<String, String> 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<String, String> 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<String, RegistryEntry> 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<String, String> 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<String> 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
new file mode 100644
index 0000000..0943a9f
--- /dev/null
+++ b/doc/specversion.md
@@ -0,0 +1,213 @@
+The library supports V4, V6, V7, V2019-09 and V2020-12 JSON schema specifications. By default, V4 is used for backward compatibility.
+
+### For Users
+
+#### To create a draft V4 JsonSchemaFactory
+
+```java
+ObjectMapper mapper = new ObjectMapper();
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build();
+```
+or with default configuration
+```java
+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.
+
+#### To create a draft V6 JsonSchemaFactory
+
+```java
+ObjectMapper mapper = new ObjectMapper();
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V6)).objectMapper(mapper).build();
+```
+or with default configuration
+```java
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V6));
+```
+
+#### To create a draft V7 JsonSchemaFactory
+
+```java
+ObjectMapper mapper = new ObjectMapper();
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build();
+```
+or with default configuration
+```java
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7));
+```
+
+#### To create a draft 2019-09 JsonSchemaFactory
+
+```java
+ObjectMapper mapper = new ObjectMapper();
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).objectMapper(mapper).build();
+```
+or with default configuration
+```java
+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
+ObjectMapper mapper = new ObjectMapper();
+JsonNode jsonNode = mapper.readTree(/* schema / schema input steam etc. */);
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersionDetector.detect(jsonNode))).objectMapper(mapper).build();
+```
+or with default configuration
+```java
+ObjectMapper mapper = new ObjectMapper();
+JsonNode jsonNode = mapper.readTree(/* schema / schema input steam etc. */);
+JsonSchemaFactory validatorFactory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(jsonNode));
+```
+
+### For Developers
+
+#### 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.
+
+```java
+public enum VersionFlag {
+
+ V4(1<<0),
+ V6(1<<1),
+ V7(1<<2),
+ V201909(1<<3),
+ V202012(1<<4);
+
+```
+
+In the long value, we are using 5 bits now as we are supporting 5 versions at the moment.
+
+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
+
+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 31, which is 11111. This means the validator will be loaded for every version of the specification.
+
+For example.
+
+```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, V2019-09 and V2020-12
+
+```java
+IF_THEN_ELSE("if", "1037", null, IfValidator.class, 28), // V7|V201909|V202012 11100
+```
+
+For exclusiveMaximum, it was introduced from V6
+
+```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.
+
+```java
+public static List<ValidatorTypeCode> getNonFormatKeywords(SpecVersion.VersionFlag versionFlag) {
+ final List<ValidatorTypeCode> result = new ArrayList<ValidatorTypeCode>();
+ for (ValidatorTypeCode keyword: values()) {
+ if (!FORMAT.equals(keyword) && specVersion.getVersionFlags(keyword.versionCode).contains(versionFlag)) {
+ result.add(keyword);
+ }
+ }
+ return result;
+}
+```
+
+#### JsonMetaSchema
+
+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.
+
+```java
+@Deprecated
+public static JsonSchemaFactory getInstance() {
+ return getInstance(SpecVersion.VersionFlag.V4);
+}
+
+public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag) {
+ JsonSchemaVersion jsonSchemaVersion = checkVersion(versionFlag);
+ JsonMetaSchema metaSchema = jsonSchemaVersion.getInstance();
+ return builder()
+ .defaultMetaSchemaIri(metaSchema.getIri())
+ .metaSchema(metaSchema)
+ .build();
+}
+```
+
+#### SpecVersionDetector
+
+This class detects schema version based on the schema tag.
+
+```java
+private static final String SCHEMA_TAG = "$schema";
+
+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().getIri()))
+ return SpecVersion.VersionFlag.V4;
+ else if (schemaUri.equals(JsonMetaSchema.getV6().getIri()))
+ return SpecVersion.VersionFlag.V6;
+ else if (schemaUri.equals(JsonMetaSchema.getV7().getIri()))
+ return SpecVersion.VersionFlag.V7;
+ 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");
+}
+```
+
+### 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, draft2019-09 and draft2020-12 folders.
+
+The existing JsonSchemaTest has been renamed to V4JsonSchemaTest, and the following test classes are added.
+
+```
+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 0000000..506e967
--- /dev/null
+++ b/doc/upgrading.md
@@ -0,0 +1,217 @@
+## 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.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/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%)<br>fail: r:0 (0.0%) o:1 (0.0%) | | pass: r:600 (100.0%) o:251 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:796 (100.0%) o:318 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:880 (100.0%) o:541 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1201 (100.0%) o:625 (100.0%)<br>fail: r:0 (0.0%) o:0 (0.0%) | pass: r:1226 (100.0%) o:634 (99.8%)<br>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/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<JsonNodePath>`
+
+`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
new file mode 100644
index 0000000..d5cab0b
--- /dev/null
+++ b/doc/validators.md
@@ -0,0 +1,106 @@
+This document lists all the validators supported and gives users are guideline on how to use them.
+
+### if-then-else
+
+* Specification(s): draft7
+* Contributor(s): @andersonf
+* Reference: https://json-schema.org/understanding-json-schema/reference/conditionals.html
+* Issues and PRs: https://github.com/networknt/json-schema-validator/pull/206
+
+The `if`, `then` and `else` keywords allow the application of a subschema based on the outcome of another schema, much like the `if/then/else` constructs you’ve probably seen in traditional programming languages.
+
+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
+
+### Custom Validators
+````java
+@Bean
+public JsonSchemaFactory mySchemaFactory() {
+ // base on JsonMetaSchema.V201909 copy code below
+ String URI = "https://json-schema.org/draft/2019-09/schema";
+ String ID = "$id";
+ List<Format> BUILTIN_FORMATS = new ArrayList<Format>(JsonMetaSchema.COMMON_BUILTIN_FORMATS);
+
+ JsonMetaSchema myJsonMetaSchema = new JsonMetaSchema.Builder(URI)
+ .idKeyword(ID)
+ .formats(BUILTIN_FORMATS)
+ .keywords(ValidatorTypeCode.getFormatKeywords(SpecVersion.VersionFlag.V201909))
+ // keywords that may validly exist, but have no validation aspect to them
+ .keywords(Arrays.asList(
+ new NonValidationKeyword("$schema"),
+ new NonValidationKeyword("$id"),
+ new NonValidationKeyword("title"),
+ new NonValidationKeyword("description"),
+ new NonValidationKeyword("default"),
+ new NonValidationKeyword("definitions"),
+ new NonValidationKeyword("$defs") // newly added in 2018-09 release.
+ ))
+ // add your custom keyword
+ .keyword(new GroovyKeyword())
+ .build();
+
+ return new JsonSchemaFactory.Builder().defaultMetaSchemaIri(myJsonMetaSchema.getIri())
+ .metaSchema(myJsonMetaSchema)
+ .build();
+}
+
+public class GroovyKeyword extends AbstractKeyword {
+ private static final Logger logger = LoggerFactory.getLogger(GroovyKeyword.class);
+
+ public GroovyKeyword() {
+ super("groovy");
+ }
+
+ @Override
+ public AbstractJsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
+ // you can read validator config here
+ String config = schemaNode.asText();
+ return new AbstractJsonValidator(this.getValue()) {
+ @Override
+ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+ // you can do validate here
+ logger.info("config:{} path:{} node:{}", config, at, node);
+
+ return Collections.emptySet();
+ }
+ };
+ }
+}
+````
+You can use GroovyKeyword like below:
+````json
+{
+ "type": "object",
+ "properties": {
+ "someProperty": {
+ "type": "string",
+ "groovy": "SomeScript.groovy"
+ }
+ }
+}
+````
+
+### Override Email/UUID/DateTime Validator
+
+In this library, if the format keyword is "email", "uuid", "date", "date-time", default validator provided by the library will be used.
+
+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";
+ String ID = "$id";
+
+ JsonMetaSchema overrideEmailValidatorMetaSchema = new JsonMetaSchema.Builder(URI)
+ .idKeyword(ID)
+ // Override EmailValidator
+ .format(new PatternFormat("email", "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"))
+ .build();
+
+ return new JsonSchemaFactory.Builder().defaultMetaSchemaIri(overrideEmailValidatorMetaSchema.getIri())
+ .metaSchema(overrideEmailValidatorMetaSchema)
+ .build();
+}
+```
diff --git a/doc/walk_flow.png b/doc/walk_flow.png
new file mode 100644
index 0000000..ce2a774
--- /dev/null
+++ b/doc/walk_flow.png
Binary files differ
diff --git a/doc/walkers.md b/doc/walkers.md
new file mode 100644
index 0000000..51736e4
--- /dev/null
+++ b/doc/walkers.md
@@ -0,0 +1,291 @@
+### JSON Schema Walkers
+
+There can be use-cases where we need the capability to walk through the given JsonNode allowing functionality beyond validation like collecting information,handling cross cutting concerns like logging or instrumentation, or applying default values. JSON walkers were introduced to complement the validation functionality this library already provides.
+
+Currently, walking is defined at the validator instance level for all the built-in keywords.
+
+### Walk methods
+
+A new interface is introduced into the library that a Walker should implement. It should be noted that this interface also allows the validation based on shouldValidateSchema parameter.
+
+```java
+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. <br>
+ * <br>
+ * {@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
+ * sub-schemas.
+ *
+ * @param executionContext ExecutionContext
+ * @param node JsonNode
+ * @param rootNode JsonNode
+ * @param instanceLocation JsonNodePath
+ * @param shouldValidateSchema boolean
+ * @return a set of validation messages if shouldValidateSchema is true.
+ */
+ Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema);
+}
+
+```
+
+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
+ default Set<ValidationMessage> 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 validate) {
+ return walk(createExecutionContext(), node, validate);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ Set<ValidationMessage> errors = new LinkedHashSet<>();
+ // Walk through all the JSONWalker's.
+ for (JsonValidator validator : getValidators()) {
+ JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath();
+ try {
+ // 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<ValidationMessage> 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.
+ this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext,
+ evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
+ this, validator, errors);
+ }
+ }
+ return errors;
+ }
+```
+Following code snippet shows how to call the walk method on a JsonSchema instance.
+
+```java
+ValidationResult result = jsonSchema.walk(data, false);
+
+```
+
+walk method can be overridden for select validator's based on the use-case. Currently, walk method has been overridden in PropertiesValidator,ItemsValidator,AllOfValidator,NotValidator,PatternValidator,RefValidator,AdditionalPropertiesValidator to accommodate the walk logic of the enclosed schema's.
+
+### Walk Listeners
+
+Walk listeners allows to execute a custom logic before and after the invocation of a JsonWalker walk method. Walk listeners can be modeled by a WalkListener interface.
+
+```java
+public interface JsonSchemaWalkListener {
+
+ public WalkFlow onWalkStart(WalkEvent walkEvent);
+
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages);
+}
+```
+
+Following is the example of a sample WalkListener implementation.
+
+```java
+private static class PropertiesKeywordListener implements JsonSchemaWalkListener {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
+ JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
+ if (schemaNode.get("title").textValue().equals("Property3")) {
+ return WalkFlow.SKIP;
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) {
+
+ }
+ }
+```
+If the onWalkStart method returns WalkFlow.SKIP, the actual walk method execution will be skipped.
+
+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(),
+ new PropertiesKeywordListener());
+final JsonSchemaFactory schemaFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
+ .build();
+this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
+
+```
+
+There are two kinds of walk listeners, keyword walk listeners and property walk listeners. Keyword walk listeners will be called whenever the given keyword is encountered while walking the schema and JSON node data, for example we have added Ref and Property keyword walk listeners in the above example. Property walk listeners are called for every property defined in the JSON node data.
+
+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());
+final JsonSchemaFactory schemaFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
+ .build();
+this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
+
+```
+
+### Walk Events
+
+An instance of WalkEvent is passed to both the onWalkStart and onWalkEnd methods of the WalkListeners implementations.
+
+A WalkEvent instance captures several details about the node currently being walked along with the schema of the node, Json path of the node and other details.
+
+Following snippet shows the details captured by WalkEvent instance.
+
+```java
+public class WalkEvent {
+ private ExecutionContext executionContext;
+ private JsonSchema schema;
+ private String keyword;
+ private JsonNode rootNode;
+ private JsonNode instanceNode;
+ private JsonNodePath instanceLocation;
+ private JsonValidator validator;
+ ...
+}
+```
+
+### Sample Flow
+
+Given an example schema as shown, if we write a property listener, the walk flow is as depicted in the image.
+
+```json
+{
+
+ "title": "Sample Schema",
+ "definitions" : {
+ "address" :{
+ "street-address": {
+ "title": "Street Address",
+ "type": "string"
+ },
+ "pincode": {
+ "title": "Body",
+ "type": "integer"
+ }
+ }
+ },
+ "properties": {
+ "name": {
+ "title": "Title",
+ "type": "string",
+ "maxLength": 50
+ },
+ "body": {
+ "title": "Body",
+ "type": "string"
+ },
+ "address": {
+ "title": "Excerpt",
+ "$ref": "#/definitions/address"
+ }
+
+ },
+ "additionalProperties": false
+}
+```
+
+![img](walk_flow.png)<!-- .element height="50%" width="50%" -->
+
+
+Few important points to note about the flow.
+
+1. onWalkStart and onWalkEnd are the methods defined in the property walk listener
+2. Anywhere during the flow, onWalkStart can return a WalkFlow.SKIP to stop the walk method execution of a particular "property schema".
+3. onWalkEnd will be called even if the onWalkStart returns a WalkFlow.SKIP.
+4. Walking a property will check if the keywords defined in the "property schema" has any keyword listeners, and they will be called in the defined order.
+ For example in the above schema when we walk through the "name" property if there are any keyword listeners defined for "type" or "maxlength" , they will be invoked in the defined order.
+5. Since we have a property listener defined, When we are walking through a property that has a "$ref" keyword which might have some more properties defined,
+ Our property listener would be invoked for each of the property defined in the "$ref" schema.
+6. As mentioned earlier anywhere during the "Walk Flow", we can return a WalkFlow.SKIP from onWalkStart method to stop the walk method of a particular "property schema" from being called.
+ Since the walk method will not be called any property or keyword listeners in the "property schema" will not be invoked.
+
+
+### Applying defaults
+
+In some use cases we may want to apply defaults while walking the schema.
+To accomplish this, create an ApplyDefaultsStrategy when creating a SchemaValidatorsConfig.
+The input object is changed in place, even if validation fails, or a fail-fast or some other exception is thrown.
+
+Here is the order of operations in walker.
+1. apply defaults
+1. run listeners
+1. validate if shouldValidateSchema is true
+
+Suppose the JSON schema is
+```json
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Schema with default values ",
+ "type": "object",
+ "properties": {
+ "intValue": {
+ "type": "integer",
+ "default": 15,
+ "minimum": 20
+ }
+ },
+ "required": ["intValue"]
+}
+```
+
+A JSON file like
+```json
+{
+}
+```
+
+would normally fail validation as "intValue" is required.
+But if we apply defaults while walking, then required validation passes, and the object is changed in place.
+
+```java
+ JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
+ JsonSchema jsonSchema = schemaFactory.getSchema(SchemaLocation.of("classpath:schema.json"), schemaValidatorsConfig);
+
+ JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json"));
+ ValidationResult result = jsonSchema.walk(inputNode, true);
+ assertThat(result.getValidationMessages(), Matchers.empty());
+ assertEquals("{\"intValue\":15}", inputNode.toString());
+ assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()),
+ Matchers.containsInAnyOrder("$.intValue: must have a minimum value of 20."));
+```
diff --git a/doc/yaml-line-numbers.md b/doc/yaml-line-numbers.md
new file mode 100644
index 0000000..2894bd6
--- /dev/null
+++ b/doc/yaml-line-numbers.md
@@ -0,0 +1,309 @@
+# Obtaining YAML Line Numbers
+
+## Scenario 1 - finding YAML line numbers from the JSON tree
+
+A great feature of json-schema-validator is it's ability to validate YAML documents against a JSON Scheme. The manner in which this is done though, by pre-processing the YAML into a tree of [JsonNode](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/JsonNode.html) objects, breaks the connection back to the original YAML source file. Very commonly, once the YAML has been validated against the schema, there may be additional processing and checking for semantic or content errors or inconsistency in the JSON tree. From an end user point of view, the ideal is to report such errors using line and column references back to the original YAML, but this information is not readily available from the processed JSON tree.
+
+### Scenario 1, solution part 1 - capturing line details during initial parsing
+
+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;
+
+ public MyNodeFactory(YAMLParser yp)
+ {
+ super();
+ this.yp = yp;
+ }
+
+ public ArrayNode arrayNode()
+ {
+ return new MyArrayNode(this, yp.getTokenLocation(), yp.getCurrentLocation());
+ }
+
+ public BooleanNode booleanNode(boolean v)
+ {
+ return new MyBooleanNode(v, yp.getTokenLocation(), yp.getCurrentLocation());
+ }
+
+ public NumericNode numberNode(int v)
+ {
+ return new MyIntNode(v, yp.getTokenLocation(), yp.getCurrentLocation());
+ }
+
+ public NullNode nullNode()
+ {
+ return new MyNullNode(yp.getTokenLocation(), yp.getCurrentLocation());
+ }
+
+ public ObjectNode objectNode()
+ {
+ return new MyObjectNode(this, yp.getTokenLocation(), yp.getCurrentLocation());
+ }
+
+ public TextNode textNode(String text)
+ {
+ return (text != null) ? new MyTextNode(text, yp.getTokenLocation(), yp.getCurrentLocation()) : null;
+ }
+ }
+```
+
+The example above includes a basic, but usable subset of all possible JsonNode types - if your YAML needs them, than you should also consider the others i.e. `byte`, `byte[]`, `raw`, `short`, `long`, `float`, `double`, `BigInteger`, `BigDecimal`
+
+There are some important other things to note from the example:
+
+* Even in a reduced set, `ObjectNode` and `NullNode` should be included
+* The current return for methods that receive a null parameter value seems to be null rather than `NullNode` (based on inspecting the underlying `valueOf()` methods in the various `JsonNode` sub classes). Hence the implementation of the `textNode()` method above.
+
+The actual work here is really being done by the YAMLParser - it holds the location of the token being parsed, and the current location in the file. The first of these gives us a line and column number we can use to flag where an error or problem was found, and the second (if needed) can let us calculate a span to the end of the error e.g. if we wanted to highlight or underline the text in error.
+
+### Scenario 1, solution part 2 - augmented `JsonNode` subclassess
+
+We can be as simple or fancy as we like in the `JsonNode` subclassses, but basically we need 2 pieces of information from them:
+
+* An interface so when we are post processing the JSON tree, we can recognize nodes that retain line number information
+* An interface that lets us extract the relevant location information
+
+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();
+ }
+
+ public interface LocationDetails
+ {
+ default int getLineNumber() { return 1; }
+ default int getColumnNumber() { return 1; }
+ default String getFilename() { return ""; }
+ }
+
+ public static class LocationDetailsImpl implements LocationDetails
+ {
+ final JsonLocation currentLocation;
+ final JsonLocation tokenLocation;
+
+ public LocationDetailsImpl(JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ this.tokenLocation = tokenLocation;
+ this.currentLocation = currentLocation;
+ }
+
+ @Override
+ public int getLineNumber() { return (tokenLocation != null) ? tokenLocation.getLineNr() : 1; };
+ @Override
+ public int getColumnNumber() { return (tokenLocation != null) ? tokenLocation.getColumnNr() : 1; };
+ @Override
+ public String getFilename() { return (tokenLocation != null) ? tokenLocation.getSourceRef().toString() : ""; };
+ }
+
+ public static class MyNullNode extends NullNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyNullNode(JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super();
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails()
+ {
+ return locDetails;
+ }
+ }
+
+ public static class MyTextNode extends TextNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyTextNode(String v, JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super(v);
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails() { return locDetails;}
+ }
+
+ public static class MyIntNode extends IntNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyIntNode(int v, JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super(v);
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails() { return locDetails;}
+ }
+
+ public static class MyBooleanNode extends BooleanNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyBooleanNode(boolean v, JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super(v);
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails() { return locDetails;}
+ }
+
+ public static class MyArrayNode extends ArrayNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyArrayNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super(nc);
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails() { return locDetails;}
+ }
+
+ public static class MyObjectNode extends ObjectNode implements LocationProvider
+ {
+ final LocationDetails locDetails;
+
+ public MyObjectNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation)
+ {
+ super(nc);
+ locDetails = new LocationDetailsImpl(tokenLocation, currentLocation);
+ }
+
+ @Override
+ public LocationDetails getLocationDetails() { return locDetails;}
+ }
+```
+
+### Scenario 1, solution part 3 - using the custom `JsonNodeFactory`
+
+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);)
+ {
+ ObjectReader rdr = mapper.reader(new MyNodeFactory(yp));
+ JsonNode jsonNode = rdr.readTree(yp);
+ Set<ValidationMessage> msgs = mySchema.validate(jsonNode);
+
+ if (msgs.isEmpty())
+ {
+ for (JsonNode item : jsonNode.get("someItem"))
+ {
+ processJsonItems(item);
+ }
+ }
+ else
+ {
+ // ... we'll look at how to get line locations for ValidationMessage cases in Scenario 2
+ }
+
+ }
+ // a JsonProcessingException seems to be the base exception for "gross" errors e.g.
+ // missing quotes at end of string etc.
+ catch (JsonProcessingException jpEx)
+ {
+ JsonLocation loc = jpEx.getLocation();
+ // ... do something with the loc details
+ }
+```
+Some notes on what is happening here:
+
+* We instantiate our custom JsonNodeFactory with the YAMLParser reference, and the line locations get recorded for us as the file is parsed.
+* If any exceptions are thrown, they will already contain a JsonLocation object that we can use directly if needed
+* If we get no validation messages, we know the JSON tree matches the schema and we can do any post processing we need on the tree. We'll see how to report any issues with this in the next part
+* We'll look at how to get line locations for ValidationMessage errors in Scenario 2
+
+### Scenario 1, solution part 4 - extracting the line details
+
+Having got everything prepared, actually getting the line locations is rather easy
+
+
+```java
+ void processJsonItems(JsonNode item)
+ {
+ Iterator<Map.Entry<String, JsonNode>> iter = item.fields();
+
+ while (iter.hasNext())
+ {
+ Map.Entry<String, JsonNode> node = iter.next();
+ extractErrorLocation(node.getValue());
+ }
+ }
+
+ void extractErrorLocation(JsonNode node)
+ {
+ if (node == null || !(node instanceof LocationProvider)) { return; }
+
+ //Note: we also know the "span" of the error section i.e. from token location to current location (first char after the token)
+ // if we wanted at some stage we could use this to highlight/underline all of the text in error
+ LocationDetails dets = ((LocationProvider) node).getLocationDetails();
+ // ... do something with the details e.g. report an error/issue against the YAML line
+ }
+```
+
+So that's pretty much it - as we are processing the JSON tree, if there is any point we want to report something about the contents, we can do so with a reference back to the original YAML line number.
+
+There is still a problem though, what if the validation against the schema fails?
+
+## Scenario 2 - ValidationMessage line locations
+
+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 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 = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance().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)
+ {
+ // Construct the JSONPointer.
+ JsonPointer pathPtr = JsonPointer.valueOf(msg.getPath());
+ // Now see if we can find the node.
+ JsonNode node = rootNode.at(pathPtr);
+ return node;
+ }
+
+ LocationDetails getLocationDetails(ValidationMessage msg, JsonNode rootNode)
+ {
+ LocationDetails retval = null;
+ JsonNode node = findJsonNode(msg, rootNode);
+ if (node != null && node instanceof LocationProvider)
+ {
+ retval = ((LocationProvider) node).getLocationDetails();
+ }
+ return retval;
+ }
+```
+
+## Summary
+
+Although not trivial, the steps outlined here give us a way to track back to the original source YAML for a variety of possible reporting cases:
+
+* JSON processing exceptions (mostly already done for us)
+* Issues flagged during validation of the YAML against the schema
+* Anything we need to report with source information during post processing of the validated JSON tree
diff --git a/doc/yaml.md b/doc/yaml.md
new file mode 100644
index 0000000..1c63f07
--- /dev/null
+++ b/doc/yaml.md
@@ -0,0 +1,25 @@
+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.
+
+### Usage
+
+Add the dependency
+
+```xml
+<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>2.10.1</version>
+</dependency>
+```
+
+and create object mapper using yaml factory i.e `ObjectMapper objMapper =new ObjectMapper(new YAMLFactory());`
+
+#### Example
+```java
+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<ValidationMessage> validateMsg = schema.validate(jsonNode);
+```
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9aab945
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,430 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project
+ 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"
+ >
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <version>1.4.0</version>
+ <packaging>bundle</packaging>
+ <name>JsonSchemaValidator</name>
+ <description>A json schema validator that supports draft v4, v6, v7, v2019-09 and v2020-12</description>
+ <url>https://github.com/networknt/json-schema-validator</url>
+
+ <licenses>
+ <license>
+ <name>Apache License Version 2.0</name>
+ <url>https://www.apache.org/licenses/LICENSE-2.0</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <developers>
+ <developer>
+ <id>stevehu</id>
+ <name>Steve Hu</name>
+ <email>stevehu@gmail.com</email>
+ </developer>
+ </developers>
+
+ <scm>
+ <connection>scm:git://github.com:networknt/json-schema-validator.git</connection>
+ <developerConnection>scm:git://github.com:networknt/json-schema-validator.git</developerConnection>
+ <url>https://github.com:networknt/json-schema-validator.git</url>
+ </scm>
+
+ <issueManagement>
+ <system>github</system>
+ <url>https://github.com/networknt/json-schema-validator/issues</url>
+ </issueManagement>
+
+ <distributionManagement>
+ <repository>
+ <id>ossrh</id>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ <snapshotRepository>
+ <id>ossrh</id>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ </snapshotRepository>
+ </distributionManagement>
+
+ <properties>
+ <java.testversion>1.8</java.testversion>
+ <java.version>1.8</java.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <version.hamcrest>2.2</version.hamcrest>
+ <version.itu>1.8.0</version.itu>
+ <version.jackson>2.15.3</version.jackson>
+ <version.joni>2.1.41</version.joni>
+ <version.junit>5.9.2</version.junit>
+ <version.logback>1.3.14</version.logback>
+ <version.slf4j>2.0.9</version.slf4j>
+ <version.surefire>3.0.0</version.surefire>
+ <version.undertow>2.2.25.Final</version.undertow>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${version.logback}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <!-- Used to validate RFC 3339 date and date-time -->
+ <groupId>com.ethlo.time</groupId>
+ <artifactId>itu</artifactId>
+ <version>${version.itu}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${version.jackson}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>${version.jackson}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <version>${version.undertow}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <version>${version.hamcrest}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <!-- Used to validate ECMA 262 regular expressions -->
+ <groupId>org.jruby.joni</groupId>
+ <artifactId>joni</artifactId>
+ <version>${version.joni}</version>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${version.junit}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <version>${version.junit}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${version.slf4j}</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+
+ <resources>
+ <resource>
+ <filtering>false</filtering>
+ <directory>${basedir}/src/main/resources</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </resource>
+ </resources>
+
+ <testResources>
+ <testResource>
+ <filtering>false</filtering>
+ <directory>${basedir}/src/test/resources</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </testResource>
+ <testResource>
+ <directory>${project.basedir}/src/test/suite</directory>
+ </testResource>
+ </testResources>
+
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>5.1.8</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.jcodings;resolution:=optional,
+ org.jcodings.specific;resolution:=optional,
+ org.joni;resolution:=optional,
+ org.joni.exception;resolution:=optional,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>1.6.8</version>
+ <extensions>true</extensions>
+ <configuration>
+ <serverId>ossrh</serverId>
+ <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+ <autoReleaseAfterClose>true</autoReleaseAfterClose>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.4.0</version>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <source>8</source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.6.1</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <testSource>${java.testversion}</testSource>
+ <testTarget>${java.testversion}</testTarget>
+ </configuration>
+ <executions>
+ <execution>
+ <id>test-compile</id>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ <phase>process-test-sources</phase>
+ <configuration>
+ <source>${java.testversion}</source>
+ <target>${java.testversion}</target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${version.surefire}</version>
+ <configuration>
+ <argLine>@{argLine} -Duser.language=en -Duser.region=GB</argLine>
+ <reportFormat>plain</reportFormat>
+ <consoleOutputReporter>
+ <disable>true</disable>
+ </consoleOutputReporter>
+ <statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
+ <usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
+ <usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
+ <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
+ </statelessTestsetReporter>
+ <statelessTestsetInfoReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
+ <usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
+ <usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
+ </statelessTestsetInfoReporter>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>me.fabriciorby</groupId>
+ <artifactId>maven-surefire-junit5-tree-reporter</artifactId>
+ <version>1.1.0</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.10</version>
+
+ <configuration>
+ <excludes>
+ <exclude>com/networknt/org/apache/commons/validator/**</exclude>
+ </excludes>
+ </configuration>
+
+ <executions>
+ <!--
+ | Prepares the property pointing to the JaCoCo runtime agent
+ | which is passed as VM argument when Maven the Surefire plugin
+ | is executed.
+ +-->
+ <execution>
+ <id>pre-unit-test</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+
+ <!--
+ | Ensures that the code coverage report for unit-tests
+ | is created after unit tests have been run.
+ +-->
+ <execution>
+ <id>post-unit-test</id>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ <phase>test</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+
+ <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
+ <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>
+
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${version.surefire}</version>
+ </plugin>
+ </plugins>
+ </reporting>
+
+ <profiles>
+
+ <profile>
+ <id>release-sign-artifacts</id>
+ <activation>
+ <property>
+ <name>performRelease</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ <phase>verify</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>java-module</id>
+ <activation>
+ <jdk>[9,)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.moditect</groupId>
+ <artifactId>moditect-maven-plugin</artifactId>
+ <version>1.0.0.Final</version>
+ <executions>
+ <execution>
+ <id>add-module-infos</id>
+ <goals>
+ <goal>add-module-info</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <jvmVersion>9</jvmVersion>
+ <overwriteExistingFiles>true</overwriteExistingFiles>
+ <module>
+ <moduleInfo>
+ <name>com.networknt.schema</name>
+ <!-- export everything except embedded Apache code -->
+ <exports>
+ !com.networknt.org*;
+ *;
+ </exports>
+ <!-- declare services consumed by the artifact -->
+ <addServiceUses>true</addServiceUses>
+ </moduleInfo>
+ </module>
+ <jdepsExtraArgs>
+ <arg>--multi-release=9</arg>
+ </jdepsExtraArgs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
+
+</project>
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 0000000..c621e28
--- /dev/null
+++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/DomainValidator.java
@@ -0,0 +1,2319 @@
+/*
+ * 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;
+
+/**
+ * <p><b>Domain name</b> validation routines.</p>
+ *
+ * <p>
+ * This validator provides methods for validating Internet domain names
+ * and top-level domains.
+ * </p>
+ *
+ * <p>Domain names are evaluated according
+ * to the standards <a href="http://www.ietf.org/rfc/rfc1034.txt">RFC1034</a>,
+ * section 3, and <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC1123</a>,
+ * 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.
+ * </p>
+ *
+ * <p>
+ * Validation is also provided for top-level domains (TLDs) as defined and
+ * maintained by the Internet Assigned Numbers Authority (IANA):
+ * </p>
+ *
+ * <ul>
+ * <li>{@link #isValidInfrastructureTld} - validates infrastructure TLDs
+ * (<code>.arpa</code>, etc.)</li>
+ * <li>{@link #isValidGenericTld} - validates generic TLDs
+ * (<code>.com, .org</code>, etc.)</li>
+ * <li>{@link #isValidCountryCodeTld} - validates country code TLDs
+ * (<code>.us, .uk, .cn</code>, etc.)</li>
+ * </ul>
+ *
+ * <p>
+ * (<b>NOTE</b>: 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.)
+ * </p>
+ *
+ * @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 &amp; 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 &amp; 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&amp;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&#39;Oréal
+ "beer", // beer Top Level Domain Holdings Limited
+ "bentley", // bentley Bentley Motors Limited
+ "berlin", // berlin dotBERLIN GmbH &amp; 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 (&quot;CERN&quot;)
+ "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 &amp; 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 &amp; Rubber Company
+// "duns", // duns The Dun &amp; 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&amp;1 Mail &amp; 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 &amp; 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&#39;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 &amp; 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 &amp; 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&#39;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&#39;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 &amp; 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&#39;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 (&quot;Applicant&quot;)
+ "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 &amp; Telecommunications
+ "obi", // obi OBI Group Holding SE &amp; 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 &amp; 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 (&quot;SCB&quot;)
+ "schaeffler", // schaeffler Schaeffler Technologies AG &amp; 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 &amp; 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 &amp; 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&#39;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 &amp; 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 &amp; 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 &amp; 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&#39;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 &amp; 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&#39;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 &amp; 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<Item> 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.
+ * <p>
+ * For example:
+ * <p>
+ * {@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, "apache")}
+ * <p>
+ * 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
+ * <ul>
+ * <li>COUNTRY_CODE_MINUS</li>
+ * <li>COUNTRY_CODE_PLUS</li>
+ * <li>GENERIC_MINUS</li>
+ * <li>GENERIC_PLUS</li>
+ * <li>LOCAL_MINUS</li>
+ * <li>LOCAL_PLUS</li>
+ * </ul>
+ * @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<Item> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> matches any
+ * IANA-defined top-level domain. Leading dots are ignored if present.
+ * The search is case-insensitive.
+ * <p>
+ * 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 0000000..6876d79
--- /dev/null
+++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/EmailValidator.java
@@ -0,0 +1,233 @@
+/*
+ * 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;
+
+/**
+ * <p>Perform email validations.</p>
+ * <p>
+ * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
+ * https://javascript.internet.com
+ * </p>
+ * <p>
+ * This implementation is not guaranteed to catch all possible errors in an email address.
+ * </p>.
+ *
+ * @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;
+ }
+
+ /**
+ * <p>Checks if a field has a valid e-mail address.</p>
+ *
+ * @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;
+ }
+
+ 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(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 0000000..f3ed5ae
--- /dev/null
+++ b/src/main/java/com/networknt/org/apache/commons/validator/routines/InetAddressValidator.java
@@ -0,0 +1,221 @@
+/*
+ * 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;
+
+/**
+ * <p><b>InetAddress</b> validation and conversion routines (<code>java.net.InetAddress</code>).</p>
+ *
+ * <p>This class provides methods to validate a candidate IP address.
+ *
+ * <p>
+ * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method.
+ * </p>
+ *
+ * @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<String> 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++;
+ }
+ 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/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 0000000..87d5a04
--- /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;
+
+/**
+ * <b>Regular Expression</b> validation (using the JRE's regular expression support).
+ * <p>
+ * Construct the validator either for a single regular expression or a set (array) of
+ * regular expressions. By default validation is <i>case sensitive</i> but constructors
+ * are provided to allow <i>case in-sensitive</i> validation. For example to create
+ * a validator which does <i>case in-sensitive</i> validation for a set of regular
+ * expressions:
+ * </p>
+ * <pre>
+ * <code>
+ * String[] regexs = new String[] {...};
+ * RegexValidator validator = new RegexValidator(regexs, false);
+ * </code>
+ * </pre>
+ *
+ * <ul>
+ * <li>Validate {@code true} or {@code false}:</li>
+ * <li>
+ * <ul>
+ * <li><code>boolean valid = validator.isValid(value);</code></li>
+ * </ul>
+ * </li>
+ * <li>Validate returning an aggregated String of the matched groups:</li>
+ * <li>
+ * <ul>
+ * <li><code>String result = validator.validate(value);</code></li>
+ * </ul>
+ * </li>
+ * <li>Validate returning the matched groups:</li>
+ * <li>
+ * <ul>
+ * <li><code>String[] result = validator.match(value);</code></li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * <b>Note that patterns are matched against the entire input.</b>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * @since 1.4
+ */
+public class RegexValidator implements Serializable {
+
+ private static final long serialVersionUID = -8832409930574867162L;
+
+ private final Pattern[] patterns;
+
+ /**
+ * Constructs a <i>case sensitive</i> 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<String> regexs) {
+ this(regexs.toArray(new String[] {}), true);
+ }
+
+ /**
+ * Constructs a <i>case sensitive</i> 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 <i>case
+ * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
+ */
+ public RegexValidator(final String regex, final boolean caseSensitive) {
+ this(new String[] { regex }, caseSensitive);
+ }
+
+ /**
+ * Constructs a <i>case sensitive</i> 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 <i>case
+ * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
+ */
+ 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 <i>groups</i> matched if
+ * valid or <code>null</code> 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
+ * <i>groups</i> matched if valid or <code>null</code> 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 0000000..d8f793e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbsoluteIri.java
@@ -0,0 +1,198 @@
+/*
+ * 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;
+
+/**
+ * The absolute IRI is an IRI without the fragment.
+ * <p>
+ * absolute-IRI = scheme ":" ihier-part [ "?" iquery ]
+ * <p>
+ * 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);
+ }
+
+ /**
+ * 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 (iri.contains(":")) {
+ // 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 = 0;
+ } else {
+ scheme = scheme + 3;
+ }
+ base = parent(base, scheme);
+
+ String[] iriParts = iri.split("/");
+ for (int x = 0; x < iriParts.length; x++) {
+ if ("..".equals(iriParts[x])) {
+ base = parent(base, scheme);
+ } else if (".".equals(iriParts[x])) {
+ // skip
+ } else {
+ base = base + "/" + iriParts[x];
+ }
+ }
+ if (iri.endsWith("/")) {
+ base = base + "/";
+ }
+ return base;
+ }
+ }
+ }
+
+ protected static String parent(String iri, int scheme) {
+ int slash = iri.lastIndexOf('/');
+ 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
new file mode 100644
index 0000000..3a45d81
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbstractCollector.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Base collector.
+ *
+ * @param <E> the type
+ */
+public abstract class AbstractCollector<E> implements Collector<E> {
+
+ @Override
+ public void combine(Object object) {
+ // Do nothing. This is the default Implementation.
+ }
+}
diff --git a/src/main/java/com/networknt/schema/AbstractJsonValidator.java b/src/main/java/com/networknt/schema/AbstractJsonValidator.java
new file mode 100644
index 0000000..bf3fc5d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbstractJsonValidator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.function.Consumer;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+
+/**
+ * Base {@link JsonValidator}.
+ */
+public abstract class AbstractJsonValidator implements JsonValidator {
+ private final SchemaLocation schemaLocation;
+ private final JsonNode schemaNode;
+ private final JsonNodePath evaluationPath;
+ private final String keyword;
+
+ /**
+ * Constructor.
+ *
+ * @param schemaLocation the schema location
+ * @param evaluationPath the evaluation path
+ * @param keyword the keyword
+ * @param schemaNode the schema node
+ */
+ public AbstractJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, Keyword keyword, JsonNode schemaNode) {
+ this.schemaLocation = schemaLocation;
+ this.evaluationPath = evaluationPath;
+ this.keyword = keyword.getValue();
+ this.schemaNode = schemaNode;
+ }
+
+ @Override
+ public SchemaLocation getSchemaLocation() {
+ return schemaLocation;
+ }
+
+ @Override
+ public JsonNodePath getEvaluationPath() {
+ return evaluationPath;
+ }
+
+ @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 getEvaluationPath().getName(-1);
+ }
+
+ /**
+ * 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<JsonNodeAnnotation.Builder> customizer) {
+ JsonNodeAnnotation.Builder builder = JsonNodeAnnotation.builder().evaluationPath(this.evaluationPath)
+ .schemaLocation(this.schemaLocation).keyword(getKeyword());
+ customizer.accept(builder);
+ executionContext.getAnnotations().put(builder.build());
+ }
+}
diff --git a/src/main/java/com/networknt/schema/AbstractKeyword.java b/src/main/java/com/networknt/schema/AbstractKeyword.java
new file mode 100644
index 0000000..3b6945d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/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;
+
+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/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
new file mode 100644
index 0000000..62262ba
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -0,0 +1,224 @@
+/*
+ * 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.annotation.JsonNodeAnnotation;
+import com.networknt.schema.regex.RegularExpression;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 JsonValidator} for additionalProperties.
+ */
+public class AdditionalPropertiesValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(AdditionalPropertiesValidator.class);
+
+ private final boolean allowAdditionalProperties;
+ private final JsonSchema additionalPropertiesSchema;
+ private final Set<String> allowedProperties;
+ private final List<RegularExpression> patternProperties;
+
+ private Boolean hasUnevaluatedPropertiesValidator;
+
+ public AdditionalPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ADDITIONAL_PROPERTIES, validationContext);
+ if (schemaNode.isBoolean()) {
+ allowAdditionalProperties = schemaNode.booleanValue();
+ additionalPropertiesSchema = null;
+ } else if (schemaNode.isObject()) {
+ allowAdditionalProperties = true;
+ additionalPropertiesSchema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ } else {
+ allowAdditionalProperties = false;
+ additionalPropertiesSchema = null;
+ }
+
+ JsonNode propertiesNode = parentSchema.getSchemaNode().get(PropertiesValidator.PROPERTY);
+ if (propertiesNode != null) {
+ allowedProperties = new HashSet<>();
+ for (Iterator<String> 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<>();
+ for (Iterator<String> it = patternPropertiesNode.fieldNames(); it.hasNext(); ) {
+ patternProperties.add(RegularExpression.compile(it.next(), validationContext));
+ }
+ } else {
+ this.patternProperties = Collections.emptyList();
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ if (!node.isObject()) {
+ // ignore no object
+ return Collections.emptySet();
+ }
+
+ Set<String> matchedInstancePropertyNames = null;
+
+ boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext);
+ // if allowAdditionalProperties is true, add all the properties as evaluated.
+ if (allowAdditionalProperties && collectAnnotations) {
+ for (Iterator<String> it = node.fieldNames(); it.hasNext();) {
+ if (matchedInstancePropertyNames == null) {
+ matchedInstancePropertyNames = new LinkedHashSet<>();
+ }
+ String fieldName = it.next();
+ matchedInstancePropertyNames.add(fieldName);
+ }
+ }
+
+ Set<ValidationMessage> errors = null;
+
+ for (Iterator<Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
+ Entry<String, JsonNode> entry = it.next();
+ String pname = entry.getKey();
+ // skip the context items
+ if (pname.startsWith("#")) {
+ continue;
+ }
+ boolean handledByPatternProperties = false;
+ for (RegularExpression pattern : patternProperties) {
+ if (pattern.matches(pname)) {
+ handledByPatternProperties = true;
+ break;
+ }
+ }
+
+ if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+ if (!allowAdditionalProperties) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.add(message().instanceNode(node).property(pname)
+ .instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(pname).build());
+ } else {
+ if (additionalPropertiesSchema != null) {
+ ValidatorState state = executionContext.getValidatorState();
+ if (state != null && state.isWalkEnabled()) {
+ Set<ValidationMessage> results = additionalPropertiesSchema.walk(executionContext,
+ entry.getValue(), rootNode, instanceLocation.append(pname),
+ state.isValidationEnabled());
+ if (!results.isEmpty()) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.addAll(results);
+ }
+ } else {
+ Set<ValidationMessage> results = additionalPropertiesSchema.validate(executionContext,
+ entry.getValue(), rootNode, instanceLocation.append(pname));
+ if (!results.isEmpty()) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.addAll(results);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (collectAnnotations) {
+ executionContext.getAnnotations().put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation).keyword(getKeyword())
+ .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames : Collections.emptySet())
+ .build());
+ }
+ return errors == null ? Collections.emptySet() : Collections.unmodifiableSet(errors);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ if (node == null || !node.isObject()) {
+ // ignore no object
+ return Collections.emptySet();
+ }
+
+ // Else continue walking.
+ for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ // skip the context items
+ if (pname.startsWith("#")) {
+ continue;
+ }
+ boolean handledByPatternProperties = false;
+ for (RegularExpression pattern : patternProperties) {
+ if (pattern.matches(pname)) {
+ handledByPatternProperties = true;
+ break;
+ }
+ }
+
+ if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+ if (allowAdditionalProperties) {
+ if (additionalPropertiesSchema != null) {
+ ValidatorState state = executionContext.getValidatorState();
+ if (state != null && state.isWalkEnabled()) {
+ additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode, instanceLocation.append(pname), state.isValidationEnabled());
+ }
+ }
+ }
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedPropertiesValidator();
+ }
+
+ private boolean hasUnevaluatedPropertiesValidator() {
+ if (this.hasUnevaluatedPropertiesValidator == null) {
+ this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties");
+ }
+ return hasUnevaluatedPropertiesValidator;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ if(additionalPropertiesSchema != null) {
+ additionalPropertiesSchema.initializeValidators();
+ }
+ collectAnnotations(); // cache the flag
+ }
+}
diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java
new file mode 100644
index 0000000..dc2091c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AllOfValidator.java
@@ -0,0 +1,119 @@
+/*
+ * 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.*;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link JsonValidator} for allOf.
+ */
+public class AllOfValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(AllOfValidator.class);
+
+ private final List<JsonSchema> schemas = new ArrayList<>();
+
+ public AllOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext);
+ int size = schemaNode.size();
+ for (int i = 0; i < size; i++) {
+ this.schemas.add(validationContext.newSchema(schemaLocation.append(i), evaluationPath.append(i),
+ schemaNode.get(i), parentSchema));
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // get the Validator state object storing validation data
+ ValidatorState state = executionContext.getValidatorState();
+
+ SetView<ValidationMessage> childSchemaErrors = null;
+
+ for (JsonSchema schema : this.schemas) {
+ Set<ValidationMessage> localErrors = null;
+
+ if (!state.isWalkEnabled()) {
+ localErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ } else {
+ localErrors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
+ }
+
+ if (localErrors != null && !localErrors.isEmpty()) {
+ if (childSchemaErrors == null) {
+ childSchemaErrors = new SetView<>();
+ }
+ childSchemaErrors.union(localErrors);
+ }
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ final Iterator<JsonNode> arrayElements = this.schemaNode.elements();
+ while (arrayElements.hasNext()) {
+ final ObjectNode allOfEntry = (ObjectNode) arrayElements.next();
+ final JsonNode $ref = allOfEntry.get("$ref");
+ if (null != $ref) {
+ final DiscriminatorContext currentDiscriminatorContext = executionContext
+ .getCurrentDiscriminatorContext();
+ if (null != currentDiscriminatorContext) {
+ final ObjectNode discriminator = currentDiscriminatorContext
+ .getDiscriminatorForPath(allOfEntry.get("$ref").asText());
+ if (null != discriminator) {
+ registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator,
+ this.parentSchema, instanceLocation);
+ // 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 = this.parentSchema;
+ checkDiscriminatorMatch(currentDiscriminatorContext, discriminator,
+ discriminatorPropertyValue, jsonSchema);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return childSchemaErrors != null ? childSchemaErrors : Collections.emptySet();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+ for (JsonSchema schema : this.schemas) {
+ // Walk through the schema
+ schema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(this.schemas);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/AnnotationKeyword.java b/src/main/java/com/networknt/schema/AnnotationKeyword.java
new file mode 100644
index 0000000..e0d7d5c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AnnotationKeyword.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;
+
+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, where annotations may need to be collected.
+ */
+public class AnnotationKeyword extends AbstractKeyword {
+
+ private static final class Validator extends AbstractJsonValidator {
+ public Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext, Keyword keyword) {
+ super(schemaLocation, evaluationPath, keyword, schemaNode);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ if (collectAnnotations(executionContext)) {
+ Object value = getAnnotationValue(getSchemaNode());
+ if (value != null) {
+ putAnnotation(executionContext,
+ annotation -> annotation.instanceLocation(instanceLocation).value(value));
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ protected 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 JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
+ return new Validator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext, this);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
new file mode 100644
index 0000000..931c4e3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -0,0 +1,211 @@
+/*
+ * 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.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for anyOf.
+ */
+public class AnyOfValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(AnyOfValidator.class);
+ private static final String DISCRIMINATOR_REMARK = "and the discriminator-selected candidate schema didn't pass validation";
+
+ private final List<JsonSchema> schemas = new ArrayList<>();
+
+ private Boolean canShortCircuit = null;
+
+ public AnyOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext);
+ int size = schemaNode.size();
+ for (int i = 0; i < size; i++) {
+ this.schemas.add(validationContext.newSchema(schemaLocation.append(i), evaluationPath.append(i),
+ schemaNode.get(i), parentSchema));
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // get the Validator state object storing validation data
+ ValidatorState state = executionContext.getValidatorState();
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ executionContext.enterDiscriminatorContext(new DiscriminatorContext(), instanceLocation);
+ }
+
+ boolean initialHasMatchedNode = state.hasMatchedNode();
+
+ SetView<ValidationMessage> allErrors = null;
+
+ int numberOfValidSubSchemas = 0;
+ try {
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ executionContext.setFailFast(false);
+ for (JsonSchema schema : this.schemas) {
+ Set<ValidationMessage> errors = Collections.emptySet();
+ state.setMatchedNode(initialHasMatchedNode);
+
+ TypeValidator typeValidator = schema.getTypeValidator();
+ if (typeValidator != null) {
+ // 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)) {
+ if (allErrors == null) {
+ allErrors = new SetView<>();
+ }
+ allErrors.union(typeValidator.validate(executionContext, node, rootNode, instanceLocation));
+ continue;
+ }
+ }
+ if (!state.isWalkEnabled()) {
+ errors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ } else {
+ errors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
+ }
+
+ // check if any validation errors have occurred
+ if (errors.isEmpty()) {
+ // check whether there are no errors HOWEVER we have validated the exact
+ // validator
+ if (!state.hasMatchedNode()) {
+ continue;
+ }
+ // we found a valid subschema, so increase counter
+ numberOfValidSubSchemas++;
+ }
+
+ if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())
+ && canShortCircuit() && canShortCircuit(executionContext)) {
+ // Clear all errors. Note that this is checked in finally.
+ allErrors = null;
+ // return empty errors.
+ return errors;
+ } else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ DiscriminatorContext currentDiscriminatorContext = executionContext.getCurrentDiscriminatorContext();
+ if (currentDiscriminatorContext.isDiscriminatorMatchFound()
+ || currentDiscriminatorContext.isDiscriminatorIgnore()) {
+ if (!errors.isEmpty()) {
+ // The following is to match the previous logic adding to all errors
+ // which is generally discarded as it returns errors but the allErrors
+ // is getting processed in finally
+ if (allErrors == null) {
+ allErrors = new SetView<>();
+ }
+ allErrors.union(Collections
+ .singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(DISCRIMINATOR_REMARK)
+ .build()));
+ } else {
+ // Clear all errors. Note that this is checked in finally.
+ allErrors = null;
+ }
+ return errors;
+ }
+ }
+ if (allErrors == null) {
+ allErrors = new SetView<>();
+ }
+ allErrors.union(errors);
+ }
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+ }
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()
+ && executionContext.getCurrentDiscriminatorContext().isActive()
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorIgnore()) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .arguments(
+ "based on the provided discriminator. No alternative could be chosen based on the discriminator property")
+ .build());
+ }
+ } finally {
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
+ }
+ if (allErrors == null || allErrors.isEmpty()) {
+ state.setMatchedNode(true);
+ }
+ }
+ if (numberOfValidSubSchemas >= 1) {
+ return Collections.emptySet();
+ }
+ return allErrors != null ? allErrors : Collections.emptySet();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+ for (JsonSchema schema : this.schemas) {
+ schema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+ return new LinkedHashSet<>();
+ }
+
+ /**
+ * If annotation collection is enabled cannot short circuit.
+ *
+ * @see <a href=
+ * "https://github.com/json-schema-org/json-schema-spec/blob/f8967bcbc6cee27753046f63024b55336a9b1b54/jsonschema-core.md?plain=1#L1717-L1720">anyOf</a>
+ * @param executionContext the execution context
+ * @return true if can short circuit
+ */
+ protected boolean canShortCircuit(ExecutionContext executionContext) {
+ return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled();
+ }
+
+ /**
+ * If annotations are require for evaluation cannot short circuit.
+ *
+ * @return true if can short circuit
+ */
+ protected boolean canShortCircuit() {
+ if (this.canShortCircuit == null) {
+ boolean canShortCircuit = true;
+ for (JsonValidator validator : getEvaluationParentSchema().getValidators()) {
+ if ("unevaluatedProperties".equals(validator.getKeyword())
+ || "unevaluatedItems".equals(validator.getKeyword())) {
+ canShortCircuit = false;
+ }
+ }
+ this.canShortCircuit = canShortCircuit;
+ }
+ return this.canShortCircuit;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(this.schemas);
+ canShortCircuit(); // cache flag
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java b/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java
new file mode 100644
index 0000000..391a160
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java
@@ -0,0 +1,43 @@
+package com.networknt.schema;
+
+public class ApplyDefaultsStrategy {
+ static final ApplyDefaultsStrategy EMPTY_APPLY_DEFAULTS_STRATEGY = new ApplyDefaultsStrategy(false, false, false);
+
+ private final boolean applyPropertyDefaults;
+ private final boolean applyPropertyDefaultsIfNull;
+ private final boolean applyArrayDefaults;
+
+ /**
+ * Specify which default values to apply.
+ * We can apply property defaults only if they are missing or if they are declared to be null in the input json,
+ * and we can apply array defaults if they are declared to be null in the input json.
+ *
+ * <p>Note that the walker changes the input object in place.
+ * If validation fails, the input object will be changed.
+ *
+ * @param applyPropertyDefaults if true then apply defaults inside json objects if the attribute is missing
+ * @param applyPropertyDefaultsIfNull if true then apply defaults inside json objects if the attribute is explicitly null
+ * @param applyArrayDefaults if true then apply defaults inside json arrays if the attribute is explicitly null
+ * @throws IllegalArgumentException if applyPropertyDefaults is false and applyPropertyDefaultsIfNull is true
+ */
+ public ApplyDefaultsStrategy(boolean applyPropertyDefaults, boolean applyPropertyDefaultsIfNull, boolean applyArrayDefaults) {
+ if (!applyPropertyDefaults && applyPropertyDefaultsIfNull) {
+ throw new IllegalArgumentException();
+ }
+ this.applyPropertyDefaults = applyPropertyDefaults;
+ this.applyPropertyDefaultsIfNull = applyPropertyDefaultsIfNull;
+ this.applyArrayDefaults = applyArrayDefaults;
+ }
+
+ public boolean shouldApplyPropertyDefaults() {
+ return applyPropertyDefaults;
+ }
+
+ public boolean shouldApplyPropertyDefaultsIfNull() {
+ return applyPropertyDefaultsIfNull;
+ }
+
+ public boolean shouldApplyArrayDefaults() {
+ return applyArrayDefaults;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
new file mode 100644
index 0000000..eebcd9c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.i18n.DefaultMessageSource;
+
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public abstract class BaseJsonValidator extends ValidationMessageHandler implements JsonValidator {
+ protected final boolean suppressSubSchemaRetrieval;
+
+ protected final JsonNode schemaNode;
+
+ protected ValidationContext validationContext;
+
+ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidatorTypeCode validatorType, ValidationContext validationContext) {
+ this(schemaLocation, evaluationPath, schemaNode, parentSchema, validatorType, validatorType, validationContext,
+ false);
+ }
+
+ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ErrorMessageType errorMessageType, Keyword keyword,
+ ValidationContext validationContext, boolean suppressSubSchemaRetrieval) {
+ super(errorMessageType,
+ (validationContext != null && validationContext.getConfig() != null)
+ ? validationContext.getConfig().isCustomMessageSupported()
+ : true,
+ (validationContext != null && validationContext.getConfig() != null)
+ ? validationContext.getConfig().getMessageSource()
+ : DefaultMessageSource.getInstance(),
+ keyword,
+ parentSchema, schemaLocation, evaluationPath);
+ this.validationContext = validationContext;
+ this.schemaNode = schemaNode;
+ this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param copy to copy from
+ */
+ protected BaseJsonValidator(BaseJsonValidator copy) {
+ super(copy);
+ this.suppressSubSchemaRetrieval = copy.suppressSubSchemaRetrieval;
+ this.schemaNode = copy.schemaNode;
+ this.validationContext = copy.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;
+ }
+
+ final SchemaLocation schemaLocation = SchemaLocation.of(node.textValue());
+
+ return validationContext.getJsonSchemaFactory().getSchema(schemaLocation, validationContext.getConfig());
+ }
+
+ protected static boolean equals(double n1, double n2) {
+ return Math.abs(n1 - n2) < 1e-12;
+ }
+
+ protected static void debug(Logger logger, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ logger.debug("validate( {}, {}, {})", node, rootNode, instanceLocation);
+ }
+
+ /**
+ * 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 <code>discriminator/propertyName</code> field
+ * @param jsonSchema the {@link JsonSchema} to check
+ */
+ protected static void checkDiscriminatorMatch(final DiscriminatorContext currentDiscriminatorContext,
+ final ObjectNode discriminator,
+ final String discriminatorPropertyValue,
+ final JsonSchema jsonSchema) {
+ if (discriminatorPropertyValue == null) {
+ currentDiscriminatorContext.markIgnore();
+ 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 <code>propertyName</code> or mappings.
+ *
+ * @param currentDiscriminatorContext the currently active {@link DiscriminatorContext}
+ * @param discriminator the discriminator to use for the check
+ * @param schema the value of the <code>discriminator/propertyName</code> field
+ * @param instanceLocation the logging prefix
+ */
+ protected static void registerAndMergeDiscriminator(final DiscriminatorContext currentDiscriminatorContext,
+ final ObjectNode discriminator,
+ final JsonSchema schema,
+ final JsonNodePath instanceLocation) {
+ final JsonNode discriminatorOnSchema = schema.schemaNode.get("discriminator");
+ if (null != discriminatorOnSchema && null != currentDiscriminatorContext
+ .getDiscriminatorForPath(schema.schemaLocation)) {
+ // 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(instanceLocation + " 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<Map.Entry<String, JsonNode>> fieldsToAdd = mappingOnCurrentSchemaDiscriminator.fields();
+ while (fieldsToAdd.hasNext()) {
+ final Map.Entry<String, JsonNode> 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(instanceLocation + "discriminator mapping redefinition from " + mappingKeyToAdd
+ + "/" + currentMappingValue + " to " + mappingValueToAdd);
+ } else if (null == currentMappingValue) {
+ mappingOnContextDiscriminator.set(mappingKeyToAdd, mappingValueToAdd);
+ }
+ }
+ }
+ }
+ currentDiscriminatorContext.registerDiscriminator(schema.schemaLocation, discriminator);
+ }
+
+ private static void checkForImplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
+ final String discriminatorPropertyValue,
+ final JsonSchema schema) {
+ if (schema.schemaLocation.getFragment().getName(-1).equals(discriminatorPropertyValue)) {
+ currentDiscriminatorContext.markMatch();
+ }
+ }
+
+ private static void checkForExplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
+ final String discriminatorPropertyValue,
+ final JsonNode discriminatorMapping,
+ final JsonSchema schema) {
+ final Iterator<Map.Entry<String, JsonNode>> explicitMappings = discriminatorMapping.fields();
+ while (explicitMappings.hasNext()) {
+ final Map.Entry<String, JsonNode> candidateExplicitMapping = explicitMappings.next();
+ if (candidateExplicitMapping.getKey().equals(discriminatorPropertyValue)
+ && ("#" + schema.schemaLocation.getFragment().toString())
+ .equals(candidateExplicitMapping.getValue().asText())) {
+ currentDiscriminatorContext.markMatch();
+ break;
+ }
+ }
+ }
+
+ private static boolean noExplicitDiscriminatorKeyOverride(final JsonNode discriminatorMapping,
+ final JsonSchema parentSchema) {
+ final Iterator<Map.Entry<String, JsonNode>> explicitMappings = discriminatorMapping.fields();
+ while (explicitMappings.hasNext()) {
+ final Map.Entry<String, JsonNode> candidateExplicitMapping = explicitMappings.next();
+ if (candidateExplicitMapping.getValue().asText()
+ .equals(parentSchema.schemaLocation.getFragment().toString())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public SchemaLocation getSchemaLocation() {
+ return this.schemaLocation;
+ }
+
+ @Override
+ public JsonNodePath getEvaluationPath() {
+ return this.evaluationPath;
+ }
+
+ @Override
+ public String getKeyword() {
+ return this.keyword.getValue();
+ }
+
+ public JsonNode getSchemaNode() {
+ return this.schemaNode;
+ }
+
+ /**
+ * Gets the parent schema.
+ * <p>
+ * This is the lexical parent schema.
+ *
+ * @return the parent schema
+ */
+ public JsonSchema getParentSchema() {
+ return this.parentSchema;
+ }
+
+ /**
+ * Gets the evaluation parent schema.
+ * <p>
+ * This is the dynamic parent schema when following references.
+ *
+ * @see JsonSchema#fromRef(JsonSchema, JsonNodePath)
+ * @return the evaluation parent schema
+ */
+ public JsonSchema getEvaluationParentSchema() {
+ if (this.evaluationParentSchema != null) {
+ return this.evaluationParentSchema;
+ }
+ return getParentSchema();
+ }
+
+ protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) {
+ return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, validationContext);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node) {
+ return validate(executionContext, node, node, atRoot());
+ }
+
+ protected String getNodeFieldType() {
+ JsonNode typeField = this.getParentSchema().getSchemaNode().get("type");
+ if (typeField != null) {
+ return typeField.asText();
+ }
+ return null;
+ }
+
+ protected void preloadJsonSchemas(final Collection<JsonSchema> schemas) {
+ for (final JsonSchema schema : schemas) {
+ schema.initializeValidators();
+ }
+ }
+
+ public static class JsonNodePathLegacy {
+ private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.LEGACY);
+ public static JsonNodePath getInstance() {
+ return INSTANCE;
+ }
+ }
+
+ public static class JsonNodePathJsonPointer {
+ private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.JSON_POINTER);
+ public static JsonNodePath getInstance() {
+ return INSTANCE;
+ }
+ }
+
+ public static class JsonNodePathJsonPath {
+ private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.JSON_PATH);
+ public static JsonNodePath getInstance() {
+ return INSTANCE;
+ }
+ }
+
+ /**
+ * Get the root path.
+ *
+ * @return The path.
+ */
+ protected JsonNodePath atRoot() {
+ if (this.validationContext.getConfig().getPathType().equals(PathType.JSON_POINTER)) {
+ return JsonNodePathJsonPointer.getInstance();
+ } else if (this.validationContext.getConfig().getPathType().equals(PathType.LEGACY)) {
+ return JsonNodePathLegacy.getInstance();
+ } else if (this.validationContext.getConfig().getPathType().equals(PathType.JSON_PATH)) {
+ return JsonNodePathJsonPath.getInstance();
+ }
+ return new JsonNodePath(this.validationContext.getConfig().getPathType());
+ }
+
+ @Override
+ public String toString() {
+ return getEvaluationPath().getName(-1);
+ }
+
+ /**
+ * Determines if the keyword exists adjacent in the evaluation path.
+ * <p>
+ * 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.
+ * <p>
+ * 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(String keyword) {
+ boolean hasValidator = false;
+ JsonSchema schema = getEvaluationParentSchema();
+ while (schema != null) {
+ for (JsonValidator validator : schema.getValidators()) {
+ if (keyword.equals(validator.getKeyword())) {
+ hasValidator = true;
+ break;
+ }
+ }
+ if (hasValidator) {
+ break;
+ }
+ schema = schema.getEvaluationParentSchema();
+ }
+ return hasValidator;
+ }
+
+ @Override
+ protected MessageSourceValidationMessage.Builder message() {
+ return super.message().schemaNode(this.schemaNode);
+ }
+
+ /**
+ * 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<JsonNodeAnnotation.Builder> customizer) {
+ JsonNodeAnnotation.Builder builder = JsonNodeAnnotation.builder().evaluationPath(this.evaluationPath)
+ .schemaLocation(this.schemaLocation).keyword(getKeyword());
+ customizer.accept(builder);
+ executionContext.getAnnotations().put(builder.build());
+ }
+}
diff --git a/src/main/java/com/networknt/schema/CachedSupplier.java b/src/main/java/com/networknt/schema/CachedSupplier.java
new file mode 100644
index 0000000..109f36c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/CachedSupplier.java
@@ -0,0 +1,41 @@
+/*
+ * 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.Supplier;
+
+/**
+ * Supplier that caches the output.
+ *
+ * @param <T> the type cached
+ */
+public class CachedSupplier<T> implements Supplier<T> {
+ private final Supplier<T> delegate;
+ private T cache = null;
+
+ public CachedSupplier(Supplier<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public T get() {
+ if (cache == null) {
+ cache = delegate.get();
+ }
+ return cache;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/Collector.java b/src/main/java/com/networknt/schema/Collector.java
new file mode 100644
index 0000000..c8404e0
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Collector.java
@@ -0,0 +1,46 @@
+/*
+ * 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 <E> element
+ */
+public interface Collector<E> {
+
+
+ /**
+ * 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
new file mode 100644
index 0000000..2469fce
--- /dev/null
+++ b/src/main/java/com/networknt/schema/CollectorContext.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Context for holding the output returned by the {@link Collector}
+ * implementations.
+ */
+public class CollectorContext {
+ /**
+ * Map for holding the name and {@link Collector} or a simple Object.
+ */
+ private Map<String, Object> collectorMap = new HashMap<>();
+
+ /**
+ * Map for holding the name and {@link Collector} class collect method output.
+ */
+ private Map<String, Object> collectorLoadMap = new HashMap<>();
+
+ public CollectorContext() {
+ }
+
+
+ /**
+ * Adds a collector with give name. Preserving this method for backward
+ * compatibility.
+ *
+ * @param <E> element
+ * @param name String
+ * @param collector Collector
+ */
+ public <E> void add(String name, Collector<E> collector) {
+ this.collectorMap.put(name, collector);
+ }
+
+ /**
+ * Adds a collector or a simple object with give name.
+ *
+ * @param <E> element
+ * @param object Object
+ * @param name String
+ */
+ public <E> void add(String name, Object object) {
+ this.collectorMap.put(name, object);
+ }
+
+ /**
+ * 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.
+ * <p>
+ * 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.
+ *
+ * @param name String
+ * @return Object
+ */
+ public Object get(String name) {
+ Object object = this.collectorMap.get(name);
+ if (object instanceof Collector<?> && (this.collectorLoadMap.get(name) != null)) {
+ return this.collectorLoadMap.get(name);
+ }
+ return this.collectorMap.get(name);
+ }
+
+ /**
+ * Gets the collector map.
+ *
+ * @return the collector map
+ */
+ public Map<String, Object> getCollectorMap() {
+ return this.collectorMap;
+ }
+
+ /**
+ * Returns all the collected data. Please look into {@link #get(String)} method for more details.
+ * @return Map
+ */
+ public Map<String, Object> getAll() {
+ Map<String, Object> mergedMap = new HashMap<>();
+ mergedMap.putAll(this.collectorMap);
+ mergedMap.putAll(this.collectorLoadMap);
+ return mergedMap;
+ }
+
+ /**
+ * Combines data with Collector identified by the given name.
+ *
+ * @param name String
+ * @param data Object
+ */
+ public void combineWithCollector(String name, Object data) {
+ Object object = this.collectorMap.get(name);
+ if (object instanceof Collector<?>) {
+ Collector<?> collector = (Collector<?>) object;
+ collector.combine(data);
+ }
+ }
+
+ /**
+ * Loads data from all collectors.
+ */
+ void loadCollectors() {
+ Set<Entry<String, Object>> entrySet = this.collectorMap.entrySet();
+ for (Entry<String, Object> entry : entrySet) {
+ if (entry.getValue() instanceof Collector<?>) {
+ Collector<?> collector = (Collector<?>) entry.getValue();
+ this.collectorLoadMap.put(entry.getKey(), collector.collect());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ConstValidator.java b/src/main/java/com/networknt/schema/ConstValidator.java
new file mode 100644
index 0000000..291144b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ConstValidator.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * {@link JsonValidator} for const.
+ */
+public class ConstValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class);
+
+ public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (schemaNode.isNumber() && node.isNumber()) {
+ if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(schemaNode.asText(), node.asText())
+ .build());
+ }
+ } else if (!schemaNode.equals(node)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText()).build());
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ContainsValidator.java b/src/main/java/com/networknt/schema/ContainsValidator.java
new file mode 100644
index 0000000..1ef0199
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ContainsValidator.java
@@ -0,0 +1,205 @@
+/*
+ * 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.annotation.JsonNodeAnnotation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.networknt.schema.VersionCode.MinV201909;
+
+/**
+ * {@link JsonValidator} for contains.
+ */
+public class ContainsValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ContainsValidator.class);
+ private static final String CONTAINS_MAX = "contains.max";
+ private static final String CONTAINS_MIN = "contains.min";
+
+ private final JsonSchema schema;
+ private final boolean isMinV201909;
+
+ private Integer min = null;
+ private Integer max = null;
+
+ private Boolean hasUnevaluatedItemsValidator = null;
+
+ public ContainsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONTAINS, validationContext);
+
+ // 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 = MinV201909.getVersions().contains(this.validationContext.getMetaSchema().getSpecification());
+
+ if (schemaNode.isObject() || schemaNode.isBoolean()) {
+ this.schema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ JsonNode parentSchemaNode = parentSchema.getSchemaNode();
+ Optional.ofNullable(parentSchemaNode.get(ValidatorTypeCode.MAX_CONTAINS.getValue()))
+ .filter(JsonNode::canConvertToExactIntegral)
+ .ifPresent(node -> this.max = node.intValue());
+
+ Optional.ofNullable(parentSchemaNode.get(ValidatorTypeCode.MIN_CONTAINS.getValue()))
+ .filter(JsonNode::canConvertToExactIntegral)
+ .ifPresent(node -> this.min = node.intValue());
+ } else {
+ this.schema = null;
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // ignores non-arrays
+ Set<ValidationMessage> results = null;
+ int actual = 0, i = 0;
+ List<Integer> 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();
+ try {
+ executionContext.setFailFast(false);
+ for (JsonNode n : node) {
+ JsonNodePath path = instanceLocation.append(i);
+ if (this.schema.validate(executionContext, n, rootNode, path).isEmpty()) {
+ ++actual;
+ indexes.add(i);
+ }
+ ++i;
+ }
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+ }
+ int m = 1; // default to 1 if "min" not specified
+ if (this.min != null) {
+ m = this.min;
+ }
+ if (actual < m) {
+ results = boundsViolated(isMinV201909 ? ValidatorTypeCode.MIN_CONTAINS : ValidatorTypeCode.CONTAINS,
+ executionContext.getExecutionConfig().getLocale(),
+ executionContext.isFailFast(), node, instanceLocation, m);
+ }
+
+ if (this.max != null && actual > this.max) {
+ results = boundsViolated(isMinV201909 ? ValidatorTypeCode.MAX_CONTAINS : ValidatorTypeCode.CONTAINS,
+ executionContext.getExecutionConfig().getLocale(),
+ executionContext.isFailFast(), node, instanceLocation, this.max);
+ }
+ }
+
+ boolean collectAnnotations = collectAnnotations();
+ 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(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword("contains").value(true).build());
+ } else {
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).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.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath.append(minContainsKeyword))
+ .schemaLocation(this.schemaLocation.append(minContainsKeyword))
+ .keyword(minContainsKeyword).value(this.min).build());
+ }
+ }
+
+ if (this.max != null) {
+ String maxContainsKeyword = "maxContains";
+ if (collectAnnotations || collectAnnotations(executionContext, maxContainsKeyword)) {
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath.append(maxContainsKeyword))
+ .schemaLocation(this.schemaLocation.append(maxContainsKeyword))
+ .keyword(maxContainsKeyword).value(this.max).build());
+ }
+ }
+ }
+ return results == null ? Collections.emptySet() : results;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ Optional.ofNullable(this.schema).ifPresent(JsonSchema::initializeValidators);
+ collectAnnotations(); // cache the flag
+ }
+
+ private Set<ValidationMessage> boundsViolated(ValidatorTypeCode validatorTypeCode, Locale locale, boolean failFast,
+ JsonNode instanceNode, JsonNodePath instanceLocation, int bounds) {
+ String messageKey = "contains";
+ if (ValidatorTypeCode.MIN_CONTAINS.equals(validatorTypeCode)) {
+ messageKey = CONTAINS_MIN;
+ } else if (ValidatorTypeCode.MAX_CONTAINS.equals(validatorTypeCode)) {
+ messageKey = CONTAINS_MAX;
+ }
+ return Collections
+ .singleton(message().instanceNode(instanceNode).instanceLocation(instanceLocation).messageKey(messageKey)
+ .locale(locale).failFast(failFast).arguments(String.valueOf(bounds), this.schema.getSchemaNode().toString())
+ .code(validatorTypeCode.getErrorCode()).type(validatorTypeCode.getValue()).build());
+ }
+
+ /**
+ * Determine if annotations must be collected for evaluation.
+ * <p>
+ * This will be collected regardless of whether it is needed for reporting.
+ *
+ * @return true if annotations must be collected for evaluation.
+ */
+ private boolean collectAnnotations() {
+ return hasUnevaluatedItemsValidator();
+ }
+
+ private boolean hasUnevaluatedItemsValidator() {
+ if (this.hasUnevaluatedItemsValidator == null) {
+ this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems");
+ }
+ return hasUnevaluatedItemsValidator;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ContentEncodingValidator.java b/src/main/java/com/networknt/schema/ContentEncodingValidator.java
new file mode 100644
index 0000000..11e79ea
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ContentEncodingValidator.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 com.fasterxml.jackson.databind.JsonNode;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Set;
+/**
+ * {@link JsonValidator} for contentEncoding.
+ * <p>
+ * Note that since 2019-09 this keyword only generates annotations and not
+ * assertions.
+ */
+public class ContentEncodingValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ContentEncodingValidator.class);
+ private String contentEncoding;
+
+ /**
+ * Constructor.
+ *
+ * @param schemaLocation the schema location
+ * @param evaluationPath the evaluation path
+ * @param schemaNode the schema node
+ * @param parentSchema the parent schema
+ * @param validationContext the validation context
+ */
+ public ContentEncodingValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONTENT_ENCODING,
+ validationContext);
+ 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // Ignore non-strings
+ JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
+ if (nodeType != JsonType.STRING) {
+ return Collections.emptySet();
+ }
+
+ if (collectAnnotations(executionContext)) {
+ putAnnotation(executionContext,
+ annotation -> annotation.instanceLocation(instanceLocation).value(this.contentEncoding));
+ }
+
+ if (!matches(node.asText())) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.contentEncoding)
+ .build());
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java b/src/main/java/com/networknt/schema/ContentMediaTypeValidator.java
new file mode 100644
index 0000000..f4dd194
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ContentMediaTypeValidator.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;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * {@link JsonValidator} for contentMediaType.
+ * <p>
+ * Note that since 2019-09 this keyword only generates annotations and not assertions.
+ */
+public class ContentMediaTypeValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ContentMediaTypeValidator.class);
+ 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 evaluationPath the evaluation path
+ * @param schemaNode the schema node
+ * @param parentSchema the parent schema
+ * @param validationContext the validation context
+ */
+ public ContentMediaTypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONTENT_MEDIA_TYPE, validationContext);
+ 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // Ignore non-strings
+ JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
+ if (nodeType != JsonType.STRING) {
+ return Collections.emptySet();
+ }
+
+ if (collectAnnotations(executionContext)) {
+ putAnnotation(executionContext,
+ annotation -> annotation.instanceLocation(instanceLocation).value(this.contentMediaType));
+ }
+
+ if (!matches(node.asText())) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.contentMediaType)
+ .build());
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/CustomErrorMessageType.java b/src/main/java/com/networknt/schema/CustomErrorMessageType.java
new file mode 100644
index 0000000..ef08734
--- /dev/null
+++ b/src/main/java/com/networknt/schema/CustomErrorMessageType.java
@@ -0,0 +1,35 @@
+/*
+ * 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 class CustomErrorMessageType implements ErrorMessageType {
+ private final String errorCode;
+
+ private CustomErrorMessageType(String errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public static ErrorMessageType of(String errorCode) {
+ return new CustomErrorMessageType(errorCode);
+ }
+
+ @Override
+ public String getErrorCode() {
+ return errorCode;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/DefaultJsonMetaSchemaFactory.java b/src/main/java/com/networknt/schema/DefaultJsonMetaSchemaFactory.java
new file mode 100644
index 0000000..cefc60b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DefaultJsonMetaSchemaFactory.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Map.Entry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Default {@link JsonMetaSchemaFactory}.
+ */
+public class DefaultJsonMetaSchemaFactory implements JsonMetaSchemaFactory {
+ @Override
+ public JsonMetaSchema getMetaSchema(String iri, JsonSchemaFactory schemaFactory, SchemaValidatorsConfig config) {
+ // Is it a well-known dialect?
+ return SpecVersionDetector.detectOptionalVersion(iri)
+ .map(JsonSchemaFactory::checkVersion)
+ .map(JsonSchemaVersion::getInstance)
+ .orElseGet(() -> {
+ // Custom meta schema
+ return loadMetaSchema(iri, schemaFactory, config);
+ });
+ }
+
+ protected JsonMetaSchema loadMetaSchema(String iri, JsonSchemaFactory schemaFactory,
+ SchemaValidatorsConfig config) {
+ try {
+ return loadMetaSchemaBuilder(iri, schemaFactory, config).build();
+ } catch (InvalidSchemaException e) {
+ throw e;
+ } catch (Exception e) {
+ ValidationMessage validationMessage = ValidationMessage.builder()
+ .message("Failed to load meta-schema ''{1}''").arguments(iri).build();
+ throw new InvalidSchemaException(validationMessage, e);
+ }
+ }
+
+ protected JsonMetaSchema.Builder loadMetaSchemaBuilder(String iri, JsonSchemaFactory schemaFactory,
+ SchemaValidatorsConfig config) {
+ JsonSchema schema = schemaFactory.getSchema(SchemaLocation.of(iri), config);
+ JsonMetaSchema.Builder builder = JsonMetaSchema.builder(iri, schema.getValidationContext().getMetaSchema());
+ VersionFlag specification = schema.getValidationContext().getMetaSchema().getSpecification();
+ if (specification != null) {
+ if (specification.getVersionFlagValue() >= VersionFlag.V201909.getVersionFlagValue()) {
+ // Process vocabularies
+ JsonNode vocabulary = schema.getSchemaNode().get("$vocabulary");
+ if (vocabulary != null) {
+ builder.vocabularies(vocabularies -> vocabularies.clear());
+ for (Entry<String, JsonNode> vocabs : vocabulary.properties()) {
+ builder.vocabulary(vocabs.getKey(), vocabs.getValue().booleanValue());
+ }
+ }
+ }
+ }
+ return builder;
+ }
+
+ private static class Holder {
+ private static DefaultJsonMetaSchemaFactory INSTANCE = new DefaultJsonMetaSchemaFactory();
+ }
+
+ public static DefaultJsonMetaSchemaFactory getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/DependenciesValidator.java b/src/main/java/com/networknt/schema/DependenciesValidator.java
new file mode 100644
index 0000000..596e77a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DependenciesValidator.java
@@ -0,0 +1,104 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for dependencies.
+ */
+public class DependenciesValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(DependenciesValidator.class);
+ private final Map<String, List<String>> propertyDeps = new HashMap<String, List<String>>();
+ private final Map<String, JsonSchema> schemaDeps = new HashMap<String, JsonSchema>();
+
+ /**
+ * Constructor.
+ *
+ * @param schemaLocation the schema location
+ * @param evaluationPath the evaluation path
+ * @param schemaNode the schema node
+ * @param parentSchema the parent schema
+ * @param validationContext the validation context
+ */
+ public DependenciesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENCIES, validationContext);
+
+ for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ JsonNode pvalue = schemaNode.get(pname);
+ if (pvalue.isArray()) {
+ List<String> 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, validationContext.newSchema(schemaLocation.append(pname),
+ evaluationPath.append(pname), pvalue, parentSchema));
+ }
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ Set<ValidationMessage> errors = null;
+
+ for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ List<String> deps = propertyDeps.get(pname);
+ if (deps != null && !deps.isEmpty()) {
+ for (String field : deps) {
+ if (node.get(field) == null) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(propertyDeps.toString()).build());
+ }
+ }
+ }
+ JsonSchema schema = schemaDeps.get(pname);
+ if (schema != null) {
+ Set<ValidationMessage> schemaDepsErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ if (!schemaDepsErrors.isEmpty()) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.addAll(schemaDepsErrors);
+ }
+ }
+ }
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : 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
new file mode 100644
index 0000000..8350726
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DependentRequired.java
@@ -0,0 +1,72 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for dependentRequired.
+ */
+public class DependentRequired extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(DependentRequired.class);
+ private final Map<String, List<String>> propertyDependencies = new HashMap<String, List<String>>();
+
+ public DependentRequired(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_REQUIRED, validationContext);
+
+ for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ JsonNode pvalue = schemaNode.get(pname);
+ if (pvalue.isArray()) {
+ List<String> dependencies = propertyDependencies.computeIfAbsent(pname, k -> new ArrayList<>());
+
+ for (int i = 0; i < pvalue.size(); i++) {
+ dependencies.add(pvalue.get(i).asText());
+ }
+ }
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
+
+ for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ List<String> dependencies = propertyDependencies.get(pname);
+ if (dependencies != null && !dependencies.isEmpty()) {
+ for (String field : dependencies) {
+ if (node.get(field) == null) {
+ errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(field, pname)
+ .build());
+ }
+ }
+ }
+ }
+
+ return Collections.unmodifiableSet(errors);
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/DependentSchemas.java b/src/main/java/com/networknt/schema/DependentSchemas.java
new file mode 100644
index 0000000..253ed04
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DependentSchemas.java
@@ -0,0 +1,83 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for dependentSchemas.
+ */
+public class DependentSchemas extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(DependentSchemas.class);
+ private final Map<String, JsonSchema> schemaDependencies = new HashMap<>();
+
+ public DependentSchemas(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_SCHEMAS, validationContext);
+
+ for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ JsonNode pvalue = schemaNode.get(pname);
+ if (pvalue.isObject() || pvalue.isBoolean()) {
+ this.schemaDependencies.put(pname, validationContext.newSchema(schemaLocation.append(pname),
+ evaluationPath.append(pname), pvalue, parentSchema));
+ }
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ Set<ValidationMessage> errors = null;
+ for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
+ String pname = it.next();
+ JsonSchema schema = this.schemaDependencies.get(pname);
+ if (schema != null) {
+ Set<ValidationMessage> schemaDependenciesErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ if (!schemaDependenciesErrors.isEmpty()) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.addAll(schemaDependenciesErrors);
+ }
+ }
+ }
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(this.schemaDependencies.values());
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+ for (JsonSchema schema : this.schemaDependencies.values()) {
+ schema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactory.java b/src/main/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactory.java
new file mode 100644
index 0000000..938f424
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactory.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;
+
+/**
+ * A {@link JsonMetaSchemaFactory} that does not meta-schemas that aren't
+ * explicitly configured in the {@link JsonSchemaFactory}.
+ */
+public class DisallowUnknownJsonMetaSchemaFactory implements JsonMetaSchemaFactory {
+ @Override
+ public JsonMetaSchema getMetaSchema(String iri, JsonSchemaFactory schemaFactory, SchemaValidatorsConfig config) {
+ throw new InvalidSchemaException(ValidationMessage.builder()
+ .message("Unknown meta-schema ''{1}''. Only meta-schemas that are explicitly configured can be used.")
+ .arguments(iri).build());
+ }
+
+ private static class Holder {
+ private static DisallowUnknownJsonMetaSchemaFactory INSTANCE = new DisallowUnknownJsonMetaSchemaFactory();
+ }
+
+ public static DisallowUnknownJsonMetaSchemaFactory getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/DisallowUnknownKeywordFactory.java b/src/main/java/com/networknt/schema/DisallowUnknownKeywordFactory.java
new file mode 100644
index 0000000..3a66505
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DisallowUnknownKeywordFactory.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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, ValidationContext validationContext) {
+ logger.error("Keyword '{}' is unknown and must be configured on the meta-schema or vocabulary", value);
+ throw new InvalidSchemaException(ValidationMessage.builder()
+ .message("Keyword ''{1}'' is unknown and must be configured on the meta-schema or vocabulary")
+ .arguments(value).build());
+ }
+
+ private static class Holder {
+ private static DisallowUnknownKeywordFactory INSTANCE = new DisallowUnknownKeywordFactory();
+ }
+
+ public static DisallowUnknownKeywordFactory getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/DiscriminatorContext.java b/src/main/java/com/networknt/schema/DiscriminatorContext.java
new file mode 100644
index 0000000..1ab2ddd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DiscriminatorContext.java
@@ -0,0 +1,58 @@
+package com.networknt.schema;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class DiscriminatorContext {
+ private final Map<String, ObjectNode> discriminators = new HashMap<>();
+
+ private boolean discriminatorMatchFound = false;
+
+ private boolean discriminatorIgnore = false;
+
+ public void registerDiscriminator(final SchemaLocation schemaLocation, final ObjectNode discriminator) {
+ this.discriminators.put("#" + schemaLocation.getFragment().toString(), discriminator);
+ }
+
+ public ObjectNode getDiscriminatorForPath(final SchemaLocation schemaLocation) {
+ return this.discriminators.get("#" + schemaLocation.getFragment().toString());
+ }
+
+ public ObjectNode getDiscriminatorForPath(final String schemaLocation) {
+ return this.discriminators.get(schemaLocation);
+ }
+
+ public void markMatch() {
+ this.discriminatorMatchFound = true;
+ }
+
+ /**
+ * Indicate that discriminator processing should be ignored.
+ * <p>
+ * This is used when the discriminator property value is missing from the data.
+ * <p>
+ * See issue #436 for background.
+ */
+ public void markIgnore() {
+ this.discriminatorIgnore = true;
+ }
+
+ public boolean isDiscriminatorMatchFound() {
+ return this.discriminatorMatchFound;
+ }
+
+ public boolean isDiscriminatorIgnore() {
+ return this.discriminatorIgnore;
+ }
+
+ /**
+ * 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 !this.discriminators.isEmpty();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/DiscriminatorValidator.java b/src/main/java/com/networknt/schema/DiscriminatorValidator.java
new file mode 100644
index 0000000..d4b1135
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DiscriminatorValidator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * {@link JsonValidator} that resolves discriminator.
+ */
+public class DiscriminatorValidator extends BaseJsonValidator {
+ private final String propertyName;
+ private final Map<String, String> mapping;
+
+ public DiscriminatorValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DISCRIMINATOR,
+ validationContext);
+ ObjectNode discriminator = schemaNode.isObject() ? (ObjectNode) schemaNode : null;
+ if (discriminator != null) {
+ JsonNode propertyName = discriminator.get("propertyName");
+ 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<Entry<String, JsonNode>> iter = mapping.fields(); iter.hasNext();) {
+ Entry<String, JsonNode> entry = iter.next();
+ this.mapping.put(entry.getKey(), entry.getValue().asText());
+ }
+ } else {
+ this.mapping = Collections.emptyMap();
+ }
+ } else {
+ this.propertyName = "";
+ this.mapping = Collections.emptyMap();
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ return Collections.emptySet();
+ }
+
+ /**
+ * 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<String, String> getMapping() {
+ return mapping;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/DynamicRefValidator.java b/src/main/java/com/networknt/schema/DynamicRefValidator.java
new file mode 100644
index 0000000..6f72993
--- /dev/null
+++ b/src/main/java/com/networknt/schema/DynamicRefValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} that resolves $dynamicRef.
+ */
+public class DynamicRefValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(DynamicRefValidator.class);
+
+ protected final JsonSchemaRef schema;
+
+ public DynamicRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DYNAMIC_REF, validationContext);
+ String refValue = schemaNode.asText();
+ this.schema = getRefSchema(parentSchema, validationContext, refValue, evaluationPath);
+ }
+
+ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue,
+ JsonNodePath evaluationPath) {
+ String ref = resolve(parentSchema, refValue);
+ return new JsonSchemaRef(new CachedSupplier<>(() -> {
+ JsonSchema refSchema = validationContext.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
+ JsonSchemaRef r = RefValidator.getRefSchema(parentSchema, validationContext, refValue, evaluationPath);
+ if (r != null) {
+ refSchema = r.getSchema();
+ }
+ } else {
+ // Check parents
+ JsonSchema base = parentSchema;
+ int index = ref.indexOf("#");
+ String anchor = ref.substring(index);
+ String absoluteIri = ref.substring(0, index);
+ while (base.getEvaluationParentSchema() != null) {
+ base = base.getEvaluationParentSchema();
+ String baseAbsoluteIri = base.getSchemaLocation().getAbsoluteIri() != null ? base.getSchemaLocation().getAbsoluteIri().toString() : "";
+ if (!baseAbsoluteIri.equals(absoluteIri)) {
+ absoluteIri = baseAbsoluteIri;
+ String parentRef = SchemaLocation.resolve(base.getSchemaLocation(), anchor);
+ JsonSchema parentRefSchema = validationContext.getDynamicAnchors().get(parentRef);
+ if (parentRefSchema != null) {
+ refSchema = parentRefSchema;
+ }
+ }
+ }
+ }
+
+ if (refSchema != null) {
+ refSchema = refSchema.fromRef(parentSchema, evaluationPath);
+ }
+ return refSchema;
+ }));
+ }
+
+ private static String resolve(JsonSchema parentSchema, String refValue) {
+ // $ref prevents a sibling $id from changing the base uri
+ JsonSchema base = parentSchema;
+ if (parentSchema.getId() != null && parentSchema.parentSchema != null) {
+ base = parentSchema.parentSchema;
+ }
+ return SchemaLocation.resolve(base.getSchemaLocation(), refValue);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.DYNAMIC_REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ debug(logger, node, rootNode, instanceLocation);
+ // 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.
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.DYNAMIC_REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ }
+
+ public JsonSchemaRef getSchemaRef() {
+ return this.schema;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ JsonSchema jsonSchema = null;
+ try {
+ jsonSchema = this.schema.getSchema();
+ } catch (JsonSchemaException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw new JsonSchemaException(e);
+ }
+ // 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();
+ JsonSchema check = jsonSchema;
+ boolean circularDependency = false;
+ while (check.getEvaluationParentSchema() != null) {
+ check = check.getEvaluationParentSchema();
+ if (check.getSchemaLocation().equals(schemaLocation)) {
+ circularDependency = true;
+ break;
+ }
+ }
+ if (!circularDependency) {
+ jsonSchema.initializeValidators();
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java
new file mode 100644
index 0000000..582666d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/EnumValidator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.fasterxml.jackson.databind.node.DecimalNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for enum.
+ */
+public class EnumValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class);
+
+ private final Set<JsonNode> nodes;
+ private final String error;
+
+ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext);
+ if (schemaNode != null && schemaNode.isArray()) {
+ nodes = new HashSet<JsonNode>();
+ 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(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]";
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (node.isNumber()) {
+ node = processNumberNode(node);
+ } else if (node.isArray()) {
+ node = processArrayNode((ArrayNode) node);
+ }
+ if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(error).build());
+ }
+
+ return Collections.emptySet();
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 = (ArrayNode) 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/ErrorMessageType.java b/src/main/java/com/networknt/schema/ErrorMessageType.java
new file mode 100644
index 0000000..72b0acb
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ErrorMessageType.java
@@ -0,0 +1,38 @@
+/*
+ * 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 ErrorMessageType {
+ /**
+ * Your error code. Please ensure global uniqueness. Builtin error codes are sequential numbers.
+ * <p>
+ * Customer error codes could have a prefix to denote the namespace of your custom keywords and errors.
+ *
+ * @return error code
+ */
+ String getErrorCode();
+
+ /**
+ * Get the text representation of the error code.
+ *
+ * @return The error code value.
+ */
+ default String getErrorCodeValue() {
+ return getErrorCode();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java
new file mode 100644
index 0000000..35db8dd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.utils.JsonNodeUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for exclusiveMaximum.
+ */
+public class ExclusiveMaximumValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ExclusiveMaximumValidator.class);
+
+ private final ThresholdMixin typedMaximum;
+
+ public ExclusiveMaximumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MAXIMUM, validationContext);
+ if (!schemaNode.isNumber()) {
+ throw new JsonSchemaException("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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!JsonNodeUtil.isNumber(node, validationContext.getConfig())) {
+ // maximum only applies to numbers
+ return Collections.emptySet();
+ }
+
+ if (typedMaximum.crossesThreshold(node)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(typedMaximum.thresholdValue()).build());
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java
new file mode 100644
index 0000000..ab2568f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java
@@ -0,0 +1,124 @@
+/*
+ * 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.utils.JsonNodeUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for exclusiveMinimum.
+ */
+public class ExclusiveMinimumValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ExclusiveMinimumValidator.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;
+
+ public ExclusiveMinimumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MINIMUM, validationContext);
+ if (!schemaNode.isNumber()) {
+ throw new JsonSchemaException("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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
+ // minimum only applies to numbers
+ return Collections.emptySet();
+ }
+
+ if (typedMinimum.crossesThreshold(node)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(typedMinimum.thresholdValue()).build());
+ }
+ 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 0000000..516241d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ExecutionConfig.java
@@ -0,0 +1,183 @@
+/*
+ * 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 {
+ /**
+ * The locale to use for formatting messages.
+ */
+ private Locale locale = Locale.ROOT;
+
+ /**
+ * Determines if annotation collection is enabled.
+ * <p>
+ * This does not affect annotation collection required for evaluating keywords
+ * such as unevaluatedItems or unevaluatedProperties and only affects reporting.
+ */
+ private boolean annotationCollectionEnabled = false;
+
+ /**
+ * If annotation collection is enabled, determine which annotations to collect.
+ * <p>
+ * This does not affect annotation collection required for evaluating keywords
+ * such as unevaluatedItems or unevaluatedProperties and only affects reporting.
+ */
+ private Predicate<String> annotationCollectionFilter = keyword -> false;
+
+ /**
+ * Since Draft 2019-09 format assertions are not enabled by default.
+ */
+ private Boolean formatAssertionsEnabled = null;
+
+ /**
+ * Determine if the validation execution can fail fast.
+ */
+ private boolean failFast = false;
+
+ /**
+ * Gets the locale to use for formatting messages.
+ *
+ * @return the locale
+ */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * Sets the locale to use for formatting messages.
+ *
+ * @param locale the locale
+ */
+ public void setLocale(Locale locale) {
+ this.locale = Objects.requireNonNull(locale, "Locale must not be null");
+ }
+
+ /**
+ * Gets the format assertion enabled flag.
+ * <p>
+ * This defaults to null meaning that it will follow the defaults of the
+ * specification.
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Sets the format assertion enabled flag.
+ *
+ * @param formatAssertionsEnabled the format assertions enabled flag
+ */
+ public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
+ this.formatAssertionsEnabled = formatAssertionsEnabled;
+ }
+
+ /**
+ * Return if fast fail is enabled.
+ *
+ * @return if fast fail is enabled
+ */
+ public boolean isFailFast() {
+ return failFast;
+ }
+
+ /**
+ * Sets whether fast fail is enabled.
+ *
+ * @param failFast true to fast fail
+ */
+ public void setFailFast(boolean failFast) {
+ this.failFast = failFast;
+ }
+
+ /**
+ * Return if annotation collection is enabled.
+ * <p>
+ * This does not affect annotation collection required for evaluating keywords
+ * such as unevaluatedItems or unevaluatedProperties and only affects reporting.
+ * <p>
+ * The annotations to collect can be customized using the annotation collection
+ * predicate.
+ *
+ * @return if annotation collection is enabled
+ */
+ public boolean isAnnotationCollectionEnabled() {
+ return annotationCollectionEnabled;
+ }
+
+ /**
+ * Sets whether the annotation collection is enabled.
+ * <p>
+ * This does not affect annotation collection required for evaluating keywords
+ * such as unevaluatedItems or unevaluatedProperties and only affects reporting.
+ * <p>
+ * The annotations to collect can be customized using the annotation collection
+ * predicate.
+ *
+ * @param annotationCollectionEnabled true to enable annotation collection
+ */
+ public void setAnnotationCollectionEnabled(boolean annotationCollectionEnabled) {
+ this.annotationCollectionEnabled = 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.
+ * <p>
+ * The default value is to not collect any annotation keywords if annotation
+ * collection is enabled.
+ * <p>
+ * 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<String> getAnnotationCollectionFilter() {
+ return annotationCollectionFilter;
+ }
+
+ /**
+ * Predicate to determine if annotation collection is allowed for a particular
+ * keyword. This only has an effect if annotation collection is enabled.
+ * <p>
+ * The default value is to not collect any annotation keywords if annotation
+ * collection is enabled.
+ * <p>
+ * 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
+ */
+ public void setAnnotationCollectionFilter(Predicate<String> annotationCollectionFilter) {
+ this.annotationCollectionFilter = Objects.requireNonNull(annotationCollectionFilter,
+ "annotationCollectionFilter must not be null");
+ }
+
+}
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 0000000..8ca79cd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ExecutionContext.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.networknt.schema.annotation.JsonNodeAnnotations;
+import com.networknt.schema.result.JsonNodeResults;
+
+import java.util.Stack;
+
+/**
+ * Stores the execution context for the validation run.
+ */
+public class ExecutionContext {
+ private ExecutionConfig executionConfig;
+ private CollectorContext collectorContext = null;
+ private ValidatorState validatorState = null;
+ private Stack<DiscriminatorContext> discriminatorContexts = null;
+ private JsonNodeAnnotations annotations = null;
+ private JsonNodeResults results = null;
+
+ /**
+ * This is used during the execution to determine if the validator should fail fast.
+ * <p>
+ * This valid is determined by the previous validator.
+ */
+ private Boolean failFast = null;
+
+ /**
+ * Creates an execution context.
+ */
+ public ExecutionContext() {
+ this(new ExecutionConfig(), null);
+ }
+
+ /**
+ * Creates an execution context.
+ *
+ * @param collectorContext the collector context
+ */
+ public ExecutionContext(CollectorContext collectorContext) {
+ this(new ExecutionConfig(), 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;
+ }
+
+ /**
+ * 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 JsonNodeAnnotations getAnnotations() {
+ if (this.annotations == null) {
+ this.annotations = new JsonNodeAnnotations();
+ }
+ return annotations;
+ }
+
+ public JsonNodeResults getResults() {
+ if (this.results == null) {
+ this.results = new JsonNodeResults();
+ }
+ return results;
+ }
+
+ /**
+ * Determines if the validator should immediately throw a fail fast exception if
+ * an error has occurred.
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Gets the validator state.
+ *
+ * @return the validator state
+ */
+ public ValidatorState getValidatorState() {
+ return validatorState;
+ }
+
+ /**
+ * Sets the validator state.
+ *
+ * @param validatorState the validator state
+ */
+ public void setValidatorState(ValidatorState validatorState) {
+ this.validatorState = validatorState;
+ }
+
+ public DiscriminatorContext getCurrentDiscriminatorContext() {
+ if (this.discriminatorContexts == null) {
+ return null;
+ }
+
+ if (!this.discriminatorContexts.empty()) {
+ return this.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, @SuppressWarnings("unused") JsonNodePath instanceLocation) {
+ if (this.discriminatorContexts == null) {
+ this.discriminatorContexts = new Stack<>();
+ }
+ this.discriminatorContexts.push(ctx);
+ }
+
+ public void leaveDiscriminatorContextImmediately(@SuppressWarnings("unused") JsonNodePath instanceLocation) {
+ this.discriminatorContexts.pop();
+ }
+}
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 0000000..edaa949
--- /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.
+ * <p>
+ * The validation context should only be used for reference as it is shared.
+ *
+ * @param executionContext the execution context
+ * @param validationContext the validation context for reference
+ */
+ void customize(ExecutionContext executionContext, ValidationContext validationContext);
+} \ 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 0000000..6ea3cf4
--- /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.Objects;
+import java.util.Set;
+
+/**
+ * Thrown when an assertion happens and the evaluation can fail fast.
+ * <p>
+ * This doesn't extend off JsonSchemaException as it is used for flow control
+ * and is intended to be caught in a specific place.
+ * <p>
+ * This will be caught in the JsonSchema validate method to be passed to the
+ * output formatter.
+ */
+public class FailFastAssertionException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private final ValidationMessage validationMessage;
+
+ /**
+ * Constructor.
+ *
+ * @param validationMessage the validation message
+ */
+ public FailFastAssertionException(ValidationMessage validationMessage) {
+ this.validationMessage = Objects.requireNonNull(validationMessage);
+ }
+
+ /**
+ * Gets the validation message.
+ *
+ * @return the validation message
+ */
+ public ValidationMessage getValidationMessage() {
+ return this.validationMessage;
+ }
+
+ /**
+ * Gets the validation message.
+ *
+ * @return the validation message
+ */
+ public Set<ValidationMessage> getValidationMessages() {
+ return Collections.singleton(this.validationMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ return this.validationMessage != null ? this.validationMessage.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
new file mode 100644
index 0000000..8018a76
--- /dev/null
+++ b/src/main/java/com/networknt/schema/FalseValidator.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * {@link JsonValidator} for false.
+ */
+public class FalseValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(FalseValidator.class);
+
+ private final String reason;
+
+ public FalseValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.FALSE, validationContext);
+ this.reason = this.evaluationPath.getParent().getName(-1);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ // For the false validator, it is always not valid
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(reason).build());
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Format.java b/src/main/java/com/networknt/schema/Format.java
new file mode 100644
index 0000000..6f79b10
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Format.java
@@ -0,0 +1,158 @@
+/*
+ * 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;
+import java.util.function.Supplier;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Used to implement the various formats for the format keyword.
+ * <p>
+ * 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.
+ * <p>
+ * See jsv-messages.properties.
+ * <p>
+ * The following are the arguments.<br>
+ * {0} The instance location<br>
+ * {1} The format name<br>
+ * {2} The error message description<br>
+ * {3} The input value
+ *
+ * @return the message key
+ */
+ default String getMessageKey() {
+ return "format";
+ }
+
+ /**
+ * Gets the error message description.
+ * <p>
+ * Deprecated. Override getMessageKey() and set the localized message in the
+ * resource bundle or message source.
+ *
+ * @return the error message description.
+ */
+ @Deprecated
+ default String getErrorMessageDescription() {
+ return "";
+ }
+
+
+ /**
+ * Determines if the value matches the format.
+ * <p>
+ * 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 validationContext the validation context
+ * @param value to match
+ * @return true if matches
+ */
+ default boolean matches(ExecutionContext executionContext, ValidationContext validationContext, String value) {
+ return matches(executionContext, value);
+ }
+
+ /**
+ * Determines if the value matches the format.
+ *
+ * @param executionContext the execution context
+ * @param validationContext the validation context
+ * @param value to match
+ * @return true if matches
+ */
+ default boolean matches(ExecutionContext executionContext, ValidationContext validationContext, JsonNode value) {
+ JsonType nodeType = TypeFactory.getValueNodeType(value, validationContext.getConfig());
+ if (nodeType != JsonType.STRING) {
+ return true;
+ }
+ return matches(executionContext, validationContext, value.textValue());
+ }
+
+ /**
+ * Determines if the value matches the format.
+ * <p>
+ * This can be implemented for non-string node types.
+ *
+ * @param executionContext the execution context
+ * @param validationContext the validation 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, ValidationContext validationContext, JsonNode node,
+ JsonNode rootNode, JsonNodePath instanceLocation, boolean assertionsEnabled, FormatValidator formatValidator) {
+ return matches(executionContext, validationContext, node);
+ }
+
+ /**
+ * Validates the format.
+ * <p>
+ * This is the most flexible method to implement.
+ *
+ * @param executionContext the execution context
+ * @param validationContext the validation 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
+ * @return the messages
+ */
+ default Set<ValidationMessage> validate(ExecutionContext executionContext, ValidationContext validationContext,
+ JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean assertionsEnabled,
+ Supplier<MessageSourceValidationMessage.Builder> message,
+ FormatValidator formatValidator) {
+ if (assertionsEnabled) {
+ if (!matches(executionContext, validationContext, node, rootNode, instanceLocation, assertionsEnabled,
+ formatValidator)) {
+ return Collections
+ .singleton(message.get()
+ .arguments(this.getName(), this.getErrorMessageDescription(), node.asText()).build());
+ }
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/FormatKeyword.java b/src/main/java/com/networknt/schema/FormatKeyword.java
new file mode 100644
index 0000000..cb2031f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/FormatKeyword.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 java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Format Keyword.
+ */
+public class FormatKeyword implements Keyword {
+ private final String value;
+ private final ErrorMessageType errorMessageType;
+ private final Map<String, Format> formats;
+
+ public FormatKeyword(Map<String, Format> formats) {
+ this(ValidatorTypeCode.FORMAT, formats);
+ }
+
+ public FormatKeyword(ValidatorTypeCode type, Map<String, Format> formats) {
+ this(type.getValue(), type, formats);
+ }
+
+ public FormatKeyword(String value, ErrorMessageType errorMessageType, Map<String, Format> formats) {
+ this.value = value;
+ this.formats = formats;
+ this.errorMessageType = errorMessageType;
+ }
+
+ Collection<Format> getFormats() {
+ return Collections.unmodifiableCollection(this.formats.values());
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ Format format = null;
+ if (schemaNode != null && schemaNode.isTextual()) {
+ String formatName = schemaNode.textValue();
+ format = this.formats.get(formatName);
+ }
+ return new FormatValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext, format,
+ errorMessageType, this);
+ }
+
+ @Override
+ public String getValue() {
+ return this.value;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java
new file mode 100644
index 0000000..9665762
--- /dev/null
+++ b/src/main/java/com/networknt/schema/FormatValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.format.BaseFormatJsonValidator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Validator for Format.
+ */
+public class FormatValidator extends BaseFormatJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(FormatValidator.class);
+
+ private final Format format;
+
+ public FormatValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext, Format format,
+ ErrorMessageType errorMessageType, Keyword keyword) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, errorMessageType, keyword, validationContext);
+ this.format = format;
+ }
+
+ public FormatValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext, Format format, ValidatorTypeCode type) {
+ this(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext, format, type, type);
+ }
+
+ /**
+ * 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, 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 {
+ return format.validate(executionContext, validationContext, node, rootNode, instanceLocation,
+ assertionsEnabled,
+ () -> this.message().instanceNode(node).instanceLocation(instanceLocation)
+ .messageKey(format.getMessageKey())
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()),
+ 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);
+ return Collections.emptySet();
+ }
+ } else {
+ return 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
+ * @return the messages
+ */
+ protected Set<ValidationMessage> validateUnknownFormat(ExecutionContext executionContext,
+ JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ /*
+ * Unknown formats should create an assertion if the vocab is specified
+ * according to the specification.
+ */
+ if (createUnknownFormatAssertions(executionContext) && this.schemaNode.isTextual()) {
+ return Collections.singleton(message().instanceLocation(instanceLocation).instanceNode(node)
+ .messageKey("format.unknown").arguments(schemaNode.textValue()).build());
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * When the Format-Assertion vocabulary is specified, implementations MUST fail
+ * upon encountering unknown formats.
+ * <p>
+ * Note that this is different from setting the setFormatAssertionsEnabled
+ * configuration option.
+ * <p>
+ * 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.
+ * <p>
+ * Note that this defaults to false.
+ *
+ * @param executionContext the execution context
+ * @return whether to perform strict handling
+ */
+ protected boolean isStrict(ExecutionContext executionContext) {
+ return this.validationContext.getConfig().isStrict(getKeyword(), Boolean.FALSE);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Formats.java b/src/main/java/com/networknt/schema/Formats.java
new file mode 100644
index 0000000..c9dd0bd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Formats.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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.networknt.schema.format.DateFormat;
+import com.networknt.schema.format.DateTimeFormat;
+import com.networknt.schema.format.DurationFormat;
+import com.networknt.schema.format.EmailFormat;
+import com.networknt.schema.format.IPv6Format;
+import com.networknt.schema.format.IdnEmailFormat;
+import com.networknt.schema.format.IdnHostnameFormat;
+import com.networknt.schema.format.IriFormat;
+import com.networknt.schema.format.IriReferenceFormat;
+import com.networknt.schema.format.PatternFormat;
+import com.networknt.schema.format.RegexFormat;
+import com.networknt.schema.format.TimeFormat;
+import com.networknt.schema.format.UriFormat;
+import com.networknt.schema.format.UriReferenceFormat;
+
+/**
+ * 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<Format> DEFAULT;
+
+ static {
+ List<Format> 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/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java
new file mode 100644
index 0000000..c3c340a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/IfValidator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for if.
+ */
+public class IfValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(IfValidator.class);
+
+ private static final List<String> KEYWORDS = Arrays.asList("if", "then", "else");
+
+ private final JsonSchema ifSchema;
+ private final JsonSchema thenSchema;
+ private final JsonSchema elseSchema;
+
+ public IfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.IF_THEN_ELSE, validationContext);
+
+ JsonSchema foundIfSchema = null;
+ JsonSchema foundThenSchema = null;
+ JsonSchema foundElseSchema = null;
+
+ for (final String keyword : KEYWORDS) {
+ final JsonNode node = parentSchema.getSchemaNode().get(keyword);
+ final SchemaLocation schemaLocationOfSchema = parentSchema.schemaLocation.append(keyword);
+ final JsonNodePath evaluationPathOfSchema = parentSchema.evaluationPath.append(keyword);
+ if (keyword.equals("if")) {
+ foundIfSchema = validationContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node,
+ parentSchema);
+ } else if (keyword.equals("then") && node != null) {
+ foundThenSchema = validationContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node,
+ parentSchema);
+ } else if (keyword.equals("else") && node != null) {
+ foundElseSchema = validationContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node,
+ parentSchema);
+ }
+ }
+
+ this.ifSchema = foundIfSchema;
+ this.thenSchema = foundThenSchema;
+ this.elseSchema = foundElseSchema;
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ boolean ifConditionPassed = false;
+
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ executionContext.setFailFast(false);
+ ifConditionPassed = this.ifSchema.validate(executionContext, node, rootNode, instanceLocation).isEmpty();
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+ }
+
+ if (ifConditionPassed && this.thenSchema != null) {
+ return this.thenSchema.validate(executionContext, node, rootNode, instanceLocation);
+ } else if (!ifConditionPassed && this.elseSchema != null) {
+ return this.elseSchema.validate(executionContext, node, rootNode, instanceLocation);
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ if (null != this.ifSchema) {
+ this.ifSchema.initializeValidators();
+ }
+ if (null != this.thenSchema) {
+ this.thenSchema.initializeValidators();
+ }
+ if (null != this.elseSchema) {
+ this.elseSchema.initializeValidators();
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ if (null != this.ifSchema) {
+ this.ifSchema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+ if (null != this.thenSchema) {
+ this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+ if (null != this.elseSchema) {
+ this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, false);
+ }
+
+ return Collections.emptySet();
+ }
+
+}
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 0000000..f375dce
--- /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 0000000..e60a348
--- /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 JsonSchemaException {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidSchemaException(ValidationMessage message, Exception cause) {
+ super(Objects.requireNonNull(message));
+ this.initCause(cause);
+ }
+
+ public InvalidSchemaException(ValidationMessage 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 0000000..9aad3a5
--- /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(ValidationMessage message, Exception cause) {
+ super(message, cause);
+ }
+
+ public InvalidSchemaRefException(ValidationMessage message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
new file mode 100644
index 0000000..7aede16
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -0,0 +1,304 @@
+/*
+ * 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.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for items V4 to V2019-09.
+ */
+public class ItemsValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ItemsValidator.class);
+ private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems";
+
+ private final JsonSchema schema;
+ private final List<JsonSchema> tupleSchema;
+ private final Boolean additionalItems;
+ private final JsonSchema additionalSchema;
+
+ private Boolean hasUnevaluatedItemsValidator = null;
+
+ private final JsonNodePath additionalItemsEvaluationPath;
+ private final SchemaLocation additionalItemsSchemaLocation;
+ private final JsonNode additionalItemsSchemaNode;
+
+ public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
+
+ Boolean additionalItems = null;
+
+ this.tupleSchema = new ArrayList<>();
+ JsonSchema foundSchema = null;
+ JsonSchema foundAdditionalSchema = null;
+ JsonNode additionalItemsSchemaNode = null;
+
+ if (schemaNode.isObject() || schemaNode.isBoolean()) {
+ foundSchema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ } else {
+ int i = 0;
+ for (JsonNode s : schemaNode) {
+ this.tupleSchema.add(validationContext.newSchema(schemaLocation.append(i), evaluationPath.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 = validationContext.newSchema(
+ parentSchema.schemaLocation.append(PROPERTY_ADDITIONAL_ITEMS),
+ parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS), addItemNode, parentSchema);
+ }
+ }
+ }
+ this.additionalItems = additionalItems;
+ this.schema = foundSchema;
+ this.additionalSchema = foundAdditionalSchema;
+ this.additionalItemsEvaluationPath = parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS);
+ this.additionalItemsSchemaLocation = parentSchema.schemaLocation.append(PROPERTY_ADDITIONAL_ITEMS);
+ this.additionalItemsSchemaNode = additionalItemsSchemaNode;
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!node.isArray() && !this.validationContext.getConfig().isTypeLoose()) {
+ // ignores non-arrays
+ return Collections.emptySet();
+ }
+ boolean collectAnnotations = collectAnnotations();
+
+ // Add items annotation
+ if (collectAnnotations || collectAnnotations(executionContext)) {
+ if (this.schema != null) {
+ // Applies to all
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).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(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(schemas).build());
+ } else {
+ // Applies to all
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(true).build());
+ }
+ }
+ }
+
+ boolean hasAdditionalItem = false;
+ SetView<ValidationMessage> errors = new SetView<ValidationMessage>();
+ if (node.isArray()) {
+ int i = 0;
+ for (JsonNode n : node) {
+ if (doValidate(executionContext, errors, i, n, rootNode, instanceLocation)) {
+ hasAdditionalItem = true;
+ }
+ i++;
+ }
+ } else {
+ if (doValidate(executionContext, errors, 0, node, rootNode, instanceLocation)) {
+ hasAdditionalItem = true;
+ }
+ }
+
+ if (hasAdditionalItem) {
+ if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) {
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.additionalItemsEvaluationPath)
+ .schemaLocation(this.additionalItemsSchemaLocation)
+ .keyword("additionalItems").value(true).build());
+ }
+ }
+ return errors.isEmpty() ? Collections.emptySet() : errors;
+ }
+
+ private boolean doValidate(ExecutionContext executionContext, SetView<ValidationMessage> errors, int i, JsonNode node,
+ JsonNode rootNode, JsonNodePath instanceLocation) {
+ boolean isAdditionalItem = false;
+ JsonNodePath path = instanceLocation.append(i);
+
+ if (this.schema != null) {
+ // validate with item schema (the whole array has the same item
+ // schema)
+ Set<ValidationMessage> results = this.schema.validate(executionContext, node, rootNode, path);
+ if (!results.isEmpty()) {
+ errors.union(results);
+ }
+ } else if (this.tupleSchema != null) {
+ if (i < this.tupleSchema.size()) {
+ // validate against tuple schema
+ Set<ValidationMessage> results = this.tupleSchema.get(i).validate(executionContext, node, rootNode, path);
+ if (!results.isEmpty()) {
+ errors.union(results);
+ }
+ } else {
+ if ((this.additionalItems != null && this.additionalItems) || this.additionalSchema != null) {
+ isAdditionalItem = true;
+ }
+
+ if (this.additionalSchema != null) {
+ // validate against additional item schema
+ Set<ValidationMessage> results = this.additionalSchema.validate(executionContext, node, rootNode, path);
+ if (!results.isEmpty()) {
+ errors.union(results);
+ }
+ } else if (this.additionalItems != null) {
+ if (this.additionalItems) {
+// evaluatedItems.add(path);
+ } else {
+ // no additional item allowed, return error
+ errors.union(Collections.singleton(message().instanceNode(rootNode).instanceLocation(instanceLocation)
+ .type("additionalItems")
+ .messageKey("additionalItems")
+ .evaluationPath(this.additionalItemsEvaluationPath)
+ .schemaLocation(this.additionalItemsSchemaLocation)
+ .schemaNode(this.additionalItemsSchemaNode)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(i).build()));
+ }
+ }
+ }
+ }
+ return isAdditionalItem;
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ HashSet<ValidationMessage> validationMessages = new LinkedHashSet<>();
+ if (node instanceof ArrayNode) {
+ ArrayNode arrayNode = (ArrayNode) node;
+ JsonNode defaultNode = null;
+ if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
+ && this.schema != null) {
+ defaultNode = getDefaultNode(this.schema);
+ }
+ int i = 0;
+ for (JsonNode n : arrayNode) {
+ if (n.isNull() && defaultNode != null) {
+ arrayNode.set(i, defaultNode);
+ n = defaultNode;
+ }
+ doWalk(executionContext, validationMessages, i, n, rootNode, instanceLocation, shouldValidateSchema);
+ i++;
+ }
+ } else {
+ doWalk(executionContext, validationMessages, 0, node, rootNode, instanceLocation, shouldValidateSchema);
+ }
+ return validationMessages;
+ }
+
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
+ private void doWalk(ExecutionContext executionContext, HashSet<ValidationMessage> validationMessages, int i, JsonNode node,
+ JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (this.schema != null) {
+ // Walk the schema.
+ walkSchema(executionContext, this.schema, node, rootNode, instanceLocation.append(i), shouldValidateSchema, validationMessages);
+ }
+
+ if (this.tupleSchema != null) {
+ if (i < this.tupleSchema.size()) {
+ // walk tuple schema
+ walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i),
+ shouldValidateSchema, validationMessages);
+ } else {
+ if (this.additionalSchema != null) {
+ // walk additional item schema
+ walkSchema(executionContext, this.additionalSchema, node, rootNode, instanceLocation.append(i),
+ shouldValidateSchema, validationMessages);
+ }
+ }
+ }
+ }
+
+ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
+ boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(),
+ node, rootNode, instanceLocation, walkSchema, this);
+ if (executeWalk) {
+ validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
+ }
+ this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode,
+ instanceLocation, walkSchema, this, validationMessages);
+
+ }
+
+ public List<JsonSchema> getTupleSchema() {
+ return this.tupleSchema;
+ }
+
+ public JsonSchema getSchema() {
+ return this.schema;
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedItemsValidator();
+ }
+
+ private boolean hasUnevaluatedItemsValidator() {
+ if (this.hasUnevaluatedItemsValidator == null) {
+ this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems");
+ }
+ return hasUnevaluatedItemsValidator;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ if (null != this.schema) {
+ this.schema.initializeValidators();
+ }
+ preloadJsonSchemas(this.tupleSchema);
+ if (null != this.additionalSchema) {
+ this.additionalSchema.initializeValidators();
+ }
+ collectAnnotations(); // cache the flag
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java
new file mode 100644
index 0000000..a9cd02a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ItemsValidator202012.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for items from V2012-12.
+ */
+public class ItemsValidator202012 extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ItemsValidator202012.class);
+
+ private final JsonSchema schema;
+ private final int prefixCount;
+ private final boolean additionalItems;
+
+ private Boolean hasUnevaluatedItemsValidator = null;
+
+ public ItemsValidator202012(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS_202012,
+ validationContext);
+
+ 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 = validationContext.newSchema(schemaLocation, evaluationPath, 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ // ignores non-arrays
+ if (node.isArray()) {
+ SetView<ValidationMessage> errors = null;
+ boolean evaluated = false;
+ for (int i = this.prefixCount; i < node.size(); ++i) {
+ JsonNodePath path = instanceLocation.append(i);
+ // validate with item schema (the whole array has the same item schema)
+ Set<ValidationMessage> results = null;
+ if (additionalItems) {
+ results = 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;
+ results = Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(x).build());
+ }
+ if (results.isEmpty()) {
+// evaluatedItems.add(path);
+ } else {
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ errors.union(results);
+ }
+ evaluated = true;
+ }
+ if (evaluated) {
+ if (collectAnnotations() || collectAnnotations(executionContext)) {
+ // Applies to all
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(true).build());
+ }
+ }
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : errors;
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ Set<ValidationMessage> validationMessages = new LinkedHashSet<>();
+
+ if (node instanceof ArrayNode) {
+ ArrayNode arrayNode = (ArrayNode) node;
+ JsonNode defaultNode = null;
+ if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
+ && this.schema != null) {
+ defaultNode = getDefaultNode(this.schema);
+ }
+ 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,
+ validationMessages);
+ }
+ } else {
+ walkSchema(executionContext, this.schema, node, rootNode, instanceLocation, shouldValidateSchema,
+ validationMessages);
+ }
+
+ return validationMessages;
+ }
+
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
+ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
+ //@formatter:off
+ boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(
+ executionContext,
+ ValidatorTypeCode.ITEMS.getValue(),
+ node,
+ rootNode,
+ instanceLocation,
+ walkSchema, this
+ );
+ if (executeWalk) {
+ validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
+ }
+ this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(
+ executionContext,
+ ValidatorTypeCode.ITEMS.getValue(),
+ node,
+ rootNode,
+ instanceLocation,
+ walkSchema,
+ this, validationMessages
+ );
+ //@formatter:on
+ }
+
+ public JsonSchema getSchema() {
+ return this.schema;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ this.schema.initializeValidators();
+ collectAnnotations(); // cache the flag
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedItemsValidator();
+ }
+
+ private boolean hasUnevaluatedItemsValidator() {
+ if (this.hasUnevaluatedItemsValidator == null) {
+ this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems");
+ }
+ return hasUnevaluatedItemsValidator;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java
new file mode 100644
index 0000000..d5a1b82
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java
@@ -0,0 +1,533 @@
+/*
+ * 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.SpecVersion.VersionFlag;
+import com.networknt.schema.utils.StringUtils;
+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;
+
+/**
+ * Represents a meta-schema which is uniquely identified by its IRI.
+ */
+public class JsonMetaSchema {
+ private static final Logger logger = LoggerFactory.getLogger(JsonMetaSchema.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<String, Format> formats);
+ }
+
+ /**
+ * Builder for {@link JsonMetaSchema}.
+ */
+ public static class Builder {
+ private String iri;
+ private String idKeyword = "$id";
+ private VersionFlag specification = null;
+ private Map<String, Keyword> keywords = new HashMap<>();
+ private Map<String, Format> formats = new HashMap<>();
+ private Map<String, Boolean> vocabularies = new HashMap<>();
+ private FormatKeywordFactory formatKeywordFactory = null;
+ private VocabularyFactory vocabularyFactory = null;
+ private KeywordFactory unknownKeywordFactory = null;
+
+ public Builder(String iri) {
+ this.iri = iri;
+ }
+
+ private Map<String, Keyword> createKeywordsMap(Map<String, Keyword> kwords, Map<String, Format> formats) {
+ boolean formatKeywordPresent = false;
+ Map<String, Keyword> map = new HashMap<>();
+ for (Map.Entry<String, Keyword> type : kwords.entrySet()) {
+ String keywordName = type.getKey();
+ Keyword keyword = type.getValue();
+ if (ValidatorTypeCode.FORMAT.getValue().equals(keywordName)) {
+ if (!(keyword instanceof FormatKeyword) && !ValidatorTypeCode.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 factory for handling custom vocabularies.
+ *
+ * @param vocabularyFactory the factory
+ * @return the builder
+ */
+ public Builder vocabularyFactory(VocabularyFactory vocabularyFactory) {
+ this.vocabularyFactory = vocabularyFactory;
+ 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<Map<String, Format>> customizer) {
+ customizer.accept(this.formats);
+ return this;
+ }
+
+ /**
+ * Customize the keywords.
+ *
+ * @param customizer the customizer
+ * @return the builder
+ */
+ public Builder keywords(Consumer<Map<String, Keyword>> 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<? extends Keyword> 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<? extends Format> formats) {
+ for (Format format : formats) {
+ format(format);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a required vocabulary.
+ * <p>
+ * 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<String, Boolean> vocabularies) {
+ this.vocabularies.putAll(vocabularies);
+ return this;
+ }
+
+ /**
+ * Customize the vocabularies.
+ *
+ * @param customizer the customizer
+ * @return the builder
+ */
+ public Builder vocabularies(Consumer<Map<String, Boolean>> customizer) {
+ customizer.accept(this.vocabularies);
+ return this;
+ }
+
+ /**
+ * Sets the specification.
+ *
+ * @param specification the specification
+ * @return the builder
+ */
+ public Builder specification(VersionFlag specification) {
+ this.specification = 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 JsonMetaSchema build() {
+ // create builtin keywords with (custom) formats.
+ Map<String, Keyword> keywords = this.keywords;
+ if (this.specification != null) {
+ if (this.specification.getVersionFlagValue() >= SpecVersion.VersionFlag.V201909.getVersionFlagValue()) {
+ keywords = new HashMap<>(this.keywords);
+ for(Entry<String, Boolean> entry : this.vocabularies.entrySet()) {
+ Vocabulary vocabulary = null;
+ String id = entry.getKey();
+ if (this.vocabularyFactory != null) {
+ vocabulary = this.vocabularyFactory.getVocabulary(id);
+ }
+ if (vocabulary == null) {
+ vocabulary = Vocabularies.getVocabulary(id);
+ }
+ if (vocabulary != null) {
+ for (Keyword keyword : vocabulary.getKeywords()) {
+ keywords.put(keyword.getValue(), keyword);
+ }
+ } else if (Boolean.TRUE.equals(entry.getValue())) {
+ ValidationMessage validationMessage = ValidationMessage.builder()
+ .message("Meta-schema ''{1}'' has unknown required vocabulary ''{2}''")
+ .arguments(this.iri, id).build();
+ throw new InvalidSchemaException(validationMessage);
+ }
+ }
+ }
+ }
+ Map<String, Keyword> result = createKeywordsMap(keywords, this.formats);
+ return new JsonMetaSchema(this.iri, this.idKeyword, result, this.vocabularies, this.specification, this);
+ }
+
+ @Deprecated
+ public Builder addKeyword(Keyword keyword) {
+ return keyword(keyword);
+ }
+
+ @Deprecated
+ public Builder addKeywords(Collection<? extends Keyword> keywords) {
+ return keywords(keywords);
+ }
+
+ @Deprecated
+ public Builder addFormat(Format format) {
+ return format(format);
+ }
+
+ @Deprecated
+ public Builder addFormats(Collection<? extends Format> formats) {
+ return formats(formats);
+ }
+ }
+
+ private final String iri;
+ private final String idKeyword;
+ private final Map<String, Keyword> keywords;
+ private final Map<String, Boolean> vocabularies;
+ private final VersionFlag specification;
+
+ private final Builder builder;
+
+ JsonMetaSchema(String iri, String idKeyword, Map<String, Keyword> keywords, Map<String, Boolean> vocabularies, VersionFlag specification, Builder builder) {
+ if (StringUtils.isBlank(iri)) {
+ throw new IllegalArgumentException("iri 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.iri = iri;
+ this.idKeyword = idKeyword;
+ this.keywords = keywords;
+ this.specification = specification;
+ this.vocabularies = vocabularies;
+ this.builder = builder;
+ }
+
+ public static JsonMetaSchema getV4() {
+ return new Version4().getInstance();
+ }
+
+ public static JsonMetaSchema getV6() {
+ return new Version6().getInstance();
+ }
+
+ public static JsonMetaSchema getV7() {
+ return new Version7().getInstance();
+ }
+
+ public static JsonMetaSchema getV201909() {
+ return new Version201909().getInstance();
+ }
+
+ public static JsonMetaSchema getV202012() {
+ return new Version202012().getInstance();
+ }
+
+ /**
+ * Create a builder without keywords or formats.
+ * <p>
+ * Use {@link #getV4()} for the Draft 4 Metaschema, or if you need a builder based on Draft4, use
+ *
+ * <code>
+ * JsonMetaSchema.builder("http://your-metaschema-iri", JsonSchemaFactory.getDraftV4()).build();
+ * </code>
+ *
+ * @param iri the IRI 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 iri) {
+ return new Builder(iri);
+ }
+
+ /**
+ * Create a builder.
+ *
+ * @param iri the IRI 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 iri, JsonMetaSchema blueprint) {
+ Builder builder = builder(blueprint);
+ builder.iri = iri;
+ return builder;
+ }
+
+ /**
+ * Create a builder.
+ *
+ * @param blueprint the JsonMetaSchema to base your custom JsonMetaSchema on.
+ * @return a builder instance preconfigured to be the same as blueprint
+ */
+ public static Builder builder(JsonMetaSchema blueprint) {
+ Map<String, Boolean> vocabularies = new HashMap<>(blueprint.getVocabularies());
+ return builder(blueprint.getIri())
+ .idKeyword(blueprint.idKeyword)
+ .keywords(blueprint.builder.keywords.values())
+ .formats(blueprint.builder.formats.values())
+ .specification(blueprint.getSpecification())
+ .vocabularies(vocabularies)
+ .vocabularyFactory(blueprint.builder.vocabularyFactory)
+ .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 idNode = node.get(field);
+ if (idNode == null || !idNode.isTextual()) {
+ return null;
+ }
+ return idNode.textValue();
+ }
+
+ public String getIri() {
+ return this.iri;
+ }
+
+ public Map<String, Keyword> getKeywords() {
+ return this.keywords;
+ }
+
+ public Map<String, Boolean> getVocabularies() {
+ return this.vocabularies;
+ }
+
+ public VersionFlag getSpecification() {
+ return this.specification;
+ }
+
+ /**
+ * Creates a new validator of the keyword.
+ *
+ * @param validationContext the validation context
+ * @param schemaLocation the schema location
+ * @param evaluationPath the evaluation path
+ * @param keyword the keyword
+ * @param schemaNode the schema node
+ * @param parentSchema the parent schema
+ * @return the validator
+ */
+ public JsonValidator newValidator(ValidationContext validationContext, SchemaLocation schemaLocation,
+ JsonNodePath evaluationPath, String keyword, JsonNode schemaNode, JsonSchema parentSchema) {
+ try {
+ Keyword kw = this.keywords.get(keyword);
+ if (kw == null) {
+ if ("message".equals(keyword) && validationContext.getConfig().isCustomMessageSupported()) {
+ return null;
+ }
+ if (ValidatorTypeCode.DISCRIMINATOR.getValue().equals(keyword)
+ && validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ return ValidatorTypeCode.DISCRIMINATOR.newValidator(schemaLocation, evaluationPath, schemaNode,
+ parentSchema, validationContext);
+ }
+ kw = this.builder.unknownKeywordFactory != null
+ ? this.builder.unknownKeywordFactory.getKeyword(keyword, validationContext)
+ : UnknownKeywordFactory.getInstance().getKeyword(keyword, validationContext);
+ if (kw == null) {
+ return null;
+ }
+ }
+ return kw.newValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext);
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof JsonSchemaException) {
+ logger.error("Error:", e);
+ throw (JsonSchemaException) e.getTargetException();
+ }
+ 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);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.iri;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(iri);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ JsonMetaSchema other = (JsonMetaSchema) obj;
+ return Objects.equals(iri, other.iri);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchemaFactory.java b/src/main/java/com/networknt/schema/JsonMetaSchemaFactory.java
new file mode 100644
index 0000000..31d7f1a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonMetaSchemaFactory.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;
+
+/**
+ * Factory for {@link JsonMetaSchema}.
+ */
+@FunctionalInterface
+public interface JsonMetaSchemaFactory {
+ /**
+ * Gets the meta-schema given the IRI.
+ *
+ * @param iri the meta-schema IRI
+ * @param schemaFactory the schema factory
+ * @param config the config
+ * @return the meta-schema
+ */
+ JsonMetaSchema getMetaSchema(String iri, JsonSchemaFactory schemaFactory, SchemaValidatorsConfig config);
+}
diff --git a/src/main/java/com/networknt/schema/JsonNodePath.java b/src/main/java/com/networknt/schema/JsonNodePath.java
new file mode 100644
index 0000000..5500185
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonNodePath.java
@@ -0,0 +1,258 @@
+/*
+ * 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;
+
+/**
+ * Represents a path to a JSON node.
+ */
+public class JsonNodePath implements Comparable<JsonNodePath> {
+ private final PathType type;
+ private final JsonNodePath parent;
+
+ private final String pathSegment;
+ private final int pathSegmentIndex;
+
+ private volatile String value = null; // computed lazily
+
+ public JsonNodePath(PathType type) {
+ this.type = type;
+ this.parent = null;
+ this.pathSegment = null;
+ this.pathSegmentIndex = -1;
+ }
+
+ private JsonNodePath(JsonNodePath parent, String pathSegment) {
+ this.parent = parent;
+ this.type = parent.type;
+ this.pathSegment = pathSegment;
+ this.pathSegmentIndex = -1;
+ }
+
+ private JsonNodePath(JsonNodePath 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 JsonNodePath getParent() {
+ return this.parent;
+ }
+
+ /**
+ * Append the child token to the path.
+ *
+ * @param token the child token
+ * @return the path
+ */
+ public JsonNodePath append(String token) {
+ return new JsonNodePath(this, token);
+ }
+
+ /**
+ * Append the index to the path.
+ *
+ * @param index the index
+ * @return the path
+ */
+ public JsonNodePath append(int index) {
+ return new JsonNodePath(this, index);
+ }
+
+ /**
+ * Gets the {@link PathType}.
+ *
+ * @return the path type
+ */
+ public PathType getPathType() {
+ return this.type;
+ }
+
+ /**
+ * Gets the name element given an index.
+ * <p>
+ * 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.
+ * <p>
+ * 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 Integer.valueOf(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("");
+ }
+ JsonNodePath 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(JsonNodePath other) {
+ int count = getNameCount();
+ int otherCount = other.getNameCount();
+
+ if (otherCount > count) {
+ return false;
+ } else if (otherCount == count) {
+ return this.equals(other);
+ } else {
+ JsonNodePath 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.
+ * <p>
+ * 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;
+ }
+ JsonNodePath 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) {
+ String parentValue = this.parent == null ? type.getRoot() : this.parent.toString();
+ if (pathSegmentIndex != -1) {
+ this.value = this.type.append(parentValue, pathSegmentIndex);
+ } else if (pathSegment != null) {
+ this.value = this.type.append(parentValue, pathSegment);
+ } else {
+ this.value = parentValue;
+ }
+ }
+ return this.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(parent, pathSegment, pathSegmentIndex, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ JsonNodePath other = (JsonNodePath) obj;
+ return Objects.equals(parent, other.parent) && Objects.equals(pathSegment, other.pathSegment)
+ && pathSegmentIndex == other.pathSegmentIndex && type == other.type;
+ }
+
+ @Override
+ public int compareTo(JsonNodePath 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/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
new file mode 100644
index 0000000..74ed724
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -0,0 +1,1136 @@
+/*
+ * 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.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.serialization.YamlMapperFactory;
+import com.networknt.schema.utils.JsonNodes;
+import com.networknt.schema.utils.SetView;
+
+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.LinkedHashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Used for creating a schema with validators for validating inputs. This is
+ * created using {@link JsonSchemaFactory#getInstance(VersionFlag, Consumer)}
+ * and should be cached for performance.
+ * <p>
+ * 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 long V201909_VALUE = VersionFlag.V201909.getVersionFlagValue();
+
+ /**
+ * The validators sorted and indexed by evaluation path.
+ */
+ private List<JsonValidator> validators;
+ private boolean validatorsLoaded = false;
+ private boolean recursiveAnchor = false;
+
+ private JsonValidator requiredValidator = null;
+ private TypeValidator typeValidator;
+
+ private final String id;
+
+ static JsonSchema from(ValidationContext validationContext, SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parent, boolean suppressSubSchemaRetrieval) {
+ return new JsonSchema(validationContext, schemaLocation, evaluationPath, schemaNode, parent, suppressSubSchemaRetrieval);
+ }
+
+ private boolean hasNoFragment(SchemaLocation schemaLocation) {
+ return this.schemaLocation.getFragment() == null || this.schemaLocation.getFragment().getNameCount() == 0;
+ }
+
+ private static SchemaLocation resolve(SchemaLocation schemaLocation, JsonNode schemaNode, boolean rootSchema,
+ ValidationContext validationContext) {
+ String id = validationContext.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;
+ JsonSchemaIdValidator validator = validationContext.getConfig().getSchemaIdValidator();
+ if (validator != null) {
+ if (!validator.validate(id, rootSchema, schemaLocation, result, validationContext)) {
+ SchemaLocation idSchemaLocation = schemaLocation.append(validationContext.getMetaSchema().getIdKeyword());
+ ValidationMessage validationMessage = ValidationMessage.builder()
+ .code(ValidatorTypeCode.ID.getValue()).type(ValidatorTypeCode.ID.getValue())
+ .instanceLocation(idSchemaLocation.getFragment())
+ .arguments(id, validationContext.getMetaSchema().getIdKeyword(), idSchemaLocation)
+ .schemaLocation(idSchemaLocation)
+ .schemaNode(schemaNode)
+ .messageFormatter(args -> validationContext.getConfig().getMessageSource().getMessage(
+ ValidatorTypeCode.ID.getValue(), validationContext.getConfig().getLocale(), args))
+ .build();
+ throw new InvalidSchemaException(validationMessage);
+ }
+ }
+ return result;
+ } else {
+ return schemaLocation;
+ }
+ }
+
+ private JsonSchema(ValidationContext validationContext, SchemaLocation schemaLocation, JsonNodePath evaluationPath,
+ JsonNode schemaNode, JsonSchema parent, boolean suppressSubSchemaRetrieval) {
+ super(resolve(schemaLocation, schemaNode, parent == null, validationContext), evaluationPath, schemaNode, parent,
+ null, null, validationContext, suppressSubSchemaRetrieval);
+ String id = this.validationContext.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.contains("#") ? 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.validationContext.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.validationContext.getSchemaResources()
+ .putIfAbsent(schemaLocation != null ? schemaLocation.toString() : this.id, this);
+ } else {
+ this.id = null;
+ }
+ }
+ String anchor = this.validationContext.getMetaSchema().readAnchor(this.schemaNode);
+ if (anchor != null) {
+ String absoluteIri = this.schemaLocation.getAbsoluteIri() != null
+ ? this.schemaLocation.getAbsoluteIri().toString()
+ : "";
+ this.validationContext.getSchemaResources()
+ .putIfAbsent(absoluteIri + "#" + anchor, this);
+ }
+ String dynamicAnchor = this.validationContext.getMetaSchema().readDynamicAnchor(schemaNode);
+ if (dynamicAnchor != null) {
+ String absoluteIri = this.schemaLocation.getAbsoluteIri() != null
+ ? this.schemaLocation.getAbsoluteIri().toString()
+ : "";
+ this.validationContext.getDynamicAnchors()
+ .putIfAbsent(absoluteIri + "#" + dynamicAnchor, this);
+ }
+ getValidators();
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param copy to copy from
+ */
+ protected JsonSchema(JsonSchema copy) {
+ super(copy);
+ this.validators = copy.validators;
+ this.validatorsLoaded = copy.validatorsLoaded;
+ this.recursiveAnchor = copy.recursiveAnchor;
+ this.requiredValidator = copy.requiredValidator;
+ this.typeValidator = copy.typeValidator;
+ this.id = copy.id;
+ }
+
+ /**
+ * Creates a schema using the current one as a template with the parent as the
+ * ref.
+ * <p>
+ * This is typically used if this schema is a schema resource that can be
+ * pointed to by various references.
+ *
+ * @param refEvaluationParentSchema the parent ref
+ * @param refEvaluationPath the ref evaluation path
+ * @return the schema
+ */
+ public JsonSchema fromRef(JsonSchema refEvaluationParentSchema, JsonNodePath refEvaluationPath) {
+ JsonSchema copy = new JsonSchema(this);
+ copy.validationContext = new ValidationContext(copy.getValidationContext().getMetaSchema(),
+ copy.getValidationContext().getJsonSchemaFactory(),
+ refEvaluationParentSchema.validationContext.getConfig(),
+ refEvaluationParentSchema.getValidationContext().getSchemaReferences(),
+ refEvaluationParentSchema.getValidationContext().getSchemaResources(),
+ refEvaluationParentSchema.getValidationContext().getDynamicAnchors());
+ copy.evaluationPath = refEvaluationPath;
+ copy.evaluationParentSchema = refEvaluationParentSchema;
+ // Validator state is reset due to the changes in evaluation path
+ copy.validatorsLoaded = false;
+ copy.requiredValidator = null;
+ copy.typeValidator = null;
+ copy.validators = null;
+ return copy;
+ }
+
+ public JsonSchema withConfig(SchemaValidatorsConfig config) {
+ if (!this.getValidationContext().getConfig().equals(config)) {
+ JsonSchema copy = new JsonSchema(this);
+ copy.validationContext = new ValidationContext(copy.getValidationContext().getMetaSchema(),
+ copy.getValidationContext().getJsonSchemaFactory(), config,
+ copy.getValidationContext().getSchemaReferences(),
+ copy.getValidationContext().getSchemaResources(),
+ copy.getValidationContext().getDynamicAnchors());
+ copy.validatorsLoaded = false;
+ copy.requiredValidator = null;
+ copy.typeValidator = null;
+ copy.validators = null;
+ return copy;
+ }
+ return this;
+ }
+
+ public ValidationContext getValidationContext() {
+ return this.validationContext;
+ }
+
+ /**
+ * Find the schema node for $ref attribute.
+ *
+ * @param ref String
+ * @return JsonNode
+ */
+ public JsonNode getRefSchemaNode(String ref) {
+ JsonSchema 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 JsonSchema getRefSchema(JsonNodePath 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.toString();
+ JsonSchema result = this.validationContext.getSchemaResources().get(anchor);
+ if (result == null) {
+ result = this.validationContext.getDynamicAnchors().get(anchor);
+ }
+ if (result == null) {
+ throw new JsonSchemaException("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 JsonSchema getSubSchema(JsonNodePath fragment) {
+ JsonSchema document = findSchemaResourceRoot();
+ JsonSchema parent = document;
+ JsonSchema subSchema = null;
+ JsonNode parentNode = parent.getSchemaNode();
+ SchemaLocation schemaLocation = document.getSchemaLocation();
+ JsonNodePath evaluationPath = document.getEvaluationPath();
+ 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) {
+ int index = ((Number) segment).intValue();
+ schemaLocation = schemaLocation.append(index);
+ evaluationPath = evaluationPath.append(index);
+ } else {
+ schemaLocation = schemaLocation.append(segment.toString());
+ evaluationPath = evaluationPath.append(segment.toString());
+ }
+ /*
+ * The parent validation context is used to create as there can be changes in
+ * $schema is later drafts which means the validation context can change.
+ */
+ // This may need a redesign see Issue 939 and 940
+ String id = parent.getValidationContext().resolveSchemaId(subSchemaNode);
+// if (!("definitions".equals(segment.toString()) || "$defs".equals(segment.toString())
+// )) {
+ if (id != null || x == nameCount - 1) {
+ subSchema = parent.getValidationContext().newSchema(schemaLocation, evaluationPath, 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
+ */
+ JsonSchema found = document.findSchemaResourceRoot().fetchSubSchemaNode(this.validationContext);
+ if (found != null) {
+ found = found.getSubSchema(fragment);
+ }
+ if (found == null) {
+ ValidationMessage validationMessage = ValidationMessage.builder()
+ .type(ValidatorTypeCode.REF.getValue()).code("internal.unresolvedRef")
+ .message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(schemaLocation.getFragment())
+ .schemaLocation(schemaLocation)
+ .evaluationPath(evaluationPath)
+ .arguments(fragment).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ 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 JsonSchema findLexicalRoot() {
+ JsonSchema ancestor = this;
+ while (ancestor.getId() == null) {
+ if (null == ancestor.getParentSchema()) break;
+ ancestor = ancestor.getParentSchema();
+ }
+ return ancestor;
+ }
+
+ /**
+ * Finds the root of the schema resource.
+ * <p>
+ * This is either the schema document root or the subschema resource root.
+ *
+ * @return the root of the schema
+ */
+ public JsonSchema findSchemaResourceRoot() {
+ JsonSchema ancestor = this;
+ while (!ancestor.isSchemaResourceRoot()) {
+ ancestor = ancestor.getParentSchema();
+ }
+ return ancestor;
+ }
+
+ /**
+ * Determines if this schema resource is a schema resource root.
+ * <p>
+ * 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
+ if (!Objects.equals(getSchemaLocation().getAbsoluteIri(),
+ getParentSchema().getSchemaLocation().getAbsoluteIri())) {
+ return true;
+ }
+ return false;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ 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(this.validationContext);
+ if (subSchema != null) {
+ return subSchema.getRefSchemaNode(ref);
+ }
+ return null;
+ }
+
+ /**
+ * Please note that the key in {@link #validators} map is the evaluation path.
+ */
+ private List<JsonValidator> read(JsonNode schemaNode) {
+ List<JsonValidator> validators = new ArrayList<>();
+ if (schemaNode.isBoolean()) {
+ if (schemaNode.booleanValue()) {
+ JsonNodePath path = getEvaluationPath().append("true");
+ JsonValidator validator = this.validationContext.newValidator(getSchemaLocation().append("true"), path,
+ "true", schemaNode, this);
+ validators.add(validator);
+ } else {
+ JsonNodePath path = getEvaluationPath().append("false");
+ JsonValidator validator = this.validationContext.newValidator(getSchemaLocation().append("false"),
+ path, "false", schemaNode, this);
+ validators.add(validator);
+ }
+ } else {
+ JsonValidator refValidator = null;
+
+ Iterator<Entry<String, JsonNode>> iterator = schemaNode.fields();
+ while (iterator.hasNext()) {
+ Entry<String, JsonNode> entry = iterator.next();
+ String pname = entry.getKey();
+ JsonNode nodeToUse = entry.getValue();
+
+ JsonNodePath path = getEvaluationPath().append(pname);
+ SchemaLocation schemaPath = getSchemaLocation().append(pname);
+
+ if ("$recursiveAnchor".equals(pname)) {
+ if (!nodeToUse.isBoolean()) {
+ ValidationMessage validationMessage = ValidationMessage.builder().type("$recursiveAnchor")
+ .code("internal.invalidRecursiveAnchor")
+ .message(
+ "{0}: The value of a $recursiveAnchor must be a Boolean literal but is {1}")
+ .instanceLocation(path)
+ .evaluationPath(path)
+ .schemaLocation(schemaPath)
+ .arguments(nodeToUse.getNodeType().toString())
+ .build();
+ throw new JsonSchemaException(validationMessage);
+ }
+ this.recursiveAnchor = nodeToUse.booleanValue();
+ }
+
+ JsonValidator validator = this.validationContext.newValidator(schemaPath, path,
+ pname, nodeToUse, this);
+ if (validator != null) {
+ validators.add(validator);
+
+ if ("$ref".equals(pname)) {
+ refValidator = validator;
+ } else if ("required".equals(pname)) {
+ this.requiredValidator = validator;
+ } else if ("type".equals(pname)) {
+ this.typeValidator = (TypeValidator) validator;
+ }
+ }
+
+ }
+
+ // Ignore siblings for older drafts
+ if (null != refValidator && activeDialect() < V201909_VALUE) {
+ validators.clear();
+ validators.add(refValidator);
+ }
+ }
+ if (validators.size() > 1) {
+ Collections.sort(validators, VALIDATOR_SORT);
+ }
+ return validators;
+ }
+
+ private long activeDialect() {
+ return this.validationContext
+ .activeDialect()
+ .map(VersionFlag::getVersionFlagValue)
+ .orElse(Long.MAX_VALUE);
+ }
+
+ /**
+ * A comparator that sorts validators, such that 'properties' comes before 'required',
+ * so that we can apply default values before validating required.
+ */
+ private static Comparator<JsonValidator> VALIDATOR_SORT = (lhs, rhs) -> {
+ String lhsName = lhs.getEvaluationPath().getName(-1);
+ String rhsName = rhs.getEvaluationPath().getName(-1);
+
+ if (lhsName.equals(rhsName)) return 0;
+
+ 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator");
+ if (null != discriminator && null != executionContext.getCurrentDiscriminatorContext()) {
+ executionContext.getCurrentDiscriminatorContext().registerDiscriminator(schemaLocation,
+ discriminator);
+ }
+ }
+
+ SetView<ValidationMessage> errors = null;
+ // Set the walkEnabled and isValidationEnabled flag in internal validator state.
+ setValidatorState(executionContext, false, true);
+
+ for (JsonValidator v : getValidators()) {
+ Set<ValidationMessage> results = null;
+
+ try {
+ results = v.validate(executionContext, jsonNode, rootNode, instanceLocation);
+ } finally {
+ if (results != null && !results.isEmpty()) {
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ errors.union(results);
+ }
+ }
+ }
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ ObjectNode discriminator = (ObjectNode) this.schemaNode.get("discriminator");
+ if (null != discriminator) {
+ final DiscriminatorContext discriminatorContext = executionContext
+ .getCurrentDiscriminatorContext();
+ if (null != discriminatorContext) {
+ final ObjectNode discriminatorToUse;
+ final ObjectNode discriminatorFromContext = discriminatorContext
+ .getDiscriminatorForPath(this.schemaLocation);
+ 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(this.schemaLocation, 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);
+ }
+ }
+ }
+
+ if (errors != null && !errors.isEmpty()) {
+ // Failed with assertion set result and drop all annotations from this schema
+ // and all subschemas
+ executionContext.getResults().setResult(instanceLocation, getSchemaLocation(), getEvaluationPath(), false);
+ }
+ return errors == null ? Collections.emptySet() : errors;
+ }
+
+ /**
+ * Validate the given root JsonNode, starting at the root of the data path.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param rootNode the root node
+ * @return A list of ValidationMessage if there is any validation error, or an
+ * empty list if there is no error.
+ */
+ public Set<ValidationMessage> validate(JsonNode rootNode) {
+ return validate(rootNode, OutputFormat.DEFAULT);
+ }
+
+ /**
+ * Validate the given root JsonNode, starting at the root of the data path.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param rootNode the root node
+ * @param executionCustomizer the execution customizer
+ * @return the assertions
+ */
+ public Set<ValidationMessage> validate(JsonNode rootNode, ExecutionContextCustomizer executionCustomizer) {
+ return validate(rootNode, OutputFormat.DEFAULT, executionCustomizer);
+ }
+
+ /**
+ * Validate the given root JsonNode, starting at the root of the data path.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param rootNode the root node
+ * @param executionCustomizer the execution customizer
+ * @return the assertions
+ */
+ public Set<ValidationMessage> validate(JsonNode rootNode, Consumer<ExecutionContext> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> the result type
+ * @param rootNode the root node
+ * @param format the formatter
+ * @return the result
+ */
+ public <T> T validate(JsonNode rootNode, OutputFormat<T> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> the result type
+ * @param rootNode the root node
+ * @param format the formatter
+ * @param executionCustomizer the execution customizer
+ * @return the result
+ */
+ public <T> T validate(JsonNode rootNode, OutputFormat<T> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> the result type
+ * @param rootNode the root node
+ * @param format the formatter
+ * @param executionCustomizer the execution customizer
+ * @return the result
+ */
+ public <T> T validate(JsonNode rootNode, OutputFormat<T> format, Consumer<ExecutionContext> executionCustomizer) {
+ return validate(createExecutionContext(), rootNode, format, (executionContext, validationContext) -> {
+ executionCustomizer.accept(executionContext);
+ });
+ }
+
+ /**
+ * Validate the given input string using the input format, starting at the root
+ * of the data path.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param input the input
+ * @param inputFormat the inputFormat
+ * @return A list of ValidationMessage if there is any validation error, or an
+ * empty list if there is no error.
+ */
+ public Set<ValidationMessage> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param input the input
+ * @param inputFormat the inputFormat
+ * @param executionCustomizer the execution customizer
+ * @return the assertions
+ */
+ public Set<ValidationMessage> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param input the input
+ * @param inputFormat the inputFormat
+ * @param executionCustomizer the execution customizer
+ * @return the assertions
+ */
+ public Set<ValidationMessage> validate(String input, InputFormat inputFormat, Consumer<ExecutionContext> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> the result type
+ * @param input the input
+ * @param inputFormat the inputFormat
+ * @param format the formatter
+ * @return the result
+ */
+ public <T> T validate(String input, InputFormat inputFormat, OutputFormat<T> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> 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> T validate(String input, InputFormat inputFormat, OutputFormat<T> 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.
+ * <p>
+ * Note that since Draft 2019-09 by default format generates only annotations
+ * and not assertions.
+ * <p>
+ * Use {@link ExecutionConfig#setFormatAssertionsEnabled(Boolean)} to override
+ * the default.
+ *
+ * @param <T> 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> T validate(String input, InputFormat inputFormat, OutputFormat<T> format, Consumer<ExecutionContext> executionCustomizer) {
+ return validate(createExecutionContext(), deserialize(input, inputFormat), format, (executionContext, validationContext) -> {
+ executionCustomizer.accept(executionContext);
+ });
+ }
+
+ /**
+ * Validates to a format.
+ *
+ * @param <T> the result type
+ * @param executionContext the execution context
+ * @param node the node
+ * @param format the format
+ * @return the result
+ */
+ public <T> T validate(ExecutionContext executionContext, JsonNode node, OutputFormat<T> format) {
+ return validate(executionContext, node, format, null);
+ }
+
+ /**
+ * Validates to a format.
+ *
+ * @param <T> 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> T validate(ExecutionContext executionContext, JsonNode node, OutputFormat<T> format,
+ ExecutionContextCustomizer executionCustomizer) {
+ format.customize(executionContext, this.validationContext);
+ if (executionCustomizer != null) {
+ executionCustomizer.customize(executionContext, this.validationContext);
+ }
+ Set<ValidationMessage> validationMessages = null;
+ try {
+ validationMessages = validate(executionContext, node);
+ } catch (FailFastAssertionException e) {
+ validationMessages = e.getValidationMessages();
+ }
+ return format.format(this, validationMessages, executionContext, this.validationContext);
+ }
+
+ /**
+ * Deserialize string to JsonNode.
+ *
+ * @param input the input
+ * @param inputFormat the format
+ * @return the JsonNode.
+ */
+ private JsonNode deserialize(String input, InputFormat inputFormat) {
+ try {
+ if (InputFormat.JSON.equals(inputFormat)) {
+ return JsonMapperFactory.getInstance().readTree(input);
+ } else if (InputFormat.YAML.equals(inputFormat)) {
+ return YamlMapperFactory.getInstance().readTree(input);
+ }
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Invalid input", e);
+ }
+ throw new IllegalArgumentException("Unsupported input format "+inputFormat);
+ }
+
+ public ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode node) {
+ return validateAndCollect(executionContext, node, node, atRoot());
+ }
+
+ /**
+ * 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 executionContext ExecutionContext
+ * @param jsonNode JsonNode
+ * @param rootNode JsonNode
+ * @param instanceLocation JsonNodePath
+ *
+ * @return ValidationResult
+ */
+ private ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
+ // Set the walkEnabled and isValidationEnabled flag in internal validator state.
+ setValidatorState(executionContext, false, true);
+ // Validate.
+ Set<ValidationMessage> errors = validate(executionContext, jsonNode, rootNode, instanceLocation);
+
+ // Get the config.
+ SchemaValidatorsConfig config = this.validationContext.getConfig();
+
+ // 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()) {
+ // Get the collector context.
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+
+ // Load all the data from collectors into the context.
+ collectorContext.loadCollectors();
+ }
+ // Collect errors and collector context into validation result.
+ return new ValidationResult(errors, executionContext);
+ }
+
+ public ValidationResult validateAndCollect(JsonNode node) {
+ return validateAndCollect(createExecutionContext(), node, node, atRoot());
+ }
+
+ /************************ 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
+ *
+ * @return the validation result
+ */
+ public ValidationResult walk(ExecutionContext executionContext, JsonNode node, boolean validate) {
+ return walkAtNodeInternal(executionContext, node, node, atRoot(), validate);
+ }
+
+ /**
+ * 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 ValidationResult walk(ExecutionContext executionContext, String input, InputFormat inputFormat,
+ boolean validate) {
+ JsonNode node = deserialize(input, inputFormat);
+ return walkAtNodeInternal(executionContext, node, node, atRoot(), validate);
+ }
+
+ /**
+ * Walk the JSON node.
+ *
+ * @param node the input
+ * @param validate true to validate the input against the schema
+ * @return the validation result
+ */
+ public ValidationResult walk(JsonNode node, boolean validate) {
+ return walk(createExecutionContext(), node, validate);
+ }
+
+ /**
+ * 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 ValidationResult walk(String input, InputFormat inputFormat, boolean validate) {
+ return walk(createExecutionContext(), deserialize(input, inputFormat), validate);
+ }
+
+ /**
+ * 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 ValidationResult walkAtNode(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean validate) {
+ return walkAtNodeInternal(executionContext, node, rootNode, instanceLocation, validate);
+ }
+
+ private ValidationResult walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ // Set the walkEnabled flag in internal validator state.
+ setValidatorState(executionContext, true, shouldValidateSchema);
+ // Walk through the schema.
+ Set<ValidationMessage> errors = walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+
+ // Get the config.
+ SchemaValidatorsConfig config = this.validationContext.getConfig();
+ // 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()) {
+ // Get the collector context.
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+
+ // Load all the data from collectors into the context.
+ collectorContext.loadCollectors();
+ }
+ return new ValidationResult(errors, executionContext);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ Set<ValidationMessage> errors = new LinkedHashSet<>();
+ // Walk through all the JSONWalker's.
+ for (JsonValidator validator : getValidators()) {
+ JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath();
+ try {
+ // 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<ValidationMessage> 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.
+ this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext,
+ evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
+ this, validator, errors);
+ }
+ }
+ return errors;
+ }
+
+ /************************ END OF WALK METHODS **********************************/
+
+ private static void setValidatorState(ExecutionContext executionContext, boolean isWalkEnabled,
+ boolean shouldValidateSchema) {
+ // Get the Validator state object storing validation data
+ ValidatorState validatorState = executionContext.getValidatorState();
+ if (validatorState == null) {
+ // If one has not been created, instantiate one
+ executionContext.setValidatorState(new ValidatorState(isWalkEnabled, shouldValidateSchema));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "\"" + getEvaluationPath() + "\" : " + getSchemaNode().toString();
+ }
+
+ public boolean hasRequiredValidator() {
+ return this.requiredValidator != null;
+ }
+
+ public JsonValidator getRequiredValidator() {
+ return this.requiredValidator;
+ }
+
+ public boolean hasTypeValidator() {
+ return getTypeValidator() != null;
+ }
+
+ public TypeValidator getTypeValidator() {
+ // As the validators are lazy loaded the typeValidator is only known if the
+ // validators are not null
+ if (this.validators == null) {
+ getValidators();
+ }
+ return this.typeValidator;
+ }
+
+ public List<JsonValidator> getValidators() {
+ if (this.validators == null) {
+ this.validators = Collections.unmodifiableList(read(getSchemaNode()));
+ }
+ return this.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.
+ * <p>This comes with the issue that this way you cannot rely on validating important schema features, in particular
+ * <code>$ref</code> resolution at instantiation from {@link com.networknt.schema.JsonSchemaFactory}.</p>
+ * <p>By calling <code>initializeValidators</code> you can enforce preloading of the {@link com.networknt.schema.JsonSchema}
+ * instances of the validators.</p>
+ */
+ public void initializeValidators() {
+ if (!this.validatorsLoaded) {
+ for (final JsonValidator validator : getValidators()) {
+ validator.preloadJsonSchema();
+ }
+ /*
+ * This is only set to true after the preload as it may throw an exception for
+ * instance if the remote host is unavailable and we may want to be able to try
+ * again.
+ */
+ this.validatorsLoaded = true;
+ }
+ }
+
+ public boolean isRecursiveAnchor() {
+ return this.recursiveAnchor;
+ }
+
+ /**
+ * Creates an execution context.
+ *
+ * @return the execution context
+ */
+ public ExecutionContext createExecutionContext() {
+ SchemaValidatorsConfig config = validationContext.getConfig();
+ // Copy execution config defaults from validation config
+ ExecutionConfig executionConfig = new ExecutionConfig();
+ executionConfig.setLocale(config.getLocale());
+ executionConfig.setFormatAssertionsEnabled(config.getFormatAssertionsEnabled());
+ executionConfig.setFailFast(config.isFailFast());
+
+ ExecutionContext executionContext = new ExecutionContext(executionConfig);
+ if(config.getExecutionContextCustomizer() != null) {
+ config.getExecutionContextCustomizer().customize(executionContext, validationContext);
+ }
+ return executionContext;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchemaException.java b/src/main/java/com/networknt/schema/JsonSchemaException.java
new file mode 100644
index 0000000..2113d2e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchemaException.java
@@ -0,0 +1,53 @@
+/*
+ * 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) {
+ this.validationMessage = validationMessage;
+ }
+
+ public JsonSchemaException(String message) {
+ super(message);
+ }
+
+ public JsonSchemaException(Throwable throwable) {
+ super(throwable);
+ }
+
+ @Override
+ public String getMessage() {
+ return this.validationMessage != null ? this.validationMessage.getMessage() : super.getMessage();
+ }
+
+ public ValidationMessage getValidationMessage() {
+ return this.validationMessage;
+ }
+
+ public Set<ValidationMessage> 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
new file mode 100644
index 0000000..51d153d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
@@ -0,0 +1,692 @@
+/*
+ * 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.SpecVersion.VersionFlag;
+import com.networknt.schema.resource.DefaultSchemaLoader;
+import com.networknt.schema.resource.SchemaLoader;
+import com.networknt.schema.resource.SchemaLoaders;
+import com.networknt.schema.resource.SchemaMapper;
+import com.networknt.schema.resource.SchemaMappers;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.serialization.YamlMapperFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
+
+/**
+ * Factory for building {@link JsonSchema} instances.
+ * <p>
+ * The factory should be typically be created using
+ * {@link #getInstance(VersionFlag, Consumer)}.
+ */
+public class JsonSchemaFactory {
+ private static final Logger logger = LoggerFactory.getLogger(JsonSchemaFactory.class);
+
+ public static class Builder {
+ private ObjectMapper jsonMapper = null;
+ private ObjectMapper yamlMapper = null;
+ private String defaultMetaSchemaIri;
+ private final ConcurrentMap<String, JsonMetaSchema> metaSchemas = new ConcurrentHashMap<String, JsonMetaSchema>();
+ private SchemaLoaders.Builder schemaLoadersBuilder = null;
+ private SchemaMappers.Builder schemaMappersBuilder = null;
+ private boolean enableSchemaCache = true;
+ private JsonMetaSchemaFactory metaSchemaFactory = null;
+
+ public Builder jsonMapper(final ObjectMapper jsonMapper) {
+ this.jsonMapper = jsonMapper;
+ return this;
+ }
+
+ public Builder yamlMapper(final ObjectMapper yamlMapper) {
+ this.yamlMapper = yamlMapper;
+ return this;
+ }
+
+ public Builder defaultMetaSchemaIri(final String defaultMetaSchemaIri) {
+ this.defaultMetaSchemaIri = defaultMetaSchemaIri;
+ return this;
+ }
+
+ public Builder metaSchemaFactory(final JsonMetaSchemaFactory jsonMetaSchemaFactory) {
+ this.metaSchemaFactory = jsonMetaSchemaFactory;
+ return this;
+ }
+
+ public Builder metaSchema(final JsonMetaSchema jsonMetaSchema) {
+ this.metaSchemas.put(normalizeMetaSchemaUri(jsonMetaSchema.getIri()), jsonMetaSchema);
+ return this;
+ }
+
+ public Builder metaSchemas(final Collection<? extends JsonMetaSchema> jsonMetaSchemas) {
+ for (JsonMetaSchema jsonMetaSchema : jsonMetaSchemas) {
+ metaSchema(jsonMetaSchema);
+ }
+ return this;
+ }
+
+ public Builder metaSchemas(Consumer<Map<String, JsonMetaSchema>> customizer) {
+ customizer.accept(this.metaSchemas);
+ return this;
+ }
+
+ public Builder enableSchemaCache(boolean enableSchemaCache) {
+ this.enableSchemaCache = enableSchemaCache;
+ return this;
+ }
+
+ public Builder schemaLoaders(Consumer<SchemaLoaders.Builder> schemaLoadersBuilderCustomizer) {
+ if (this.schemaLoadersBuilder == null) {
+ this.schemaLoadersBuilder = SchemaLoaders.builder();
+ }
+ schemaLoadersBuilderCustomizer.accept(this.schemaLoadersBuilder);
+ return this;
+ }
+
+ public Builder schemaMappers(Consumer<SchemaMappers.Builder> schemaMappersBuilderCustomizer) {
+ if (this.schemaMappersBuilder == null) {
+ this.schemaMappersBuilder = SchemaMappers.builder();
+ }
+ schemaMappersBuilderCustomizer.accept(this.schemaMappersBuilder);
+ return this;
+ }
+
+ @Deprecated
+ public Builder addMetaSchema(final JsonMetaSchema jsonMetaSchema) {
+ return metaSchema(jsonMetaSchema);
+ }
+
+ @Deprecated
+ public Builder addMetaSchemas(final Collection<? extends JsonMetaSchema> jsonMetaSchemas) {
+ return metaSchemas(jsonMetaSchemas);
+ }
+
+ public JsonSchemaFactory build() {
+ return new JsonSchemaFactory(
+ jsonMapper,
+ yamlMapper,
+ defaultMetaSchemaIri,
+ schemaLoadersBuilder,
+ schemaMappersBuilder,
+ metaSchemas,
+ enableSchemaCache,
+ metaSchemaFactory
+ );
+ }
+ }
+
+ private final ObjectMapper jsonMapper;
+ private final ObjectMapper yamlMapper;
+ private final String defaultMetaSchemaIri;
+ private final SchemaLoaders.Builder schemaLoadersBuilder;
+ private final SchemaMappers.Builder schemaMappersBuilder;
+ private final SchemaLoader schemaLoader;
+ private final ConcurrentMap<String, JsonMetaSchema> metaSchemas;
+ private final ConcurrentMap<SchemaLocation, JsonSchema> schemaCache = new ConcurrentHashMap<>();
+ private final boolean enableSchemaCache;
+ private final JsonMetaSchemaFactory metaSchemaFactory;
+
+ private static final List<SchemaLoader> DEFAULT_SCHEMA_LOADERS = SchemaLoaders.builder().build();
+ private static final List<SchemaMapper> DEFAULT_SCHEMA_MAPPERS = SchemaMappers.builder().build();
+
+ private JsonSchemaFactory(
+ ObjectMapper jsonMapper,
+ ObjectMapper yamlMapper,
+ String defaultMetaSchemaIri,
+ SchemaLoaders.Builder schemaLoadersBuilder,
+ SchemaMappers.Builder schemaMappersBuilder,
+ ConcurrentMap<String, JsonMetaSchema> metaSchemas,
+ boolean enableSchemaCache,
+ JsonMetaSchemaFactory metaSchemaFactory) {
+ this.metaSchemas = metaSchemas;
+ if (defaultMetaSchemaIri == null || defaultMetaSchemaIri.trim().isEmpty()) {
+ throw new IllegalArgumentException("defaultMetaSchemaIri must not be null or empty");
+ } else if (metaSchemas == null || metaSchemas.isEmpty()) {
+ throw new IllegalArgumentException("Json Meta Schemas must not be null or empty");
+ } else if (this.metaSchemas.get(normalizeMetaSchemaUri(defaultMetaSchemaIri)) == null) {
+ throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided");
+ }
+ this.jsonMapper = jsonMapper;
+ this.yamlMapper = yamlMapper;
+ this.defaultMetaSchemaIri = defaultMetaSchemaIri;
+ this.schemaLoadersBuilder = schemaLoadersBuilder;
+ this.schemaMappersBuilder = schemaMappersBuilder;
+ this.schemaLoader = new DefaultSchemaLoader(
+ schemaLoadersBuilder != null ? schemaLoadersBuilder.build() : DEFAULT_SCHEMA_LOADERS,
+ schemaMappersBuilder != null ? schemaMappersBuilder.build() : DEFAULT_SCHEMA_MAPPERS);
+ this.enableSchemaCache = enableSchemaCache;
+ this.metaSchemaFactory = metaSchemaFactory;
+ }
+
+ public SchemaLoader getSchemaLoader() {
+ return this.schemaLoader;
+ }
+
+ /**
+ * Builder without keywords or formats.
+ *
+ * Typically {@link #builder(JsonSchemaFactory)} is what is required.
+ *
+ * @return a builder instance without any keywords or formats - usually not what one needs.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a factory with a default schema dialect. The schema dialect will only
+ * be used if the input does not specify a $schema.
+ *
+ * @param versionFlag the default dialect
+ * @return the factory
+ */
+ public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag) {
+ return getInstance(versionFlag, null);
+ }
+
+ /**
+ * Creates a factory with a default schema dialect. The schema dialect will only
+ * be used if the input does not specify a $schema.
+ *
+ * @param versionFlag the default dialect
+ * @param customizer to customize the factory
+ * @return the factory
+ */
+ public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag,
+ Consumer<JsonSchemaFactory.Builder> customizer) {
+ JsonSchemaVersion jsonSchemaVersion = checkVersion(versionFlag);
+ JsonMetaSchema metaSchema = jsonSchemaVersion.getInstance();
+ JsonSchemaFactory.Builder builder = builder().defaultMetaSchemaIri(metaSchema.getIri())
+ .metaSchema(metaSchema);
+ if (customizer != null) {
+ customizer.accept(builder);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Gets the json schema version to get the meta schema.
+ * <p>
+ * This throws an {@link IllegalArgumentException} for an unsupported value.
+ *
+ * @param versionFlag the schema dialect
+ * @return the version
+ */
+ public static JsonSchemaVersion checkVersion(SpecVersion.VersionFlag versionFlag){
+ if (null == versionFlag) return null;
+ switch (versionFlag) {
+ case V202012: return new Version202012();
+ case V201909: return new Version201909();
+ case V7: return new Version7();
+ case V6: return new Version6();
+ case V4: return new Version4();
+ default: throw new IllegalArgumentException("Unsupported value" + versionFlag);
+ }
+ }
+
+ /**
+ * Builder from an existing {@link JsonSchemaFactory}.
+ * <p>
+ * <code>
+ * JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909));
+ * </code>
+ *
+ * @param blueprint the existing factory
+ * @return the builder
+ */
+ public static Builder builder(final JsonSchemaFactory blueprint) {
+ Builder builder = builder()
+ .metaSchemas(blueprint.metaSchemas.values())
+ .defaultMetaSchemaIri(blueprint.defaultMetaSchemaIri)
+ .jsonMapper(blueprint.jsonMapper)
+ .yamlMapper(blueprint.yamlMapper);
+ if (blueprint.schemaLoadersBuilder != null) {
+ builder.schemaLoadersBuilder = SchemaLoaders.builder().with(blueprint.schemaLoadersBuilder);
+ }
+ if (blueprint.schemaMappersBuilder != null) {
+ builder.schemaMappersBuilder = SchemaMappers.builder().with(blueprint.schemaMappersBuilder);
+ }
+ return builder;
+ }
+
+ /**
+ * Creates a json schema from initial input.
+ *
+ * @param schemaUri the schema location
+ * @param schemaNode the schema data node
+ * @param config the config to use
+ * @return the schema
+ */
+ protected JsonSchema newJsonSchema(final SchemaLocation schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) {
+ final ValidationContext validationContext = createValidationContext(schemaNode, config);
+ JsonSchema jsonSchema = doCreate(validationContext, getSchemaLocation(schemaUri),
+ new JsonNodePath(validationContext.getConfig().getPathType()), schemaNode, null, false);
+ if (config.isPreloadJsonSchema()) {
+ try {
+ /*
+ * Attempt to preload and resolve $refs for performance.
+ */
+ jsonSchema.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.
+ */
+ }
+ }
+ return jsonSchema;
+ }
+
+ public JsonSchema create(ValidationContext validationContext, SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema) {
+ return doCreate(validationContext, schemaLocation, evaluationPath, schemaNode, parentSchema, false);
+ }
+
+ private JsonSchema doCreate(ValidationContext validationContext, SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, boolean suppressSubSchemaRetrieval) {
+ return JsonSchema.from(withMetaSchema(validationContext, schemaNode), schemaLocation, evaluationPath,
+ schemaNode, parentSchema, suppressSubSchemaRetrieval);
+ }
+
+ /**
+ * Determines the validation context to use for the schema given the parent
+ * validation context.
+ * <p>
+ * This is typically the same validation context unless the schema has a
+ * different $schema from the parent.
+ * <p>
+ * If the schema does not define a $schema, the parent should be used.
+ *
+ * @param validationContext the parent validation context
+ * @param schemaNode the schema node
+ * @return the validation context to use
+ */
+ private ValidationContext withMetaSchema(ValidationContext validationContext, JsonNode schemaNode) {
+ JsonMetaSchema metaSchema = getMetaSchema(schemaNode, validationContext.getConfig());
+ if (metaSchema != null && !metaSchema.getIri().equals(validationContext.getMetaSchema().getIri())) {
+ return new ValidationContext(metaSchema, validationContext.getJsonSchemaFactory(),
+ validationContext.getConfig(), validationContext.getSchemaReferences(),
+ validationContext.getSchemaResources(), validationContext.getDynamicAnchors());
+ }
+ return validationContext;
+ }
+
+ /**
+ * Gets the base IRI from the schema retrieval IRI if present otherwise return
+ * one with a null base IRI.
+ * <p>
+ * Note that the resolving of the $id or id in the schema node will take place
+ * in the JsonSchema constructor.
+ *
+ * @param schemaLocation the schema retrieval uri
+ * @return the schema location
+ */
+ protected SchemaLocation getSchemaLocation(SchemaLocation schemaLocation) {
+ return schemaLocation != null ? schemaLocation : SchemaLocation.DOCUMENT;
+ }
+
+ protected ValidationContext createValidationContext(final JsonNode schemaNode, SchemaValidatorsConfig config) {
+ final JsonMetaSchema jsonMetaSchema = getMetaSchemaOrDefault(schemaNode, config);
+ return new ValidationContext(jsonMetaSchema, this, config);
+ }
+
+ private JsonMetaSchema getMetaSchema(final JsonNode schemaNode, SchemaValidatorsConfig config) {
+ final JsonNode iriNode = schemaNode.get("$schema");
+ if (iriNode != null && iriNode.isTextual()) {
+ return metaSchemas.computeIfAbsent(normalizeMetaSchemaUri(iriNode.textValue()), id -> loadMetaSchema(id, config));
+ }
+ return null;
+ }
+
+ private JsonMetaSchema getMetaSchemaOrDefault(final JsonNode schemaNode, SchemaValidatorsConfig config) {
+ final JsonNode iriNode = schemaNode.get("$schema");
+ if (iriNode != null && !iriNode.isNull() && !iriNode.isTextual()) {
+ throw new JsonSchemaException("Unknown MetaSchema: " + iriNode.toString());
+ }
+ final String iri = iriNode == null || iriNode.isNull() ? defaultMetaSchemaIri : iriNode.textValue();
+ return getMetaSchema(iri, config);
+ }
+
+ /**
+ * Gets the meta-schema that is available to the factory.
+ *
+ * @param iri the IRI of the meta-schema
+ * @param config the schema validators config
+ * @return the meta-schema
+ */
+ public JsonMetaSchema getMetaSchema(String iri, SchemaValidatorsConfig config) {
+ String key = normalizeMetaSchemaUri(iri);
+ return metaSchemas.computeIfAbsent(key, id -> loadMetaSchema(id, config));
+ }
+
+ /**
+ * Loads the meta-schema from the configured meta-schema factory.
+ *
+ * @param iri the IRI of the meta-schema
+ * @param config the schema validators config
+ * @return the meta-schema
+ */
+ protected JsonMetaSchema loadMetaSchema(String iri, SchemaValidatorsConfig config) {
+ return this.metaSchemaFactory != null ? this.metaSchemaFactory.getMetaSchema(iri, this, config)
+ : DefaultJsonMetaSchemaFactory.getInstance().getMetaSchema(iri, this, config);
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * 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 config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final String schema, final SchemaValidatorsConfig config) {
+ try {
+ final JsonNode schemaNode = getJsonMapper().readTree(schema);
+ return newJsonSchema(null, schemaNode, config);
+ } catch (IOException ioe) {
+ logger.error("Failed to load json schema!", ioe);
+ throw new JsonSchemaException(ioe);
+ }
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * 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 JsonSchema getSchema(final String schema) {
+ return getSchema(schema, createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * 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 config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final InputStream schemaStream, final SchemaValidatorsConfig config) {
+ try {
+ final JsonNode schemaNode = getJsonMapper().readTree(schemaStream);
+ return newJsonSchema(null, schemaNode, config);
+ } catch (IOException ioe) {
+ logger.error("Failed to load json schema!", ioe);
+ throw new JsonSchemaException(ioe);
+ }
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * 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 JsonSchema getSchema(final InputStream schemaStream) {
+ return getSchema(schemaStream, createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the absolute IRI of the schema which can map to the retrieval IRI.
+ * @param config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final SchemaLocation schemaUri, final SchemaValidatorsConfig config) {
+ if (enableSchemaCache) {
+ // 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
+ JsonSchema cachedUriSchema = schemaCache.get(schemaUri);
+ if (cachedUriSchema == null) {
+ synchronized (this) { // acquire lock on shared factory object to prevent deadlock
+ cachedUriSchema = schemaCache.get(schemaUri);
+ if (cachedUriSchema == null) {
+ cachedUriSchema = getMappedSchema(schemaUri, config);
+ if (cachedUriSchema != null) {
+ schemaCache.put(schemaUri, cachedUriSchema);
+ }
+ }
+ }
+ }
+ return cachedUriSchema.withConfig(config);
+ }
+ return getMappedSchema(schemaUri, config);
+ }
+
+ protected ObjectMapper getYamlMapper() {
+ return this.yamlMapper != null ? this.yamlMapper : YamlMapperFactory.getInstance();
+ }
+
+ protected ObjectMapper getJsonMapper() {
+ return this.jsonMapper != null ? this.jsonMapper : JsonMapperFactory.getInstance();
+ }
+
+ /**
+ * Creates a schema validators config.
+ *
+ * @return the schema validators config
+ */
+ protected SchemaValidatorsConfig createSchemaValidatorsConfig() {
+ return new SchemaValidatorsConfig();
+ }
+
+ protected JsonSchema getMappedSchema(final SchemaLocation schemaUri, SchemaValidatorsConfig config) {
+ try (InputStream inputStream = this.schemaLoader.getSchema(schemaUri.getAbsoluteIri()).getInputStream()) {
+ if (inputStream == null) {
+ throw new IOException("Cannot load schema at " + schemaUri.toString());
+ }
+ final JsonNode schemaNode;
+ if (isYaml(schemaUri)) {
+ schemaNode = getYamlMapper().readTree(inputStream);
+ } else {
+ schemaNode = getJsonMapper().readTree(inputStream);
+ }
+
+ final JsonMetaSchema jsonMetaSchema = getMetaSchemaOrDefault(schemaNode, config);
+ JsonNodePath evaluationPath = new JsonNodePath(config.getPathType());
+ if (schemaUri.getFragment() == null
+ || schemaUri.getFragment().getNameCount() == 0) {
+ // Schema without fragment
+ ValidationContext validationContext = new ValidationContext(jsonMetaSchema, this, config);
+ return doCreate(validationContext, schemaUri, evaluationPath, schemaNode, null, true /* retrieved via id, resolving will not change anything */);
+ } else {
+ // Schema with fragment pointing to sub schema
+ final ValidationContext validationContext = createValidationContext(schemaNode, config);
+ SchemaLocation documentLocation = new SchemaLocation(schemaUri.getAbsoluteIri());
+ JsonSchema document = doCreate(validationContext, documentLocation, evaluationPath, schemaNode, null, false);
+ return document.getRefSchema(schemaUri.getFragment());
+ }
+ } catch (IOException e) {
+ logger.error("Failed to load json schema from {}", schemaUri.getAbsoluteIri(), e);
+ JsonSchemaException exception = new JsonSchemaException("Failed to load json schema from "+schemaUri.getAbsoluteIri());
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the absolute IRI of the schema which can map to the retrieval IRI.
+ * @return the schema
+ */
+ public JsonSchema getSchema(final URI schemaUri) {
+ return getSchema(SchemaLocation.of(schemaUri.toString()), createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the absolute IRI of the schema which can map to the retrieval IRI.
+ * @param jsonNode the node
+ * @param config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode, final SchemaValidatorsConfig config) {
+ return newJsonSchema(SchemaLocation.of(schemaUri.toString()), jsonNode, config);
+ }
+
+ /**
+ * 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 JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode) {
+ return newJsonSchema(SchemaLocation.of(schemaUri.toString()), jsonNode, createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the absolute IRI of the schema which can map to the retrieval IRI.
+ * @return the schema
+ */
+ public JsonSchema getSchema(final SchemaLocation schemaUri) {
+ return getSchema(schemaUri, createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the base absolute IRI
+ * @param jsonNode the node
+ * @param config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final SchemaLocation schemaUri, final JsonNode jsonNode, final SchemaValidatorsConfig config) {
+ return newJsonSchema(schemaUri, jsonNode, config);
+ }
+
+ /**
+ * Gets the schema.
+ *
+ * @param schemaUri the base absolute IRI
+ * @param jsonNode the node
+ * @return the schema
+ */
+ public JsonSchema getSchema(final SchemaLocation schemaUri, final JsonNode jsonNode) {
+ return newJsonSchema(schemaUri, jsonNode, createSchemaValidatorsConfig());
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * Using this is not recommended as there is potentially no base IRI for
+ * resolving references to the absolute IRI.
+ * <p>
+ * Prefer {@link #getSchema(SchemaLocation, JsonNode, SchemaValidatorsConfig)}
+ * instead to ensure the base IRI if no id is present.
+ *
+ * @param jsonNode the node
+ * @param config the config
+ * @return the schema
+ */
+ public JsonSchema getSchema(final JsonNode jsonNode, final SchemaValidatorsConfig config) {
+ return newJsonSchema(null, jsonNode, config);
+ }
+
+ /**
+ * Gets the schema.
+ * <p>
+ * Using this is not recommended as there is potentially no base IRI for
+ * resolving references to the absolute IRI.
+ * <p>
+ * Prefer {@link #getSchema(SchemaLocation, JsonNode)} instead to ensure the
+ * base IRI if no id is present.
+ *
+ * @param jsonNode the node
+ * @return the schema
+ */
+ public JsonSchema getSchema(final JsonNode jsonNode) {
+ return newJsonSchema(null, jsonNode, createSchemaValidatorsConfig());
+ }
+
+ 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.
+ * <p>
+ * This should not normalize any other unrecognized dialects.
+ *
+ * @param id the $schema identifier
+ * @return the normalized uri
+ */
+ static protected String normalizeMetaSchemaUri(String id) {
+ boolean found = false;
+ for (VersionFlag flag : SpecVersion.VersionFlag.values()) {
+ if(flag.getId().equals(id)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (id.contains("://json-schema.org/draft")) {
+ // unnormalized $schema
+ if (id.contains("/draft-07/")) {
+ id = SchemaId.V7;
+ } else if (id.contains("/draft/2019-09/")) {
+ id = SchemaId.V201909;
+ } else if (id.contains("/draft/2020-12/")) {
+ id = SchemaId.V202012;
+ } else if (id.contains("/draft-04/")) {
+ id = SchemaId.V4;
+ } else if (id.contains("/draft-06/")) {
+ id = SchemaId.V6;
+ }
+ }
+ }
+ return id;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchemaIdValidator.java b/src/main/java/com/networknt/schema/JsonSchemaIdValidator.java
new file mode 100644
index 0000000..0629e43
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchemaIdValidator.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 JsonSchemaIdValidator {
+ /**
+ * 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 validationContext the validation context for instance to get the
+ * meta schema
+ * @return true if valid
+ */
+ boolean validate(String id, boolean rootSchema, SchemaLocation schemaLocation,
+ SchemaLocation resolvedSchemaLocation, ValidationContext validationContext);
+
+ public static final JsonSchemaIdValidator DEFAULT = new DefaultJsonSchemaIdValidator();
+
+ /**
+ * Implementation of {@link JsonSchemaIdValidator}.
+ * <p>
+ * Note that this does not strictly follow the specification.
+ * <p>
+ * 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.
+ * <p>
+ * This also allows non-empty fragments.
+ */
+ public static class DefaultJsonSchemaIdValidator implements JsonSchemaIdValidator {
+ @Override
+ public boolean validate(String id, boolean rootSchema, SchemaLocation schemaLocation,
+ SchemaLocation resolvedSchemaLocation, ValidationContext validationContext) {
+ 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/JsonSchemaRef.java b/src/main/java/com/networknt/schema/JsonSchemaRef.java
new file mode 100644
index 0000000..c4d4cf4
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchemaRef.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 JsonSchema for references.
+ */
+public class JsonSchemaRef {
+
+ private final Supplier<JsonSchema> schemaSupplier;
+
+ public JsonSchemaRef(Supplier<JsonSchema> schema) {
+ this.schemaSupplier = schema;
+ }
+
+ public JsonSchema getSchema() {
+ return this.schemaSupplier.get();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchemaVersion.java b/src/main/java/com/networknt/schema/JsonSchemaVersion.java
new file mode 100644
index 0000000..47f9f64
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonSchemaVersion.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Json schema version.
+ */
+public interface JsonSchemaVersion {
+ /**
+ * Gets the meta-schema.
+ *
+ * @return the instance
+ */
+ JsonMetaSchema getInstance();
+}
diff --git a/src/main/java/com/networknt/schema/JsonType.java b/src/main/java/com/networknt/schema/JsonType.java
new file mode 100644
index 0000000..4e7a0ec
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonType.java
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 0000000..fe4b71a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonValidator.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+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 {
+ /**
+ * Validate the given JsonNode, the given node is the child node of the root node at given
+ * data path.
+ * @param executionContext ExecutionContext
+ * @param node JsonNode
+ * @param rootNode JsonNode
+ * @param instanceLocation JsonNodePath
+ *
+ * @return A list of ValidationMessage if there is any validation error, or an empty
+ * list if there is no error.
+ */
+ Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation);
+
+ /**
+ * 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 <code>$ref</code> not resolving)
+ * @since 1.0.54
+ */
+ default void preloadJsonSchema() throws JsonSchemaException {
+ // do nothing by default - to be overridden in subclasses
+ }
+
+ /**
+ * This is default implementation of walk method. Its job is to call the
+ * validate method if shouldValidateSchema is enabled.
+ */
+ @Override
+ default Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation)
+ : Collections.emptySet();
+ }
+
+ /**
+ * 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
+ */
+ public SchemaLocation getSchemaLocation();
+
+ /**
+ * 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 JsonNodePath getEvaluationPath();
+
+ /**
+ * The keyword of the validator.
+ *
+ * @return the keyword
+ */
+ public String getKeyword();
+}
diff --git a/src/main/java/com/networknt/schema/Keyword.java b/src/main/java/com/networknt/schema/Keyword.java
new file mode 100644
index 0000000..592c803
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Keyword.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * 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 evaluationPath the evaluation path
+ * @param schemaNode the schema node
+ * @param parentSchema the parent schema
+ * @param validationContext the validation context
+ * @return the validation
+ * @throws JsonSchemaException the exception
+ * @throws Exception the exception
+ */
+ JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception;
+}
diff --git a/src/main/java/com/networknt/schema/KeywordFactory.java b/src/main/java/com/networknt/schema/KeywordFactory.java
new file mode 100644
index 0000000..e887bc5
--- /dev/null
+++ b/src/main/java/com/networknt/schema/KeywordFactory.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;
+
+/**
+ * Factory for {@link Keyword}.
+ */
+@FunctionalInterface
+public interface KeywordFactory {
+ /**
+ * Gets the keyword given the keyword value.
+ *
+ * @param value the keyword value
+ * @param validationContext the validationContext
+ * @return the keyword
+ */
+ Keyword getKeyword(String value, ValidationContext validationContext);
+}
diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java
new file mode 100644
index 0000000..b8ee041
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MaxItemsValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for maxItems.
+ */
+public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator {
+
+ private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class);
+
+ private int max = 0;
+
+ public MaxItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext);
+ if (schemaNode.canConvertToExactIntegral()) {
+ max = schemaNode.intValue();
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (node.isArray()) {
+ if (node.size() > max) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(max, node.size()).build());
+ }
+ } else if (this.validationContext.getConfig().isTypeLoose()) {
+ if (1 > max) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(max, 1).build());
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MaxLengthValidator.java b/src/main/java/com/networknt/schema/MaxLengthValidator.java
new file mode 100644
index 0000000..08882ed
--- /dev/null
+++ b/src/main/java/com/networknt/schema/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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for maxLength.
+ */
+public class MaxLengthValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MaxLengthValidator.class);
+
+ private int maxLength;
+
+ public MaxLengthValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_LENGTH, validationContext);
+ maxLength = Integer.MAX_VALUE;
+ if (schemaNode != null && schemaNode.canConvertToExactIntegral()) {
+ maxLength = schemaNode.intValue();
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ 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(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(maxLength).build());
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
new file mode 100644
index 0000000..0d33b12
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MaxPropertiesValidator.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 com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator}for maxProperties.
+ */
+public class MaxPropertiesValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MaxPropertiesValidator.class);
+
+ private final int max;
+
+ public MaxPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES, validationContext);
+ if (schemaNode.canConvertToExactIntegral()) {
+ max = schemaNode.intValue();
+ } else {
+ max = Integer.MAX_VALUE;
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (node.isObject()) {
+ if (node.size() > max) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(max).build());
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java
new file mode 100644
index 0000000..03a7c9c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MaximumValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.utils.JsonNodeUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for maxmimum.
+ */
+public class MaximumValidator extends BaseJsonValidator {
+ 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;
+
+
+ public MaximumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, 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();
+ }
+
+ 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<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
+ // maximum only applies to numbers
+ return Collections.emptySet();
+ }
+
+ if (typedMaximum.crossesThreshold(node)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(typedMaximum.thresholdValue()).build());
+ }
+ return Collections.emptySet();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/MessageSourceValidationMessage.java b/src/main/java/com/networknt/schema/MessageSourceValidationMessage.java
new file mode 100644
index 0000000..1871236
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MessageSourceValidationMessage.java
@@ -0,0 +1,99 @@
+/*
+ * 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 java.util.function.BiConsumer;
+
+import com.networknt.schema.i18n.MessageSource;
+
+/**
+ * MessageSourceValidationMessage.
+ */
+public class MessageSourceValidationMessage {
+
+ public static Builder builder(MessageSource messageSource, Map<String, String> errorMessage,
+ BiConsumer<ValidationMessage, Boolean> observer) {
+ return new Builder(messageSource, errorMessage, observer);
+ }
+
+ public static class Builder extends BuilderSupport<Builder> {
+ public Builder(MessageSource messageSource, Map<String, String> errorMessage,
+ BiConsumer<ValidationMessage, Boolean> observer) {
+ super(messageSource, errorMessage, observer);
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
+
+ public abstract static class BuilderSupport<S> extends ValidationMessage.BuilderSupport<S> {
+ private final BiConsumer<ValidationMessage, Boolean> observer;
+ private final MessageSource messageSource;
+ private final Map<String, String> errorMessage;
+ private boolean failFast;
+ private Locale locale;
+
+ public BuilderSupport(MessageSource messageSource, Map<String, String> errorMessage,
+ BiConsumer<ValidationMessage, Boolean> observer) {
+ this.messageSource = messageSource;
+ this.observer = observer;
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public ValidationMessage build() {
+ // Use custom error message if present
+ String messagePattern = null;
+ if (this.errorMessage != null) {
+ messagePattern = this.errorMessage.get("");
+ if (this.property != null) {
+ String specificMessagePattern = this.errorMessage.get(this.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);
+ }
+ ValidationMessage validationMessage = super.build();
+ if (this.observer != null) {
+ this.observer.accept(validationMessage, this.failFast);
+ }
+ return validationMessage;
+ }
+
+ public S locale(Locale locale) {
+ this.locale = locale;
+ return self();
+ }
+
+ public S failFast(boolean failFast) {
+ this.failFast = failFast;
+ return self();
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java
new file mode 100644
index 0000000..83db6ec
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MinItemsValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for minItems.
+ */
+public class MinItemsValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MinItemsValidator.class);
+
+ private int min = 0;
+
+ public MinItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS, validationContext);
+ if (schemaNode.canConvertToExactIntegral()) {
+ min = schemaNode.intValue();
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (node.isArray()) {
+ if (node.size() < min) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(min, node.size())
+ .build());
+ }
+ } else if (this.validationContext.getConfig().isTypeLoose()) {
+ if (1 < min) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(min, 1).build());
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MinLengthValidator.java b/src/main/java/com/networknt/schema/MinLengthValidator.java
new file mode 100644
index 0000000..c82b8ec
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MinLengthValidator.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for minLength.
+ */
+public class MinLengthValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MinLengthValidator.class);
+
+ private int minLength;
+
+ public MinLengthValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_LENGTH, validationContext);
+ minLength = Integer.MIN_VALUE;
+ if (schemaNode != null && schemaNode.canConvertToExactIntegral()) {
+ minLength = schemaNode.intValue();
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ 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(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(minLength).build());
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MinMaxContainsValidator.java b/src/main/java/com/networknt/schema/MinMaxContainsValidator.java
new file mode 100644
index 0000000..768aba6
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MinMaxContainsValidator.java
@@ -0,0 +1,92 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * {@link JsonValidator} for {@literal maxContains} and {@literal minContains} in a schema.
+ * <p>
+ * 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 BaseJsonValidator {
+ private final Set<Analysis> analysis;
+
+ public MinMaxContainsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_CONTAINS, validationContext);
+
+ Set<Analysis> 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ return this.analysis != null ? this.analysis.stream()
+ .map(analysis -> message().instanceNode(node)
+ .instanceLocation(instanceLocation)
+ .messageKey(analysis.getMessageKey()).locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .type(analysis.getMessageKey())
+ .arguments(parentSchema.getSchemaNode().toString()).build())
+ .collect(Collectors.toCollection(LinkedHashSet::new)) : Collections.emptySet();
+ }
+
+ 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/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
new file mode 100644
index 0000000..1ff5664
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MinPropertiesValidator.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 com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for minProperties.
+ */
+public class MinPropertiesValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MinPropertiesValidator.class);
+
+ protected final int min;
+
+ public MinPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES, validationContext);
+ if (schemaNode.canConvertToExactIntegral()) {
+ min = schemaNode.intValue();
+ } else {
+ min = 0;
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (node.isObject()) {
+ if (node.size() < min) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(min).build());
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java
new file mode 100644
index 0000000..8b2e5e7
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MinimumValidator.java
@@ -0,0 +1,134 @@
+/*
+ * 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.utils.JsonNodeUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for minimum.
+ */
+public class MinimumValidator extends BaseJsonValidator {
+ 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;
+
+ public MinimumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, 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();
+ }
+
+ 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;
+ }
+ };
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
+ // minimum only applies to numbers
+ return Collections.emptySet();
+ }
+
+ if (typedMinimum.crossesThreshold(node)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(typedMinimum.thresholdValue()).build());
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/MultipleOfValidator.java b/src/main/java/com/networknt/schema/MultipleOfValidator.java
new file mode 100644
index 0000000..95b57ce
--- /dev/null
+++ b/src/main/java/com/networknt/schema/MultipleOfValidator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.utils.JsonNodeUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for multipleOf.
+ */
+public class MultipleOfValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(MultipleOfValidator.class);
+
+ private final BigDecimal divisor;
+
+ public MultipleOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF, validationContext);
+ this.divisor = getDivisor(schemaNode);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ if (this.divisor != null) {
+ BigDecimal dividend = getDividend(node);
+ if (dividend != null) {
+ if (dividend.divideAndRemainder(this.divisor)[1].abs().compareTo(BigDecimal.ZERO) > 0) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.divisor)
+ .build());
+ }
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * 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.validationContext.getConfig().isTypeLoose()
+ && JsonNodeUtil.isNumber(node, this.validationContext.getConfig())) {
+ // handling for type loose
+ return new BigDecimal(node.textValue());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/NonValidationKeyword.java b/src/main/java/com/networknt/schema/NonValidationKeyword.java
new file mode 100644
index 0000000..573ebea
--- /dev/null
+++ b/src/main/java/com/networknt/schema/NonValidationKeyword.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map.Entry;
+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 {
+ public Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext, Keyword keyword) {
+ super(schemaLocation, evaluationPath, keyword, schemaNode);
+ String id = validationContext.resolveSchemaId(schemaNode);
+ String anchor = validationContext.getMetaSchema().readAnchor(schemaNode);
+ String dynamicAnchor = validationContext.getMetaSchema().readDynamicAnchor(schemaNode);
+ if (id != null || anchor != null || dynamicAnchor != null) {
+ // Used to register schema resources with $id
+ validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ }
+ if ("$defs".equals(keyword.getValue()) || "definitions".equals(keyword.getValue())) {
+ for (Iterator<Entry<String, JsonNode>> field = schemaNode.fields(); field.hasNext(); ) {
+ Entry<String, JsonNode> property = field.next();
+ SchemaLocation location = schemaLocation.append(property.getKey());
+ JsonSchema schema = validationContext.newSchema(location, evaluationPath.append(property.getKey()),
+ property.getValue(), parentSchema);
+ validationContext.getSchemaReferences().put(location.toString(), schema);
+ }
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ return Collections.emptySet();
+ }
+ }
+
+ public NonValidationKeyword(String keyword) {
+ super(keyword);
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
+ return new Validator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext, this);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/NotAllowedValidator.java b/src/main/java/com/networknt/schema/NotAllowedValidator.java
new file mode 100644
index 0000000..3d9952b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/NotAllowedValidator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for notAllowed.
+ */
+public class NotAllowedValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(NotAllowedValidator.class);
+
+ private final List<String> fieldNames = new ArrayList<>();
+
+ public NotAllowedValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, 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());
+ }
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ Set<ValidationMessage> errors = null;
+
+ for (String fieldName : fieldNames) {
+ JsonNode propertyNode = node.get(fieldName);
+
+ if (propertyNode != null) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.add(message().property(fieldName).instanceNode(node)
+ .instanceLocation(instanceLocation.append(fieldName))
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(fieldName).build());
+ }
+ }
+
+ return errors == null ? Collections.emptySet() : Collections.unmodifiableSet(errors);
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java
new file mode 100644
index 0000000..a2d7f8a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/NotValidator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for not.
+ */
+public class NotValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(NotValidator.class);
+
+ private final JsonSchema schema;
+
+ public NotValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext);
+ this.schema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ Set<ValidationMessage> errors = null;
+ debug(logger, node, rootNode, instanceLocation);
+
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ executionContext.setFailFast(false);
+ errors = this.schema.validate(executionContext, node, rootNode, instanceLocation);
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+ }
+ if (errors.isEmpty()) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.schema.toString())
+ .build());
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ if (shouldValidateSchema) {
+ return validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ Set<ValidationMessage> errors = this.schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ if (errors.isEmpty()) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.schema.toString())
+ .build());
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ if (null != this.schema) {
+ this.schema.initializeValidators();
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
new file mode 100644
index 0000000..3ba564a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -0,0 +1,253 @@
+/*
+ * 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.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for oneOf.
+ */
+public class OneOfValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(OneOfValidator.class);
+
+ private final List<JsonSchema> schemas = new ArrayList<>();
+
+ private Boolean canShortCircuit = null;
+
+ public OneOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext);
+ int size = schemaNode.size();
+ for (int i = 0; i < size; i++) {
+ JsonNode childNode = schemaNode.get(i);
+ this.schemas.add(validationContext.newSchema( schemaLocation.append(i), evaluationPath.append(i), childNode, parentSchema));
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ Set<ValidationMessage> errors = null;
+
+ debug(logger, node, rootNode, instanceLocation);
+
+ ValidatorState state = executionContext.getValidatorState();
+
+ // this is a complex validator, we set the flag to true
+ state.setComplexValidator(true);
+
+ int numberOfValidSchema = 0;
+ int index = 0;
+ SetView<ValidationMessage> childErrors = null;
+ List<String> indexes = null;
+
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ DiscriminatorValidator discriminator = null;
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ DiscriminatorContext discriminatorContext = new DiscriminatorContext();
+ executionContext.enterDiscriminatorContext(discriminatorContext, instanceLocation);
+
+ // check if discriminator present
+ discriminator = (DiscriminatorValidator) this.getParentSchema().getValidators().stream()
+ .filter(v -> "discriminator".equals(v.getKeyword())).findFirst().orElse(null);
+ }
+ executionContext.setFailFast(false);
+ for (JsonSchema schema : this.schemas) {
+ Set<ValidationMessage> schemaErrors = Collections.emptySet();
+
+ // Reset state in case the previous validator did not match
+ state.setMatchedNode(true);
+
+ if (!state.isWalkEnabled()) {
+ schemaErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ } else {
+ schemaErrors = schema.walk(executionContext, node, rootNode, instanceLocation,
+ 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++;
+ if (indexes == null) {
+ indexes = new ArrayList<>();
+ }
+ indexes.add(Integer.toString(index));
+ }
+
+ if (numberOfValidSchema > 1 && canShortCircuit()) {
+ // short-circuit
+ // note that the short circuit means that only 2 valid schemas are reported even if could be more
+ break;
+ }
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ // The discriminator will cause all messages other than the one with the
+ // matching discriminator to be discarded. Note that the discriminator cannot
+ // affect the actual validation result.
+ if (discriminator != null && !discriminator.getPropertyName().isEmpty()) {
+ JsonNode discriminatorPropertyNode = node.get(discriminator.getPropertyName());
+ if (discriminatorPropertyNode != null) {
+ String discriminatorPropertyValue = discriminatorPropertyNode.asText();
+ discriminatorPropertyValue = discriminator.getMapping().getOrDefault(discriminatorPropertyValue,
+ discriminatorPropertyValue);
+ JsonNode refNode = schema.getSchemaNode().get("$ref");
+ if (refNode != null) {
+ String ref = refNode.asText();
+ if (ref.equals(discriminatorPropertyValue) || ref.endsWith("/" + discriminatorPropertyValue)) {
+ executionContext.getCurrentDiscriminatorContext().markMatch();
+ }
+ }
+ } else {
+ // See issue 436 where the condition was relaxed to not cause an assertion
+ // due to missing discriminator property value
+ // Also see BaseJsonValidator#checkDiscriminatorMatch
+ executionContext.getCurrentDiscriminatorContext().markIgnore();
+ }
+ }
+ DiscriminatorContext currentDiscriminatorContext = executionContext.getCurrentDiscriminatorContext();
+ if (currentDiscriminatorContext.isDiscriminatorMatchFound() && childErrors == null) {
+ // Note that the match is set if found and not reset so checking if childErrors
+ // found is null triggers on the correct schema
+ childErrors = new SetView<>();
+ childErrors.union(schemaErrors);
+ } else if (currentDiscriminatorContext.isDiscriminatorIgnore()) {
+ // This is the normal handling when discriminators aren't enabled
+ if (childErrors == null) {
+ childErrors = new SetView<>();
+ }
+ childErrors.union(schemaErrors);
+ }
+ } else if (!schemaErrors.isEmpty() && reportChildErrors(executionContext)) {
+ // This is the normal handling when discriminators aren't enabled
+ if (childErrors == null) {
+ childErrors = new SetView<>();
+ }
+ childErrors.union(schemaErrors);
+ }
+ index++;
+ }
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()
+ && (discriminator != null || executionContext.getCurrentDiscriminatorContext().isActive())
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorMatchFound()
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorIgnore()) {
+ errors = Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .arguments(
+ "based on the provided discriminator. No alternative could be chosen based on the discriminator property")
+ .build());
+ }
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+
+ if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
+ executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
+ }
+ }
+
+ // ensure there is always an "OneOf" error reported if number of valid schemas
+ // is not equal to 1.
+ // errors will only not be null in the discriminator case where no match is found
+ if (numberOfValidSchema != 1 && errors == null) {
+ ValidationMessage message = message().instanceNode(node).instanceLocation(instanceLocation)
+ .messageKey(numberOfValidSchema > 1 ? "oneOf.indexes" : "oneOf")
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(Integer.toString(numberOfValidSchema), numberOfValidSchema > 1 ? String.join(", ", indexes) : "").build();
+ if (childErrors != null) {
+ errors = new SetView<ValidationMessage>().union(Collections.singleton(message)).union(childErrors);
+ } else {
+ errors = Collections.singleton(message);
+ }
+ }
+
+ // Make sure to signal parent handlers we matched
+ if (errors == null || errors.isEmpty()) {
+ state.setMatchedNode(true);
+ }
+
+ // reset the ValidatorState object
+ resetValidatorState(executionContext);
+
+ return errors != null ? errors : Collections.emptySet();
+ }
+
+ /**
+ * 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() {
+ if (this.canShortCircuit == null) {
+ boolean canShortCircuit = true;
+ for (JsonValidator validator : getEvaluationParentSchema().getValidators()) {
+ if ("unevaluatedProperties".equals(validator.getKeyword())
+ || "unevaluatedItems".equals(validator.getKeyword())) {
+ canShortCircuit = false;
+ }
+ }
+ this.canShortCircuit = canShortCircuit;
+ }
+ return this.canShortCircuit;
+ }
+
+ private static void resetValidatorState(ExecutionContext executionContext) {
+ ValidatorState state = executionContext.getValidatorState();
+ state.setComplexValidator(false);
+ state.setMatchedNode(true);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ HashSet<ValidationMessage> validationMessages = new LinkedHashSet<>();
+ if (shouldValidateSchema) {
+ validationMessages.addAll(validate(executionContext, node, rootNode, instanceLocation));
+ } else {
+ for (JsonSchema schema : this.schemas) {
+ schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ }
+ }
+ return validationMessages;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ for (JsonSchema schema: this.schemas) {
+ schema.initializeValidators();
+ }
+ canShortCircuit(); // cache the flag
+ }
+}
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 0000000..b3c56e7
--- /dev/null
+++ b/src/main/java/com/networknt/schema/OutputFormat.java
@@ -0,0 +1,160 @@
+/*
+ * 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.Set;
+
+import com.networknt.schema.output.HierarchicalOutputUnitFormatter;
+import com.networknt.schema.output.ListOutputUnitFormatter;
+import com.networknt.schema.output.OutputFlag;
+import com.networknt.schema.output.OutputUnit;
+
+/**
+ * Formats the validation results.
+ *
+ * @param <T> the result type
+ */
+public interface OutputFormat<T> {
+ /**
+ * Customize the execution context before validation.
+ * <p>
+ * The validation context should only be used for reference as it is shared.
+ *
+ * @param executionContext the execution context
+ * @param validationContext the validation context for reference
+ */
+ default void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ }
+
+ /**
+ * Formats the validation results.
+ *
+ * @param jsonSchema the schema
+ * @param validationMessages the validation messages
+ * @param executionContext the execution context
+ * @param validationContext the validation context
+ *
+ * @return the result
+ */
+ T format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext);
+
+ /**
+ * The Default output format.
+ */
+ public static final Default DEFAULT = new Default();
+
+ /**
+ * The Boolean output format.
+ */
+ public static final Flag BOOLEAN = new Flag();
+
+ /**
+ * The Flag output format.
+ */
+ public static final Flag FLAG = new Flag();
+
+ /**
+ * The List output format.
+ */
+ public static final List LIST = new List();
+
+
+ /**
+ * The Hierarchical output format.
+ */
+ public static final Hierarchical HIERARCHICAL = new Hierarchical();
+
+ /**
+ * The Default output format.
+ */
+ public static class Default implements OutputFormat<Set<ValidationMessage>> {
+ @Override
+ public void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(false);
+ }
+
+ @Override
+ public Set<ValidationMessage> format(JsonSchema jsonSchema,
+ Set<ValidationMessage> validationMessages, ExecutionContext executionContext, ValidationContext validationContext) {
+ return validationMessages;
+ }
+ }
+
+ /**
+ * The Flag output format.
+ */
+ public static class Flag implements OutputFormat<OutputFlag> {
+ @Override
+ public void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(false);
+ executionContext.getExecutionConfig().setFailFast(true);
+ }
+
+ @Override
+ public OutputFlag format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext) {
+ return new OutputFlag(validationMessages.isEmpty());
+ }
+ }
+
+ /**
+ * The Boolean output format.
+ */
+ public static class Boolean implements OutputFormat<java.lang.Boolean> {
+ @Override
+ public void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(false);
+ executionContext.getExecutionConfig().setFailFast(true);
+ }
+
+ @Override
+ public java.lang.Boolean format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext) {
+ return validationMessages.isEmpty();
+ }
+ }
+
+ /**
+ * The List output format.
+ */
+ public static class List implements OutputFormat<OutputUnit> {
+ @Override
+ public void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ }
+
+ @Override
+ public OutputUnit format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext) {
+ return ListOutputUnitFormatter.format(validationMessages, executionContext, validationContext);
+ }
+ }
+
+ /**
+ * The Hierarchical output format.
+ */
+ public static class Hierarchical implements OutputFormat<OutputUnit> {
+ @Override
+ public void customize(ExecutionContext executionContext, ValidationContext validationContext) {
+ }
+
+ @Override
+ public OutputUnit format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext) {
+ return HierarchicalOutputUnitFormatter.format(jsonSchema, validationMessages, executionContext, validationContext);
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/PathType.java b/src/main/java/com/networknt/schema/PathType.java
new file mode 100644
index 0000000..4104c77
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PathType.java
@@ -0,0 +1,264 @@
+package com.networknt.schema;
+
+import java.util.function.BiFunction;
+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 + "." + replaceCommonSpecialCharactersIfPresent(token),
+ (currentPath, index) -> currentPath + "[" + index + "]"),
+
+ /**
+ * 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)) {
+ return currentPath + "." + token;
+ }
+
+ // 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);
+
+ return currentPath + "['" + token + "']";
+ }, (currentPath, index) -> currentPath + "[" + index + "]"),
+
+ /**
+ * 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);
+ return currentPath + "/" + token;
+ }, (currentPath, index) -> currentPath + "/" + index),
+
+ /**
+ * Paths as a URI reference.
+ */
+ URI_REFERENCE("", (currentPath, token) -> {
+ return !currentPath.isEmpty() ? currentPath + "/" + token : token;
+ }, (currentPath, index) -> currentPath + "/" + index);
+
+ /**
+ * The default path generation approach to use.
+ */
+ public static final PathType DEFAULT = LEGACY;
+ private final String rootToken;
+ private final BiFunction<String, String, String> appendTokenFn;
+ private final BiFunction<String, Integer, String> 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, BiFunction<String, String, String> appendTokenFn, BiFunction<String, Integer, String> 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.
+ * @return The resulting complete path.
+ */
+ public String append(String currentPath, String child) {
+ return this.appendTokenFn.apply(currentPath, child);
+ }
+
+ /**
+ * Append the given index to the provided current path.
+ *
+ * @param currentPath The path to append to.
+ * @param index The index to append.
+ * @return The resulting complete path.
+ */
+ public String append(String currentPath, int index) {
+ return this.appendIndexFn.apply(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.substring(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.substring(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/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
new file mode 100644
index 0000000..797558a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
@@ -0,0 +1,113 @@
+/*
+ * 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.annotation.JsonNodeAnnotation;
+import com.networknt.schema.regex.RegularExpression;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for patternProperties.
+ */
+public class PatternPropertiesValidator extends BaseJsonValidator {
+ public static final String PROPERTY = "patternProperties";
+ private static final Logger logger = LoggerFactory.getLogger(PatternPropertiesValidator.class);
+ private final Map<RegularExpression, JsonSchema> schemas = new IdentityHashMap<>();
+
+ private Boolean hasUnevaluatedPropertiesValidator = null;
+
+ public PatternPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN_PROPERTIES, validationContext);
+ if (!schemaNode.isObject()) {
+ throw new JsonSchemaException("patternProperties must be an object node");
+ }
+ Iterator<String> names = schemaNode.fieldNames();
+ while (names.hasNext()) {
+ String name = names.next();
+ RegularExpression pattern = RegularExpression.compile(name, validationContext);
+ schemas.put(pattern, validationContext.newSchema(schemaLocation.append(name), evaluationPath.append(name),
+ schemaNode.get(name), parentSchema));
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!node.isObject()) {
+ return Collections.emptySet();
+ }
+ Set<ValidationMessage> errors = null;
+ Set<String> matchedInstancePropertyNames = null;
+ Iterator<String> names = node.fieldNames();
+ boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext);
+ while (names.hasNext()) {
+ String name = names.next();
+ JsonNode n = node.get(name);
+ for (Map.Entry<RegularExpression, JsonSchema> entry : schemas.entrySet()) {
+ if (entry.getKey().matches(name)) {
+ JsonNodePath path = instanceLocation.append(name);
+ Set<ValidationMessage> results = entry.getValue().validate(executionContext, n, rootNode, path);
+ if (results.isEmpty()) {
+ if (collectAnnotations) {
+ if (matchedInstancePropertyNames == null) {
+ matchedInstancePropertyNames = new LinkedHashSet<>();
+ }
+ matchedInstancePropertyNames.add(name);
+ }
+ } else {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.addAll(results);
+ }
+ }
+ }
+ }
+ if (collectAnnotations) {
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword())
+ .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames
+ : Collections.emptySet())
+ .build());
+ }
+ return errors == null ? Collections.emptySet() : Collections.unmodifiableSet(errors);
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedPropertiesValidator();
+ }
+
+ private boolean hasUnevaluatedPropertiesValidator() {
+ if (this.hasUnevaluatedPropertiesValidator == null) {
+ this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties");
+ }
+ return hasUnevaluatedPropertiesValidator;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(schemas.values());
+ collectAnnotations(); // cache the flag
+ }
+}
diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java
new file mode 100644
index 0000000..db6f32e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PatternValidator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.regex.RegularExpression;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+public class PatternValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class);
+ private String pattern;
+ private RegularExpression compiledPattern;
+
+ public PatternValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN, validationContext);
+
+ this.pattern = Optional.ofNullable(schemaNode).filter(JsonNode::isTextual).map(JsonNode::textValue).orElse(null);
+ try {
+ this.compiledPattern = RegularExpression.compile(this.pattern, validationContext);
+ } 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 Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
+ if (nodeType != JsonType.STRING) {
+ return Collections.emptySet();
+ }
+
+ try {
+ if (!matches(node.asText())) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(this.pattern).build());
+ }
+ } catch (JsonSchemaException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ logger.error("Failed to apply pattern '{}' at {}: {}", this.pattern, instanceLocation, e.getMessage());
+ throw e;
+ }
+
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
new file mode 100644
index 0000000..94bd136
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
@@ -0,0 +1,187 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.utils.SetView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for prefixItems.
+ */
+public class PrefixItemsValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(PrefixItemsValidator.class);
+
+ private final List<JsonSchema> tupleSchema;
+
+ private Boolean hasUnevaluatedItemsValidator = null;
+
+ public PrefixItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.PREFIX_ITEMS, validationContext);
+
+ this.tupleSchema = new ArrayList<>();
+
+ if (schemaNode instanceof ArrayNode && 0 < schemaNode.size()) {
+ for (JsonNode s : schemaNode) {
+ this.tupleSchema.add(validationContext.newSchema(schemaLocation, evaluationPath, s, parentSchema));
+ }
+ } else {
+ throw new IllegalArgumentException("The value of 'prefixItems' MUST be a non-empty array of valid JSON Schemas.");
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ // ignores non-arrays
+ if (node.isArray()) {
+ SetView<ValidationMessage> errors = null;
+ int count = Math.min(node.size(), this.tupleSchema.size());
+ for (int i = 0; i < count; ++i) {
+ JsonNodePath path = instanceLocation.append(i);
+ Set<ValidationMessage> results = this.tupleSchema.get(i).validate(executionContext, node.get(i), rootNode, path);
+ if (!results.isEmpty()) {
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ errors.union(results);
+ }
+ }
+
+ // Add annotation
+ if (collectAnnotations() || 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(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(schemas).build());
+ } else {
+ // Applies to all
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(true).build());
+ }
+ }
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : errors;
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ Set<ValidationMessage> validationMessages = new LinkedHashSet<>();
+
+ if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
+ && node.isArray()) {
+ ArrayNode array = (ArrayNode) node;
+ int count = Math.min(node.size(), this.tupleSchema.size());
+ for (int i = 0; i < count; ++i) {
+ JsonNode n = node.get(i);
+ JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i));
+ if (n.isNull() && defaultNode != null) {
+ array.set(i, defaultNode);
+ n = defaultNode;
+ }
+ doWalk(executionContext, validationMessages, i, n, rootNode, instanceLocation, shouldValidateSchema);
+ }
+ }
+
+ return validationMessages;
+ }
+
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
+ private void doWalk(ExecutionContext executionContext, Set<ValidationMessage> validationMessages, int i,
+ JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i),
+ shouldValidateSchema, validationMessages);
+ }
+
+ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
+ //@formatter:off
+ boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(
+ executionContext,
+ ValidatorTypeCode.PREFIX_ITEMS.getValue(),
+ node,
+ rootNode,
+ instanceLocation,
+ walkSchema, this
+ );
+ if (executeWalk) {
+ validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
+ }
+ this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(
+ executionContext,
+ ValidatorTypeCode.PREFIX_ITEMS.getValue(),
+ node,
+ rootNode,
+ instanceLocation,
+ walkSchema,
+ this, validationMessages
+ );
+ //@formatter:on
+ }
+
+ public List<JsonSchema> getTupleSchema() {
+ return this.tupleSchema;
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedItemsValidator();
+ }
+
+ private boolean hasUnevaluatedItemsValidator() {
+ if (this.hasUnevaluatedItemsValidator == null) {
+ this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems");
+ }
+ return hasUnevaluatedItemsValidator;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(this.tupleSchema);
+ collectAnnotations(); // cache the flag
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
new file mode 100644
index 0000000..a47e61c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -0,0 +1,248 @@
+/*
+ * 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.ObjectNode;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.utils.SetView;
+import com.networknt.schema.walk.WalkListenerRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 JsonValidator} for properties.
+ */
+public class PropertiesValidator extends BaseJsonValidator {
+ public static final String PROPERTY = "properties";
+ private static final Logger logger = LoggerFactory.getLogger(PropertiesValidator.class);
+ private final Map<String, JsonSchema> schemas = new LinkedHashMap<>();
+
+ private Boolean hasUnevaluatedPropertiesValidator;
+
+ public PropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTIES, validationContext);
+ for (Iterator<Entry<String, JsonNode>> it = schemaNode.fields(); it.hasNext();) {
+ Entry<String, JsonNode> entry = it.next();
+ String pname = entry.getKey();
+ this.schemas.put(pname, validationContext.newSchema(schemaLocation.append(pname),
+ evaluationPath.append(pname), entry.getValue(), parentSchema));
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ SetView<ValidationMessage> errors = null;
+
+ // get the Validator state object storing validation data
+ ValidatorState state = executionContext.getValidatorState();
+
+ Set<ValidationMessage> requiredErrors = null;
+ Set<String> matchedInstancePropertyNames = null;
+ boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext);
+ for (Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
+ JsonNode propertyNode = node.get(entry.getKey());
+ if (propertyNode != null) {
+ JsonNodePath path = instanceLocation.append(entry.getKey());
+ if (collectAnnotations) {
+ if (matchedInstancePropertyNames == null) {
+ matchedInstancePropertyNames = new LinkedHashSet<>();
+ }
+ matchedInstancePropertyNames.add(entry.getKey());
+ }
+ // 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 (isComplex) {
+ 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)
+ Set<ValidationMessage> result = entry.getValue().validate(executionContext, propertyNode, rootNode,
+ path);
+ if (!result.isEmpty()) {
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ errors.union(result);
+ }
+ } else {
+ // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation.
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), false);
+ }
+
+ // reset the complex flag to the original value before the recursive call
+ if (isComplex) {
+ state.setComplexValidator(isComplex);
+ // if this was a complex validator, the node has matched and has been validated
+ state.setMatchedNode(true);
+ }
+ } else {
+ if (state.isWalkEnabled()) {
+ // 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.
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), true);
+ }
+
+ // check whether the node which has not matched was mandatory or not
+
+ // the node was mandatory, decide which behavior to employ when validator has
+ // not matched
+ if (state.isComplexValidator()) {
+ if (getParentSchema().hasRequiredValidator()) {
+
+ // The required validator runs for all properties in the node and not just the
+ // current propertyNode
+ if (requiredErrors == null) {
+ // Note that the results of the required validator shouldn't be added to the errors here, the required validator
+ // will still trigger normally in the schema
+ requiredErrors = getParentSchema().getRequiredValidator().validate(executionContext, node,
+ rootNode, instanceLocation);
+ }
+ if (!requiredErrors.isEmpty()) {
+ // this was a complex validator (ex oneOf) and the node has not been matched
+ state.setMatchedNode(false);
+ return Collections.emptySet();
+ }
+ }
+ }
+ }
+ }
+ if (collectAnnotations) {
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword(getKeyword()).value(matchedInstancePropertyNames == null ? Collections.emptySet()
+ : matchedInstancePropertyNames)
+ .build());
+ }
+
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : errors;
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ SetView<ValidationMessage> validationMessages = new SetView<>();
+ if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyPropertyDefaults() && null != node
+ && node.getNodeType() == JsonNodeType.OBJECT) {
+ applyPropertyDefaults((ObjectNode) node);
+ }
+ if (shouldValidateSchema) {
+ validationMessages.union(validate(executionContext, node, rootNode, instanceLocation));
+ } else {
+ WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner();
+ for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner, false);
+ }
+ }
+ return validationMessages;
+ }
+
+ private boolean collectAnnotations() {
+ return hasUnevaluatedPropertiesValidator();
+ }
+
+ private boolean hasUnevaluatedPropertiesValidator() {
+ if (this.hasUnevaluatedPropertiesValidator == null) {
+ this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties");
+ }
+ return hasUnevaluatedPropertiesValidator;
+ }
+
+ private void applyPropertyDefaults(ObjectNode node) {
+ for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
+ JsonNode propertyNode = node.get(entry.getKey());
+
+ JsonNode defaultNode = getDefaultNode(entry.getValue());
+ if (defaultNode == null) {
+ continue;
+ }
+ boolean applyDefault = propertyNode == null || (propertyNode.isNull() && this.validationContext.getConfig()
+ .getApplyDefaultsStrategy().shouldApplyPropertyDefaultsIfNull());
+ if (applyDefault) {
+ node.set(entry.getKey(), defaultNode);
+ }
+ }
+ }
+
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
+ private void walkSchema(ExecutionContext executionContext, Map.Entry<String, JsonSchema> entry, JsonNode node,
+ JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema,
+ SetView<ValidationMessage> validationMessages, WalkListenerRunner propertyWalkListenerRunner, boolean skipWalk) {
+ JsonSchema propertySchema = entry.getValue();
+ JsonNode propertyNode = (node == null ? null : node.get(entry.getKey()));
+ JsonNodePath path = instanceLocation.append(entry.getKey());
+ boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(executionContext,
+ ValidatorTypeCode.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());
+ }
+ if (executeWalk && !(skipWalk && propertyNode == null)) {
+ validationMessages.union(
+ propertySchema.walk(executionContext, propertyNode, rootNode, path, shouldValidateSchema));
+ }
+ propertyWalkListenerRunner.runPostWalkListeners(executionContext, ValidatorTypeCode.PROPERTIES.getValue(), propertyNode,
+ rootNode, path, propertySchema,
+ this, validationMessages);
+ }
+
+ public Map<String, JsonSchema> getSchemas() {
+ return this.schemas;
+ }
+
+
+ @Override
+ public void preloadJsonSchema() {
+ preloadJsonSchemas(this.schemas.values());
+ collectAnnotations(); // cache the flag
+ }
+}
diff --git a/src/main/java/com/networknt/schema/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/PropertyNamesValidator.java
new file mode 100644
index 0000000..6edc145
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PropertyNamesValidator.java
@@ -0,0 +1,68 @@
+/*
+ * 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(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTYNAMES, validationContext);
+ innerSchema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ Set<ValidationMessage> errors = null;
+ for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
+ final String pname = it.next();
+ final TextNode pnameText = TextNode.valueOf(pname);
+ final Set<ValidationMessage> schemaErrors = innerSchema.validate(executionContext, pnameText, node, instanceLocation.append(pname));
+ for (final ValidationMessage schemaError : schemaErrors) {
+ final String path = schemaError.getInstanceLocation().toString();
+ String msg = schemaError.getMessage();
+ if (msg.startsWith(path)) {
+ msg = msg.substring(path.length()).replaceFirst("^:\\s*", "");
+ }
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ errors.add(
+ message().property(pname).instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(pname, msg).build());
+ }
+ }
+ return errors == null || errors.isEmpty() ? Collections.emptySet() : 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
new file mode 100644
index 0000000..9ea33ea
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ReadOnlyValidator.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * {@link JsonValidator} for readOnly.
+ */
+public class ReadOnlyValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(ReadOnlyValidator.class);
+
+ private final boolean readOnly;
+
+ public ReadOnlyValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.READ_ONLY, validationContext);
+
+ this.readOnly = validationContext.getConfig().isReadOnly();
+ logger.debug("Loaded ReadOnlyValidator for property {} as {}", parentSchema, "read mode");
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ if (this.readOnly) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).build());
+ }
+ return Collections.emptySet();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
new file mode 100644
index 0000000..e73bb7d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
@@ -0,0 +1,148 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} that resolves $recursiveRef.
+ */
+public class RecursiveRefValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(RecursiveRefValidator.class);
+
+ protected final JsonSchemaRef schema;
+
+ public RecursiveRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.RECURSIVE_REF, validationContext);
+
+ String refValue = schemaNode.asText();
+ if (!"#".equals(refValue)) {
+ ValidationMessage validationMessage = message()
+ .type(ValidatorTypeCode.RECURSIVE_REF.getValue()).code("internal.invalidRecursiveRef")
+ .message("{0}: The value of a $recursiveRef must be '#' but is '{1}'").instanceLocation(schemaLocation.getFragment())
+ .instanceNode(this.schemaNode)
+ .evaluationPath(evaluationPath).arguments(refValue).build();
+ throw new JsonSchemaException(validationMessage);
+ }
+ this.schema = getRefSchema(parentSchema, validationContext, refValue, evaluationPath);
+ }
+
+ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue,
+ JsonNodePath evaluationPath) {
+ return new JsonSchemaRef(new CachedSupplier<>(() -> {
+ return getSchema(parentSchema, validationContext, refValue, evaluationPath);
+ }));
+ }
+
+ static JsonSchema getSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue,
+ JsonNodePath evaluationPath) {
+ JsonSchema refSchema = parentSchema.findSchemaResourceRoot(); // Get the document
+ JsonSchema current = refSchema;
+ JsonSchema 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
+ while (current.getEvaluationParentSchema() != null) {
+ current = current.getEvaluationParentSchema();
+ 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;
+ }
+ }
+ }
+ }
+ if (refSchema != null) {
+ refSchema = refSchema.fromRef(parentSchema, evaluationPath);
+ }
+ return refSchema;
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.RECURSIVE_REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ debug(logger, node, rootNode, instanceLocation);
+ // 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.
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.RECURSIVE_REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ }
+
+ public JsonSchemaRef getSchemaRef() {
+ return this.schema;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ JsonSchema jsonSchema = null;
+ try {
+ jsonSchema = this.schema.getSchema();
+ } catch (JsonSchemaException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw new JsonSchemaException(e);
+ }
+ // 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();
+ JsonSchema check = jsonSchema;
+ boolean circularDependency = false;
+ while (check.getEvaluationParentSchema() != null) {
+ check = check.getEvaluationParentSchema();
+ if (check.getSchemaLocation().equals(schemaLocation)) {
+ circularDependency = true;
+ break;
+ }
+ }
+ if (!circularDependency) {
+ jsonSchema.initializeValidators();
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
new file mode 100644
index 0000000..1a32e6e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -0,0 +1,233 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} that resolves $ref.
+ */
+public class RefValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(RefValidator.class);
+
+ protected final JsonSchemaRef schema;
+
+ private static final String REF_CURRENT = "#";
+
+ public RefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext);
+ String refValue = schemaNode.asText();
+ this.schema = getRefSchema(parentSchema, validationContext, refValue, evaluationPath);
+ }
+
+ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue,
+ JsonNodePath evaluationPath) {
+ // 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 JsonSchemaRef(new CachedSupplier<>(() -> {
+ JsonSchema schemaResource = validationContext.getSchemaResources().get(schemaUriFinal);
+ if (schemaResource == null) {
+ schemaResource = validationContext.getJsonSchemaFactory().getSchema(schemaLocation, validationContext.getConfig());
+ if (schemaResource != null) {
+ copySchemaResources(validationContext, schemaResource);
+ }
+ }
+ if (index < 0) {
+ if (schemaResource == null) {
+ return null;
+ }
+ return schemaResource.fromRef(parentSchema, evaluationPath);
+ } else {
+ String newRefValue = refValue.substring(index);
+ String find = schemaLocation.getAbsoluteIri() + newRefValue;
+ JsonSchema findSchemaResource = validationContext.getSchemaResources().get(find);
+ if (findSchemaResource == null) {
+ findSchemaResource = validationContext.getDynamicAnchors().get(find);
+ }
+ if (findSchemaResource != null) {
+ schemaResource = findSchemaResource;
+ } else {
+ schemaResource = getJsonSchema(schemaResource, validationContext, newRefValue, refValueOriginal,
+ evaluationPath);
+ }
+ if (schemaResource == null) {
+ return null;
+ }
+ return schemaResource.fromRef(parentSchema, evaluationPath);
+ }
+ }));
+
+ } else if (SchemaLocation.Fragment.isAnchorFragment(refValue)) {
+ String absoluteIri = resolve(parentSchema, refValue);
+ // Schema resource needs to update the parent and evaluation path
+ return new JsonSchemaRef(new CachedSupplier<>(() -> {
+ JsonSchema schemaResource = validationContext.getSchemaResources().get(absoluteIri);
+ if (schemaResource == null) {
+ schemaResource = validationContext.getDynamicAnchors().get(absoluteIri);
+ }
+ if (schemaResource == null) {
+ schemaResource = getJsonSchema(parentSchema, validationContext, refValue, refValueOriginal, evaluationPath);
+ }
+ if (schemaResource == null) {
+ return null;
+ }
+ return schemaResource.fromRef(parentSchema, evaluationPath);
+ }));
+ }
+ if (refValue.equals(REF_CURRENT)) {
+ return new JsonSchemaRef(new CachedSupplier<>(
+ () -> parentSchema.findSchemaResourceRoot().fromRef(parentSchema, evaluationPath)));
+ }
+ return new JsonSchemaRef(new CachedSupplier<>(
+ () -> getJsonSchema(parentSchema, validationContext, refValue, refValueOriginal, evaluationPath)
+ .fromRef(parentSchema, evaluationPath)));
+ }
+
+ private static void copySchemaResources(ValidationContext validationContext, JsonSchema schemaResource) {
+ if (!schemaResource.getValidationContext().getSchemaResources().isEmpty()) {
+ validationContext.getSchemaResources()
+ .putAll(schemaResource.getValidationContext().getSchemaResources());
+ }
+ if (!schemaResource.getValidationContext().getSchemaReferences().isEmpty()) {
+ validationContext.getSchemaReferences()
+ .putAll(schemaResource.getValidationContext().getSchemaReferences());
+ }
+ if (!schemaResource.getValidationContext().getDynamicAnchors().isEmpty()) {
+ validationContext.getDynamicAnchors()
+ .putAll(schemaResource.getValidationContext().getDynamicAnchors());
+ }
+ }
+
+ private static String resolve(JsonSchema parentSchema, String refValue) {
+ // $ref prevents a sibling $id from changing the base uri
+ JsonSchema base = parentSchema;
+ if (parentSchema.getId() != null && parentSchema.parentSchema != null) {
+ base = parentSchema.parentSchema;
+ }
+ return SchemaLocation.resolve(base.getSchemaLocation(), refValue);
+ }
+
+ private static JsonSchema getJsonSchema(JsonSchema parent,
+ ValidationContext validationContext,
+ String refValue,
+ String refValueOriginal,
+ JsonNodePath evaluationPath) {
+ // This should be processing json pointer fragments only
+ JsonNodePath fragment = SchemaLocation.Fragment.of(refValue);
+ 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
+ JsonSchema result = validationContext.getSchemaReferences().get(schemaReference);
+ if (result == null) {
+ synchronized (validationContext.getJsonSchemaFactory()) { // acquire lock on shared factory object to prevent deadlock
+ result = validationContext.getSchemaReferences().get(schemaReference);
+ if (result == null) {
+ result = parent.getSubSchema(fragment);
+ if (result != null) {
+ validationContext.getSchemaReferences().put(schemaReference, result);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ debug(logger, node, rootNode, instanceLocation);
+ // 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.
+ JsonSchema refSchema = this.schema.getSchema();
+ if (refSchema == null) {
+ ValidationMessage validationMessage = message().type(ValidatorTypeCode.REF.getValue())
+ .code("internal.unresolvedRef").message("{0}: Reference {1} cannot be resolved")
+ .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath())
+ .arguments(schemaNode.asText()).build();
+ throw new InvalidSchemaRefException(validationMessage);
+ }
+ return refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ }
+
+ public JsonSchemaRef getSchemaRef() {
+ return this.schema;
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ JsonSchema jsonSchema = null;
+ try {
+ jsonSchema = this.schema.getSchema();
+ } catch (JsonSchemaException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw new JsonSchemaException(e);
+ }
+ // 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();
+ JsonSchema check = jsonSchema;
+ boolean circularDependency = false;
+ while (check.getEvaluationParentSchema() != null) {
+ check = check.getEvaluationParentSchema();
+ if (check.getSchemaLocation().equals(schemaLocation)) {
+ circularDependency = true;
+ break;
+ }
+ }
+ if (!circularDependency) {
+ jsonSchema.initializeValidators();
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/RequiredValidator.java b/src/main/java/com/networknt/schema/RequiredValidator.java
new file mode 100644
index 0000000..1b6b816
--- /dev/null
+++ b/src/main/java/com/networknt/schema/RequiredValidator.java
@@ -0,0 +1,72 @@
+/*
+ * 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.*;
+
+/**
+ * {@link JsonValidator} for required.
+ */
+public class RequiredValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class);
+
+ private final List<String> fieldNames = new ArrayList<>();
+
+ public RequiredValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.REQUIRED, validationContext);
+ if (schemaNode.isArray()) {
+ for (JsonNode fieldNme : schemaNode) {
+ fieldNames.add(fieldNme.asText());
+ }
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (!node.isObject()) {
+ return Collections.emptySet();
+ }
+
+ Set<ValidationMessage> errors = null;
+
+ for (String fieldName : fieldNames) {
+ JsonNode propertyNode = node.get(fieldName);
+
+ if (propertyNode == null) {
+ if (errors == null) {
+ errors = new LinkedHashSet<>();
+ }
+ /**
+ * Note that for the required validation the instanceLocation does not contain the missing property
+ * <p>
+ * @see <a href="https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-basic">Basic</a>
+ */
+ errors.add(message().instanceNode(node).property(fieldName).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(fieldName).build());
+ }
+ }
+
+ return errors == null ? Collections.emptySet() : Collections.unmodifiableSet(errors);
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/SchemaId.java b/src/main/java/com/networknt/schema/SchemaId.java
new file mode 100644
index 0000000..6f7b84f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/SchemaId.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;
+
+/**
+ * Schema Identifier used in $schema.
+ */
+public class SchemaId {
+ /**
+ * Draft 4.
+ */
+ public static final String V4 = "http://json-schema.org/draft-04/schema#";
+
+ /**
+ * Draft 6.
+ */
+ public static final String V6 = "http://json-schema.org/draft-06/schema#";
+
+ /**
+ * Draft 7.
+ */
+ public static final String V7 = "http://json-schema.org/draft-07/schema#";
+
+ /**
+ * Draft 2019-09.
+ */
+ public static final String V201909 = "https://json-schema.org/draft/2019-09/schema";
+
+ /**
+ * Draft 2020-12.
+ */
+ public static final String V202012 = "https://json-schema.org/draft/2020-12/schema";
+}
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 0000000..1ded824
--- /dev/null
+++ b/src/main/java/com/networknt/schema/SchemaLocation.java
@@ -0,0 +1,396 @@
+/*
+ * 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;
+
+/**
+ * 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 JsonNodePath JSON_POINTER = new JsonNodePath(PathType.JSON_POINTER);
+ private static final JsonNodePath ANCHOR = new JsonNodePath(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 JsonNodePath 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, JsonNodePath 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.
+ * <p>
+ * 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 JsonNodePath 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;
+ }
+ String[] iriParts = iri.split("#");
+ AbsoluteIri absoluteIri = null;
+ JsonNodePath fragment = JSON_POINTER;
+ if (iriParts.length > 0) {
+ absoluteIri = AbsoluteIri.of(iriParts[0]);
+ }
+ if (iriParts.length > 1) {
+ fragment = Fragment.of(iriParts[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);
+ }
+ JsonNodePath fragment = JSON_POINTER;
+ String[] parts = absoluteIriReferenceOrFragment.split("#");
+ AbsoluteIri absoluteIri = this.getAbsoluteIri();
+ if (absoluteIri != null) {
+ if (!parts[0].isEmpty()) {
+ absoluteIri = absoluteIri.resolve(parts[0]);
+ }
+ } else {
+ absoluteIri = AbsoluteIri.of(parts[0]);
+ }
+ if (parts.length > 1 && !parts[1].isEmpty()) {
+ fragment = Fragment.of(parts[1]);
+ }
+ 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() + "#";
+ }
+ String[] parts = absoluteIriReferenceOrFragment.split("#");
+ AbsoluteIri absoluteIri = schemaLocation.getAbsoluteIri();
+ String resolved = parts[0];
+ if (absoluteIri != null) {
+ if (!parts[0].isEmpty()) {
+ resolved = absoluteIri.resolve(parts[0]).toString();
+ } else {
+ resolved = absoluteIri.toString();
+ }
+ }
+ if (parts.length > 1 && !parts[1].isEmpty()) {
+ resolved = resolved + "#" + parts[1];
+ } 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 JsonNodePath of(String fragmentString) {
+ if (fragmentString.startsWith("#")) {
+ fragmentString = fragmentString.substring(1);
+ }
+ JsonNodePath fragment = JSON_POINTER;
+ String[] fragmentParts = fragmentString.split("/");
+
+ 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 {
+ fragment = fragment.append(fragmentPart.toString());
+ }
+ }
+ if (index == -1 && fragmentString.endsWith("/")) {
+ // Trailing / in fragment
+ fragment = fragment.append("");
+ }
+ 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 JsonNodePath fragment = JSON_POINTER;
+
+ /**
+ * Sets the canonical absolute IRI of the schema object.
+ * <p>
+ * 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.
+ * <p>
+ * 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(JsonNodePath 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.toString());
+ }
+ result.append("#");
+ if (this.fragment != null) {
+ result.append(this.fragment.toString());
+ }
+ 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/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
new file mode 100644
index 0000000..538aa80
--- /dev/null
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -0,0 +1,626 @@
+/*
+ * 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.i18n.DefaultMessageSource;
+import com.networknt.schema.i18n.MessageSource;
+import com.networknt.schema.walk.DefaultItemWalkListenerRunner;
+import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
+import com.networknt.schema.walk.DefaultPropertyWalkListenerRunner;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkListenerRunner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+public class SchemaValidatorsConfig {
+ /**
+ * Used to validate the acceptable $id values.
+ */
+ private JsonSchemaIdValidator schemaIdValidator = JsonSchemaIdValidator.DEFAULT;
+
+ /**
+ * 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 = ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
+
+ /**
+ * 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, "messages" provided in schema are used for forming validation errors
+ * else default messages are used
+ */
+ private boolean isCustomMessageSupported = true;
+
+ /**
+ * When set to true, support for discriminators is enabled for validations of
+ * oneOf, anyOf and allOf as described on <a href=
+ * "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">GitHub</a>.
+ */
+ private boolean openAPI3StyleDiscriminators = false;
+
+ /**
+ * Contains a mapping of how strict a keyword's validators should be.
+ * Defaults to {@literal true}.
+ * <p>
+ * Each validator has its own understanding of what constitutes strict
+ * and permissive.
+ */
+ private final Map<String, Boolean> strictness = new HashMap<>(0);
+
+ /**
+ * 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
+ * <p>
+ * 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;
+
+ /**
+ * When set to true assumes that schema is used to validate incoming data from an API.
+ */
+ private Boolean readOnly = null;
+
+ /**
+ * When set to true assumes that schema is used to to validate outgoing data from an API.
+ */
+ private Boolean writeOnly = null;
+
+ /**
+ * The approach used to generate paths in reported messages, logs and errors. Default is the legacy "JSONPath-like" approach.
+ */
+ private PathType pathType = PathType.DEFAULT;
+
+ /**
+ * Controls if the schema will automatically be preloaded.
+ */
+ private boolean preloadJsonSchema = 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<String, List<JsonSchemaWalkListener>> keywordWalkListenersMap = new HashMap<>();
+
+ private final List<JsonSchemaWalkListener> propertyWalkListeners = new ArrayList<>();
+
+ private final List<JsonSchemaWalkListener> itemWalkListeners = new ArrayList<>();
+
+ private ExecutionContextCustomizer executionContextCustomizer;
+
+ private boolean loadCollectors = true;
+
+ /**
+ * The Locale to consider when loading validation messages from the default resource bundle.
+ */
+ private Locale locale;
+
+ /**
+ * The message source to use for generating localised messages.
+ */
+ private MessageSource messageSource;
+
+ /**
+ * Since Draft 2019-09 format assertions are not enabled by default.
+ */
+ private Boolean formatAssertionsEnabled = null;
+
+ /************************ START OF UNEVALUATED CHECKS **********************************/
+
+ // These are costly in terms of performance so we provide a way to disable them.
+ private boolean disableUnevaluatedItems = false;
+ private boolean disableUnevaluatedProperties = false;
+
+ public SchemaValidatorsConfig disableUnevaluatedAnalysis() {
+ disableUnevaluatedItems();
+ disableUnevaluatedProperties();
+ return this;
+ }
+
+ public SchemaValidatorsConfig disableUnevaluatedItems() {
+ this.disableUnevaluatedItems = true;
+ return this;
+ }
+
+ public SchemaValidatorsConfig disableUnevaluatedProperties() {
+ this.disableUnevaluatedProperties = true;
+ return this;
+ }
+
+ public SchemaValidatorsConfig enableUnevaluatedAnalysis() {
+ enableUnevaluatedItems();
+ enableUnevaluatedProperties();
+ return this;
+ }
+
+ public SchemaValidatorsConfig enableUnevaluatedItems() {
+ this.disableUnevaluatedItems = false;
+ return this;
+ }
+
+ public SchemaValidatorsConfig enableUnevaluatedProperties() {
+ this.disableUnevaluatedProperties = false;
+ return this;
+ }
+
+ public boolean isUnevaluatedItemsAnalysisDisabled() {
+ return this.disableUnevaluatedItems;
+ }
+
+ public boolean isUnevaluatedItemsAnalysisEnabled() {
+ return !isUnevaluatedItemsAnalysisDisabled();
+ }
+
+ public boolean isUnevaluatedPropertiesAnalysisDisabled() {
+ return this.disableUnevaluatedProperties;
+ }
+
+ public boolean isUnevaluatedPropertiesAnalysisEnabled() {
+ return !isUnevaluatedPropertiesAnalysisDisabled();
+ }
+
+ /************************ END OF UNEVALUATED CHECKS **********************************/
+
+ /**
+ *
+ * @return true if type loose is used.
+ */
+ public boolean isTypeLoose() {
+ return this.typeLoose;
+ }
+
+ public void setTypeLoose(boolean typeLoose) {
+ this.typeLoose = typeLoose;
+ }
+
+ /**
+ * When enabled,
+ * {@link JsonValidator#validate(ExecutionContext, JsonNode, JsonNode, JsonNodePath)}
+ * doesn't return any {@link java.util.Set}&lt;{@link ValidationMessage}&gt;,
+ * 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 != null ? applyDefaultsStrategy
+ : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
+ }
+
+ public ApplyDefaultsStrategy getApplyDefaultsStrategy() {
+ return this.applyDefaultsStrategy;
+ }
+
+ public boolean isHandleNullableField() {
+ return this.handleNullableField;
+ }
+
+ public void setHandleNullableField(boolean handleNullableField) {
+ this.handleNullableField = handleNullableField;
+ }
+
+ public boolean isEcma262Validator() {
+ return this.ecma262Validator;
+ }
+
+ public void setEcma262Validator(boolean ecma262Validator) {
+ this.ecma262Validator = ecma262Validator;
+ }
+
+ public boolean isJavaSemantics() {
+ return this.javaSemantics;
+ }
+
+ public void setJavaSemantics(boolean javaSemantics) {
+ this.javaSemantics = javaSemantics;
+ }
+
+ public boolean isCustomMessageSupported() {
+ return isCustomMessageSupported;
+ }
+
+ public void setCustomMessageSupported(boolean customMessageSupported) {
+ this.isCustomMessageSupported = customMessageSupported;
+ }
+
+ public void addKeywordWalkListener(JsonSchemaWalkListener keywordWalkListener) {
+ if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
+ List<JsonSchemaWalkListener> keywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, keywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).add(keywordWalkListener);
+ }
+
+ public void addKeywordWalkListener(String keyword, JsonSchemaWalkListener keywordWalkListener) {
+ if (this.keywordWalkListenersMap.get(keyword) == null) {
+ List<JsonSchemaWalkListener> keywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(keyword, keywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(keyword).add(keywordWalkListener);
+ }
+
+ public void addKeywordWalkListeners(List<JsonSchemaWalkListener> keywordWalkListeners) {
+ if (this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY) == null) {
+ List<JsonSchemaWalkListener> ikeywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(ALL_KEYWORD_WALK_LISTENER_KEY, ikeywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(ALL_KEYWORD_WALK_LISTENER_KEY).addAll(keywordWalkListeners);
+ }
+
+ public void addKeywordWalkListeners(String keyword, List<JsonSchemaWalkListener> keywordWalkListeners) {
+ if (this.keywordWalkListenersMap.get(keyword) == null) {
+ List<JsonSchemaWalkListener> ikeywordWalkListeners = new ArrayList<>();
+ this.keywordWalkListenersMap.put(keyword, ikeywordWalkListeners);
+ }
+ this.keywordWalkListenersMap.get(keyword).addAll(keywordWalkListeners);
+ }
+
+ public void addPropertyWalkListeners(List<JsonSchemaWalkListener> 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<JsonSchemaWalkListener> itemWalkListeners) {
+ this.itemWalkListeners.addAll(itemWalkListeners);
+ }
+
+ public List<JsonSchemaWalkListener> getPropertyWalkListeners() {
+ return this.propertyWalkListeners;
+ }
+
+ public Map<String, List<JsonSchemaWalkListener>> getKeywordWalkListenersMap() {
+ return this.keywordWalkListenersMap;
+ }
+
+ public List<JsonSchemaWalkListener> getArrayItemWalkListeners() {
+ return this.itemWalkListeners;
+ }
+
+ private final WalkListenerRunner itemWalkListenerRunner = new DefaultItemWalkListenerRunner(getArrayItemWalkListeners());
+
+ WalkListenerRunner getItemWalkListenerRunner() {
+ return this.itemWalkListenerRunner;
+ }
+
+ private WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(getKeywordWalkListenersMap());
+
+ WalkListenerRunner getKeywordWalkListenerRunner() {
+ return this.keywordWalkListenerRunner;
+ }
+
+ private WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(getPropertyWalkListeners());
+
+ WalkListenerRunner getPropertyWalkListenerRunner() {
+ return this.propertyWalkListenerRunner;
+ }
+
+ public SchemaValidatorsConfig() {
+ }
+
+ public ExecutionContextCustomizer getExecutionContextCustomizer() {
+ return this.executionContextCustomizer;
+ }
+
+ public void setExecutionContextCustomizer(ExecutionContextCustomizer executionContextCustomizer) {
+ this.executionContextCustomizer = executionContextCustomizer;
+ }
+
+ public boolean isLosslessNarrowing() {
+ return this.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 this.openAPI3StyleDiscriminators;
+ }
+
+ /**
+ * When enabled, the validation of <code>anyOf</code> and <code>allOf</code> in
+ * polymorphism will respect OpenAPI 3 style discriminators as described in the
+ * <a href=
+ * "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminatorObject">OpenAPI
+ * 3.0.3 spec</a>. The presence of a discriminator configuration on the schema
+ * will lead to the following changes in the behavior:
+ * <ul>
+ * <li>for <code>oneOf</code> the spec is unfortunately very vague. Whether
+ * <code>oneOf</code> semantics should be affected by discriminators or not is
+ * not even 100% clear within the members of the OAS steering committee.
+ * Therefore <code>oneOf</code> at the moment ignores discriminators</li>
+ * <li>for <code>anyOf</code> 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
+ * <code>anyOf</code> 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.</li>
+ * <li>for <code>allOf</code> 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</li>
+ * </ul>
+ *
+ * @param openAPI3StyleDiscriminators whether or not discriminators should be
+ * used. Defaults to <code>false</code>
+ * @since 1.0.51
+ */
+ public void setOpenAPI3StyleDiscriminators(boolean openAPI3StyleDiscriminators) {
+ this.openAPI3StyleDiscriminators = openAPI3StyleDiscriminators;
+ }
+
+ public void setLoadCollectors(boolean loadCollectors) {
+ this.loadCollectors = loadCollectors;
+ }
+
+ public boolean doLoadCollectors() {
+ return this.loadCollectors;
+ }
+
+ public boolean isReadOnly() {
+ return null != this.readOnly && this.readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ public boolean isWriteOnly() {
+ return null != this.writeOnly && this.writeOnly;
+ }
+
+ public void setWriteOnly(boolean writeOnly) {
+ this.writeOnly = writeOnly;
+ }
+
+ /**
+ * Use {@code isReadOnly} or {@code isWriteOnly}
+ * @return true if schema is used to write data
+ */
+ @Deprecated
+ public boolean isWriteMode() {
+ return null == this.writeOnly || this.writeOnly;
+ }
+
+ /**
+ * Set the approach used to generate paths in messages, logs and errors (default is PathType.LEGACY).
+ *
+ * @param pathType The path generation approach.
+ */
+ public void setPathType(PathType pathType) {
+ this.pathType = pathType;
+ }
+
+ /**
+ * Get the approach used to generate paths in messages, logs and errors.
+ *
+ * @return The path generation approach.
+ */
+ public PathType getPathType() {
+ return this.pathType;
+ }
+
+ /**
+ * 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.
+ * <p>
+ * 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);
+ }
+
+ /**
+ * Alters the strictness of validations for a specific keyword. When set to
+ * {@literal true}, instructs the keyword's validators to perform strict
+ * validation. Otherwise, a validator may perform a more permissive check.
+ *
+ * @param keyword The keyword to adjust (not null)
+ * @param strict Whether to perform strict validations
+ */
+ public void setStrict(String keyword, boolean strict) {
+ this.strictness.put(Objects.requireNonNull(keyword, "keyword cannot be null"), strict);
+ }
+
+ /**
+ * Get the locale to consider when generating localised messages (default is the
+ * JVM default).
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Set the locale to consider when generating localised messages.
+ * <p>
+ * Note that this locale is set on a schema basis. To configure the schema on a
+ * per execution basis use
+ * {@link com.networknt.schema.ExecutionConfig#setLocale(Locale)}.
+ *
+ * @param locale The locale.
+ */
+ public void setLocale(Locale locale) {
+ this.locale = 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;
+ }
+
+ /**
+ * Set the message source to use for generating localised messages.
+ *
+ * @param messageSource the message source
+ */
+ public void setMessageSource(MessageSource messageSource) {
+ this.messageSource = messageSource;
+ }
+
+ /**
+ * Gets the format assertion enabled flag.
+ * <p>
+ * This defaults to null meaning that it will follow the defaults of the
+ * specification.
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Sets the format assertion enabled flag.
+ *
+ * @param formatAssertionsEnabled the format assertions enabled flag
+ */
+ public void setFormatAssertionsEnabled(Boolean formatAssertionsEnabled) {
+ this.formatAssertionsEnabled = formatAssertionsEnabled;
+ }
+
+ /**
+ * Gets the schema id validator to validate $id.
+ *
+ * @return the validator
+ */
+ public JsonSchemaIdValidator getSchemaIdValidator() {
+ return schemaIdValidator;
+ }
+
+ /**
+ * Sets the schema id validator to validate $id.
+ *
+ * @param schemaIdValidator the validator
+ */
+ public void setSchemaIdValidator(JsonSchemaIdValidator schemaIdValidator) {
+ this.schemaIdValidator = schemaIdValidator;
+ }
+
+ /**
+ * Gets if the schema should be preloaded.
+ *
+ * @return true if it should be preloaded
+ */
+ public boolean isPreloadJsonSchema() {
+ return preloadJsonSchema;
+ }
+
+ /**
+ * Sets if the schema should be preloaded.
+ *
+ * @param preloadJsonSchema true to preload
+ */
+ public void setPreloadJsonSchema(boolean preloadJsonSchema) {
+ this.preloadJsonSchema = preloadJsonSchema;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/SpecVersion.java b/src/main/java/com/networknt/schema/SpecVersion.java
new file mode 100644
index 0000000..3f02cd9
--- /dev/null
+++ b/src/main/java/com/networknt/schema/SpecVersion.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+public class SpecVersion {
+
+ public enum VersionFlag {
+
+ V4(1 << 0, SchemaId.V4),
+ V6(1 << 1, SchemaId.V6),
+ V7(1 << 2, SchemaId.V7),
+ V201909(1 << 3, SchemaId.V201909),
+ V202012(1 << 4, SchemaId.V202012);
+
+
+ private final long versionFlagValue;
+ private final String id;
+
+ VersionFlag(long versionFlagValue, String id) {
+ this.versionFlagValue = versionFlagValue;
+ this.id = id;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public long getVersionFlagValue() {
+ return this.versionFlagValue;
+ }
+
+ public static Optional<VersionFlag> fromId(String id) {
+ for (VersionFlag v: VersionFlag.values()) {
+ if (v.id.equals(id)) return Optional.of(v);
+ }
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/SpecVersionDetector.java b/src/main/java/com/networknt/schema/SpecVersionDetector.java
new file mode 100644
index 0000000..ce7fd9b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/SpecVersionDetector.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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+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 SpecVersionDetector {
+
+ protected static final Map<String, VersionFlag> supportedVersions = new HashMap<>();
+ private static final String SCHEMA_TAG = "$schema";
+
+ static {
+ supportedVersions.put("draft2019-09", VersionFlag.V201909);
+ supportedVersions.put("draft2020-12", VersionFlag.V202012);
+ supportedVersions.put("draft4", VersionFlag.V4);
+ supportedVersions.put("draft6", VersionFlag.V6);
+ supportedVersions.put("draft7", VersionFlag.V7);
+ }
+
+ private SpecVersionDetector() {
+ // Prevent instantiation of this utility class
+ }
+
+ /**
+ * Detects schema version based on the schema tag: if the schema tag is not present, throws
+ * {@link JsonSchemaException} 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 VersionFlag detect(JsonNode jsonNode) {
+ return detectOptionalVersion(jsonNode, true).orElseThrow(
+ () -> new JsonSchemaException("'" + 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<VersionFlag> detectOptionalVersion(JsonNode jsonNode, boolean throwIfUnsupported) {
+ return Optional.ofNullable(jsonNode.get(SCHEMA_TAG)).map(schemaTag -> {
+
+ String schemaTagValue = schemaTag.asText();
+ String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(schemaTagValue);
+
+ if (throwIfUnsupported) {
+ return VersionFlag.fromId(schemaUri)
+ .orElseThrow(() -> new JsonSchemaException("'" + schemaTagValue + "' is unrecognizable schema"));
+ } else {
+ return VersionFlag.fromId(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 VersionFlag detectVersion(JsonNode jsonNode, Path specification, VersionFlag 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<VersionFlag> detectVersionFromPath(Path path) {
+ return StreamSupport.stream(path.spliterator(), false)
+ .map(Path::toString)
+ .map(supportedVersions::get)
+ .filter(Objects::nonNull)
+ .findAny();
+ }
+
+ public static Optional<VersionFlag> detectOptionalVersion(String schemaUri) {
+ return VersionFlag.fromId(schemaUri);
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/ThresholdMixin.java b/src/main/java/com/networknt/schema/ThresholdMixin.java
new file mode 100644
index 0000000..99399fb
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ThresholdMixin.java
@@ -0,0 +1,25 @@
+/*
+ * 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 ThresholdMixin {
+ boolean crossesThreshold(JsonNode node);
+
+ String thresholdValue();
+}
diff --git a/src/main/java/com/networknt/schema/TrueValidator.java b/src/main/java/com/networknt/schema/TrueValidator.java
new file mode 100644
index 0000000..0c003d9
--- /dev/null
+++ b/src/main/java/com/networknt/schema/TrueValidator.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * {@link JsonValidator} for true.
+ */
+public class TrueValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(TrueValidator.class);
+
+ public TrueValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.TRUE, validationContext);
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ // 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
new file mode 100644
index 0000000..38fa58e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/TypeFactory.java
@@ -0,0 +1,93 @@
+/*
+ * 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.isBinary())
+ 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
new file mode 100644
index 0000000..245f690
--- /dev/null
+++ b/src/main/java/com/networknt/schema/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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.utils.JsonNodeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * {@link JsonValidator} for type.
+ */
+public class TypeValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(TypeValidator.class);
+
+ private final JsonType schemaType;
+ private final UnionTypeValidator unionTypeValidator;
+
+ public TypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext);
+ this.schemaType = TypeFactory.getSchemaNodeType(schemaNode);
+ if (this.schemaType == JsonType.UNION) {
+ this.unionTypeValidator = new UnionTypeValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext);
+ } else {
+ this.unionTypeValidator = null;
+ }
+ }
+
+ public JsonType getSchemaType() {
+ return this.schemaType;
+ }
+
+ public boolean equalsToSchemaType(JsonNode node) {
+ return JsonNodeUtil.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.validationContext);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (this.schemaType == JsonType.UNION) {
+ return this.unionTypeValidator.validate(executionContext, node, rootNode, instanceLocation);
+ }
+
+ if (!equalsToSchemaType(node)) {
+ JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast())
+ .arguments(nodeType.toString(), this.schemaType.toString()).build());
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
new file mode 100644
index 0000000..a01273f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
@@ -0,0 +1,211 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.networknt.schema.VersionCode.MinV202012;
+
+/**
+ * {@link JsonValidator} for unevaluatedItems.
+ */
+public class UnevaluatedItemsValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(UnevaluatedItemsValidator.class);
+
+ private final JsonSchema schema;
+
+ private final boolean isMinV202012;
+ private static final VersionFlag DEFAULT_VERSION = VersionFlag.V201909;
+
+ public UnevaluatedItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNEVALUATED_ITEMS,
+ validationContext);
+ isMinV202012 = MinV202012.getVersions().contains(SpecVersionDetector
+ .detectOptionalVersion(validationContext.getMetaSchema().getIri()).orElse(DEFAULT_VERSION));
+ if (schemaNode.isObject() || schemaNode.isBoolean()) {
+ this.schema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ } else {
+ throw new IllegalArgumentException("The value of 'unevaluatedItems' MUST be a valid JSON Schema.");
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ if (!node.isArray()) {
+ return Collections.emptySet();
+ }
+
+ debug(logger, node, rootNode, instanceLocation);
+ /*
+ * 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<JsonNodeAnnotation> validEvaluationPathFilter = a -> {
+ return executionContext.getResults().isValid(instanceLocation, a.getEvaluationPath());
+ };
+
+ Predicate<JsonNodeAnnotation> adjacentEvaluationPathFilter = a -> a.getEvaluationPath()
+ .startsWith(this.evaluationPath.getParent());
+
+ List<JsonNodeAnnotation> 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.size() > 0) {
+ evaluated = true;
+ }
+ } else {
+ // Get all the "items" for the instanceLocation
+ List<JsonNodeAnnotation> 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 (JsonNodeAnnotation 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<JsonNodeAnnotation> additionalItems = instanceLocationAnnotations.stream()
+ .filter(a -> additionalItemsKeyword.equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation 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<JsonNodeAnnotation> unevaluatedItems = instanceLocationAnnotations.stream()
+ .filter(a -> "unevaluatedItems".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation annotation : unevaluatedItems) {
+ if (annotation.getValue() instanceof Boolean && Boolean.TRUE.equals(annotation.getValue())) {
+ // The annotation "unevaluatedItems: true"
+ valid = true;
+ }
+ }
+ }
+ }
+ Set<ValidationMessage> messages = null;
+ if (!valid) {
+ // Get all the "contains" for the instanceLocation
+ List<JsonNodeAnnotation> contains = instanceLocationAnnotations.stream()
+ .filter(a -> "contains".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+
+ Set<Integer> containsEvaluated = new HashSet<>();
+ boolean containsEvaluatedAll = false;
+ for (JsonNodeAnnotation a : contains) {
+ if (a.getValue() instanceof List) {
+ List<Integer> values = a.getValue();
+ containsEvaluated.addAll(values);
+ } else if (a.getValue() instanceof Boolean) {
+ containsEvaluatedAll = true;
+ }
+ }
+
+ messages = new LinkedHashSet<>();
+ 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"
+ messages.add(message().instanceNode(node).instanceLocation(instanceLocation).arguments(x)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).build());
+ } else {
+ // Schema errors will be reported as is
+ messages.addAll(this.schema.validate(executionContext, node.get(x), node,
+ instanceLocation.append(x)));
+ }
+ evaluated = true;
+ }
+ }
+ }
+ if (messages.isEmpty()) {
+ 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(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
+ .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
+ .keyword("unevaluatedItems").value(true).build());
+ }
+ return messages == null || messages.isEmpty() ? Collections.emptySet() : messages;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java
new file mode 100644
index 0000000..61b6ced
--- /dev/null
+++ b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java
@@ -0,0 +1,141 @@
+/*
+ * 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.annotation.JsonNodeAnnotation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * {@link JsonValidator} for unevaluatedProperties.
+ */
+public class UnevaluatedPropertiesValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(UnevaluatedPropertiesValidator.class);
+
+ private final JsonSchema schema;
+
+ public UnevaluatedPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNEVALUATED_PROPERTIES, validationContext);
+
+ if (schemaNode.isObject() || schemaNode.isBoolean()) {
+ this.schema = validationContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema);
+ } else {
+ throw new IllegalArgumentException("The value of 'unevaluatedProperties' MUST be a valid JSON Schema.");
+ }
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ if (!node.isObject()) {
+ return Collections.emptySet();
+ }
+
+ debug(logger, node, rootNode, instanceLocation);
+ // Get all the valid adjacent annotations
+ Predicate<JsonNodeAnnotation> validEvaluationPathFilter = a -> {
+ return executionContext.getResults().isValid(instanceLocation, a.getEvaluationPath());
+ };
+
+ Predicate<JsonNodeAnnotation> adjacentEvaluationPathFilter = a -> a.getEvaluationPath()
+ .startsWith(this.evaluationPath.getParent());
+
+ List<JsonNodeAnnotation> instanceLocationAnnotations = executionContext.getAnnotations().asMap()
+ .getOrDefault(instanceLocation, Collections.emptyList());
+
+ Set<String> evaluatedProperties = new LinkedHashSet<>(); // The properties that unevaluatedProperties schema
+ Set<String> existingEvaluatedProperties = new LinkedHashSet<>();
+ // Get all the "properties" for the instanceLocation
+ List<JsonNodeAnnotation> properties = instanceLocationAnnotations.stream()
+ .filter(a -> "properties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation annotation : properties) {
+ if (annotation.getValue() instanceof Set) {
+ Set<String> p = annotation.getValue();
+ existingEvaluatedProperties.addAll(p);
+ }
+ }
+
+ // Get all the "patternProperties" for the instanceLocation
+ List<JsonNodeAnnotation> patternProperties = instanceLocationAnnotations.stream()
+ .filter(a -> "patternProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation annotation : patternProperties) {
+ if (annotation.getValue() instanceof Set) {
+ Set<String> p = annotation.getValue();
+ existingEvaluatedProperties.addAll(p);
+ }
+ }
+
+ // Get all the "patternProperties" for the instanceLocation
+ List<JsonNodeAnnotation> additionalProperties = instanceLocationAnnotations.stream()
+ .filter(a -> "additionalProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation annotation : additionalProperties) {
+ if (annotation.getValue() instanceof Set) {
+ Set<String> p = annotation.getValue();
+ existingEvaluatedProperties.addAll(p);
+ }
+ }
+
+ // Get all the "unevaluatedProperties" for the instanceLocation
+ List<JsonNodeAnnotation> unevaluatedProperties = instanceLocationAnnotations.stream()
+ .filter(a -> "unevaluatedProperties".equals(a.getKeyword())).filter(adjacentEvaluationPathFilter)
+ .filter(validEvaluationPathFilter).collect(Collectors.toList());
+ for (JsonNodeAnnotation annotation : unevaluatedProperties) {
+ if (annotation.getValue() instanceof Set) {
+ Set<String> p = annotation.getValue();
+ existingEvaluatedProperties.addAll(p);
+ }
+ }
+
+ Set<ValidationMessage> messages = new LinkedHashSet<>();
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ executionContext.setFailFast(false);
+ for (Iterator<String> 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"
+ messages.add(message().instanceNode(node).instanceLocation(instanceLocation).property(fieldName)
+ .arguments(fieldName).locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).build());
+ } else {
+ // Schema errors will be reported as is
+ messages.addAll(this.schema.validate(executionContext, node.get(fieldName), node,
+ instanceLocation.append(fieldName)));
+ }
+ }
+ }
+ } finally {
+ executionContext.setFailFast(failFast); // restore flag
+ }
+ executionContext.getAnnotations()
+ .put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation).evaluationPath(this.evaluationPath)
+ .schemaLocation(this.schemaLocation).keyword(getKeyword()).value(evaluatedProperties).build());
+
+ return messages == null || messages.isEmpty() ? Collections.emptySet() : messages;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java
new file mode 100644
index 0000000..c051ea3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/UnionTypeValidator.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@link JsonValidator} for type union.
+ */
+public class UnionTypeValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(UnionTypeValidator.class);
+
+ private final List<JsonValidator> schemas = new ArrayList<>();
+ private final String error;
+
+ public UnionTypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE, 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(validationContext.newSchema(schemaLocation.append(ValidatorTypeCode.TYPE.getValue()),
+ evaluationPath.append(ValidatorTypeCode.TRUE.getValue()), n, parentSchema));
+ else
+ schemas.add(new TypeValidator(schemaLocation.append(i), evaluationPath.append(i), n, parentSchema, validationContext));
+
+ i++;
+ }
+
+ errorBuilder.append(']');
+
+ error = errorBuilder.toString();
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig());
+
+ boolean valid = false;
+
+ // Save flag as nested schema evaluation shouldn't trigger fail fast
+ boolean failFast = executionContext.isFailFast();
+ try {
+ executionContext.setFailFast(false);
+ for (JsonValidator schema : schemas) {
+ Set<ValidationMessage> errors = schema.validate(executionContext, node, rootNode, instanceLocation);
+ if (errors == null || errors.isEmpty()) {
+ valid = true;
+ break;
+ }
+ }
+ } finally {
+ // Restore flag
+ executionContext.setFailFast(failFast);
+ }
+
+ if (!valid) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .type("type")
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).arguments(nodeType.toString(), error)
+ .build());
+ }
+
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void preloadJsonSchema() {
+ for (final JsonValidator validator : schemas) {
+ validator.preloadJsonSchema();
+ }
+ }
+
+ @Override
+ public String getKeyword() {
+ return "type";
+ }
+}
diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
new file mode 100644
index 0000000..a061b97
--- /dev/null
+++ b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * {@link JsonValidator} for uniqueItems.
+ */
+public class UniqueItemsValidator extends BaseJsonValidator implements JsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(UniqueItemsValidator.class);
+
+ private final boolean unique;
+
+ public UniqueItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS, validationContext);
+ if (schemaNode.isBoolean()) {
+ unique = schemaNode.booleanValue();
+ } else {
+ unique = false;
+ }
+ }
+
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+
+ if (unique) {
+ Set<JsonNode> set = new HashSet<JsonNode>();
+ for (JsonNode n : node) {
+ if (!set.add(n)) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).build());
+ }
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/UnknownKeywordFactory.java b/src/main/java/com/networknt/schema/UnknownKeywordFactory.java
new file mode 100644
index 0000000..2d0d486
--- /dev/null
+++ b/src/main/java/com/networknt/schema/UnknownKeywordFactory.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;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unknown keyword factory.
+ * <p>
+ * This treats unknown keywords as annotations.
+ */
+public class UnknownKeywordFactory implements KeywordFactory {
+ private static final Logger logger = LoggerFactory.getLogger(UnknownKeywordFactory.class);
+
+ private final Map<String, Keyword> keywords = new ConcurrentHashMap<>();
+
+ @Override
+ public Keyword getKeyword(String value, ValidationContext validationContext) {
+ 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 UnknownKeywordFactory INSTANCE = new UnknownKeywordFactory();
+ }
+
+ public static UnknownKeywordFactory getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ValidationContext.java b/src/main/java/com/networknt/schema/ValidationContext.java
new file mode 100644
index 0000000..8572a9a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidationContext.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 java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public class ValidationContext {
+ private final JsonMetaSchema metaSchema;
+ private final JsonSchemaFactory jsonSchemaFactory;
+ private final SchemaValidatorsConfig config;
+ private final ConcurrentMap<String, JsonSchema> schemaReferences;
+ private final ConcurrentMap<String, JsonSchema> schemaResources;
+ private final ConcurrentMap<String, JsonSchema> dynamicAnchors;
+
+ public ValidationContext(JsonMetaSchema metaSchema,
+ JsonSchemaFactory jsonSchemaFactory, SchemaValidatorsConfig config) {
+ this(metaSchema, jsonSchemaFactory, config, new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>());
+ }
+
+ public ValidationContext(JsonMetaSchema metaSchema, JsonSchemaFactory jsonSchemaFactory,
+ SchemaValidatorsConfig config, ConcurrentMap<String, JsonSchema> schemaReferences,
+ ConcurrentMap<String, JsonSchema> schemaResources, ConcurrentMap<String, JsonSchema> dynamicAnchors) {
+ if (metaSchema == null) {
+ throw new IllegalArgumentException("JsonMetaSchema must not be null");
+ }
+ if (jsonSchemaFactory == null) {
+ throw new IllegalArgumentException("JsonSchemaFactory must not be null");
+ }
+ this.metaSchema = metaSchema;
+ this.jsonSchemaFactory = jsonSchemaFactory;
+ this.config = config == null ? new SchemaValidatorsConfig() : config;
+ this.schemaReferences = schemaReferences;
+ this.schemaResources = schemaResources;
+ this.dynamicAnchors = dynamicAnchors;
+ }
+
+ public JsonSchema newSchema(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema) {
+ return getJsonSchemaFactory().create(this, schemaLocation, evaluationPath, schemaNode, parentSchema);
+ }
+
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
+ String keyword /* keyword */, JsonNode schemaNode, JsonSchema parentSchema) {
+ return this.metaSchema.newValidator(this, schemaLocation, evaluationPath, keyword, schemaNode, parentSchema);
+ }
+
+ public String resolveSchemaId(JsonNode schemaNode) {
+ return this.metaSchema.readId(schemaNode);
+ }
+
+ public JsonSchemaFactory getJsonSchemaFactory() {
+ return this.jsonSchemaFactory;
+ }
+
+ public SchemaValidatorsConfig getConfig() {
+ return this.config;
+ }
+
+ /**
+ * Gets the schema references identified by the ref uri.
+ *
+ * @return the schema references
+ */
+ public ConcurrentMap<String, JsonSchema> getSchemaReferences() {
+ return this.schemaReferences;
+ }
+
+ /**
+ * Gets the schema resources identified by id.
+ *
+ * @return the schema resources
+ */
+ public ConcurrentMap<String, JsonSchema> getSchemaResources() {
+ return this.schemaResources;
+ }
+
+ /**
+ * Gets the dynamic anchors.
+ *
+ * @return the dynamic anchors
+ */
+ public ConcurrentMap<String, JsonSchema> getDynamicAnchors() {
+ return this.dynamicAnchors;
+ }
+
+ public JsonMetaSchema getMetaSchema() {
+ return this.metaSchema;
+ }
+
+ public Optional<VersionFlag> activeDialect() {
+ String metaSchema = getMetaSchema().getIri();
+ return SpecVersionDetector.detectOptionalVersion(metaSchema);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ValidationMessage.java b/src/main/java/com/networknt/schema/ValidationMessage.java
new file mode 100644
index 0000000..4185094
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidationMessage.java
@@ -0,0 +1,440 @@
+/*
+ * 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.utils.CachingSupplier;
+import com.networknt.schema.utils.StringUtils;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * The output format.
+ *
+ * @see <a href=
+ * "https://github.com/json-schema-org/json-schema-spec/blob/main/jsonschema-validation-output-machines.md">JSON
+ * Schema</a>
+ */
+@JsonIgnoreProperties({ "messageSupplier", "schemaNode", "instanceNode", "valid" })
+@JsonPropertyOrder({ "type", "code", "message", "instanceLocation", "property", "evaluationPath", "schemaLocation",
+ "messageKey", "arguments", "details" })
+@JsonInclude(Include.NON_NULL)
+public class ValidationMessage {
+ private final String type;
+ private final String code;
+ @JsonSerialize(using = ToStringSerializer.class)
+ private final JsonNodePath evaluationPath;
+ @JsonSerialize(using = ToStringSerializer.class)
+ private final SchemaLocation schemaLocation;
+ @JsonSerialize(using = ToStringSerializer.class)
+ private final JsonNodePath instanceLocation;
+ private final String property;
+ private final Object[] arguments;
+ private final String messageKey;
+ private final Supplier<String> messageSupplier;
+ private final Map<String, Object> details;
+ private final JsonNode instanceNode;
+ private final JsonNode schemaNode;
+
+ ValidationMessage(String type, String code, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
+ JsonNodePath instanceLocation, String property, Object[] arguments, Map<String, Object> details,
+ String messageKey, Supplier<String> messageSupplier, JsonNode instanceNode, JsonNode schemaNode) {
+ super();
+ this.type = type;
+ this.code = code;
+ this.instanceLocation = instanceLocation;
+ this.schemaLocation = schemaLocation;
+ this.evaluationPath = evaluationPath;
+ this.property = property;
+ this.arguments = arguments;
+ this.details = details;
+ this.messageKey = messageKey;
+ this.messageSupplier = messageSupplier;
+ this.instanceNode = instanceNode;
+ this.schemaNode = schemaNode;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * The instance location is the location of the JSON value within the root
+ * instance being validated.
+ *
+ * @return The path to the input json
+ */
+ public JsonNodePath 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 JsonNodePath 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.
+ * <p>
+ * This corresponds with the instance location.
+ *
+ * @return the instance node
+ */
+ public JsonNode getInstanceNode() {
+ return instanceNode;
+ }
+
+ /**
+ * Returns the schema node which was evaluated.
+ * <p>
+ * This corresponds with the schema location.
+ *
+ * @return the schema node
+ */
+ public JsonNode getSchemaNode() {
+ return schemaNode;
+ }
+
+ /**
+ * Returns the property with the error.
+ * <p>
+ * 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() {
+ return property;
+ }
+
+ public Object[] getArguments() {
+ return arguments;
+ }
+
+ public Map<String, Object> getDetails() {
+ return details;
+ }
+
+ public String getMessage() {
+ return messageSupplier.get();
+ }
+
+ public String getMessageKey() {
+ return messageKey;
+ }
+
+ public boolean isValid() {
+ return messageSupplier != null;
+ }
+
+ @Override
+ public String toString() {
+ return messageSupplier.get();
+ }
+
+ @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 (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;
+ if (!Arrays.equals(arguments, that.arguments)) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (code != null ? code.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 getType() {
+ return type;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends BuilderSupport<Builder> {
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
+
+ public static abstract class BuilderSupport<S> {
+ public abstract S self();
+
+ protected String type;
+ protected String code;
+ protected JsonNodePath evaluationPath;
+ protected SchemaLocation schemaLocation;
+ protected JsonNodePath instanceLocation;
+ protected String property;
+ protected Object[] arguments;
+ protected Map<String, Object> details;
+ protected MessageFormat format;
+ protected String message;
+ protected Supplier<String> messageSupplier;
+ protected MessageFormatter messageFormatter;
+ protected String messageKey;
+ protected JsonNode instanceNode;
+ protected JsonNode schemaNode;
+
+ public S type(String type) {
+ this.type = type;
+ return self();
+ }
+
+ public S code(String code) {
+ this.code = code;
+ 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(JsonNodePath 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(JsonNodePath evaluationPath) {
+ this.evaluationPath = evaluationPath;
+ return self();
+ }
+
+ public S property(String property) {
+ this.property = property;
+ return self();
+ }
+
+ public S arguments(Object... arguments) {
+ this.arguments = arguments;
+ return self();
+ }
+
+ public S details(Map<String, Object> details) {
+ this.details = details;
+ return self();
+ }
+
+ public S format(MessageFormat format) {
+ this.format = format;
+ return self();
+ }
+
+ @Deprecated
+ public S customMessage(String message) {
+ return message(message);
+ }
+
+ /**
+ * Explicitly sets the message pattern to be used.
+ * <p>
+ * 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<String> 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 ValidationMessage build() {
+ Supplier<String> messageSupplier = this.messageSupplier;
+ String messageKey = this.messageKey;
+
+ if (StringUtils.isNotBlank(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 ValidationMessage(type, code, evaluationPath, schemaLocation, instanceLocation,
+ property, arguments, details, messageKey, messageSupplier, this.instanceNode, this.schemaNode);
+ }
+
+ protected Object[] getMessageArguments() {
+ Object[] objs = new Object[(arguments == null ? 0 : arguments.length) + 1];
+ objs[0] = instanceLocation;
+ if (arguments != null) {
+ for (int i = 1; i < objs.length; i++) {
+ objs[i] = arguments[i - 1];
+ }
+ }
+ return objs;
+ }
+
+ protected String getType() {
+ return type;
+ }
+
+ protected String getCode() {
+ return code;
+ }
+
+ protected JsonNodePath getEvaluationPath() {
+ return evaluationPath;
+ }
+
+ protected SchemaLocation getSchemaLocation() {
+ return schemaLocation;
+ }
+
+ protected JsonNodePath getInstanceLocation() {
+ return instanceLocation;
+ }
+
+ protected String getProperty() {
+ return property;
+ }
+
+ protected Object[] getArguments() {
+ return arguments;
+ }
+
+ protected Map<String, Object> getDetails() {
+ return details;
+ }
+
+ protected MessageFormat getFormat() {
+ return format;
+ }
+
+ protected String getMessage() {
+ return message;
+ }
+
+ protected Supplier<String> getMessageSupplier() {
+ return messageSupplier;
+ }
+
+ protected MessageFormatter getMessageFormatter() {
+ return messageFormatter;
+ }
+
+ protected String getMessageKey() {
+ return messageKey;
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ValidationMessageHandler.java b/src/main/java/com/networknt/schema/ValidationMessageHandler.java
new file mode 100644
index 0000000..1bacaff
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidationMessageHandler.java
@@ -0,0 +1,151 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.i18n.MessageSource;
+import com.networknt.schema.utils.StringUtils;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public abstract class ValidationMessageHandler {
+ protected final MessageSource messageSource;
+ protected ErrorMessageType errorMessageType;
+
+ protected SchemaLocation schemaLocation;
+ protected JsonNodePath evaluationPath;
+ protected JsonSchema evaluationParentSchema;
+
+ protected JsonSchema parentSchema;
+
+ protected boolean customErrorMessagesEnabled;
+ protected Map<String, String> errorMessage;
+
+ protected Keyword keyword;
+
+ protected ValidationMessageHandler(ErrorMessageType errorMessageType, boolean customErrorMessagesEnabled,
+ MessageSource messageSource, Keyword keyword, JsonSchema parentSchema, SchemaLocation schemaLocation,
+ JsonNodePath evaluationPath) {
+ this.errorMessageType = errorMessageType;
+ this.messageSource = messageSource;
+ this.schemaLocation = Objects.requireNonNull(schemaLocation);
+ this.evaluationPath = Objects.requireNonNull(evaluationPath);
+ this.parentSchema = parentSchema;
+ this.customErrorMessagesEnabled = customErrorMessagesEnabled;
+ updateKeyword(keyword);
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param copy to copy from
+ */
+ protected ValidationMessageHandler(ValidationMessageHandler copy) {
+ this.messageSource = copy.messageSource;
+ this.errorMessageType = copy.errorMessageType;
+ this.schemaLocation = copy.schemaLocation;
+ this.evaluationPath = copy.evaluationPath;
+ this.parentSchema = copy.parentSchema;
+ this.evaluationParentSchema = copy.evaluationParentSchema;
+ this.customErrorMessagesEnabled = copy.customErrorMessagesEnabled;
+ this.errorMessage = copy.errorMessage;
+ this.keyword = copy.keyword;
+ }
+
+ protected MessageSourceValidationMessage.Builder message() {
+ return MessageSourceValidationMessage.builder(this.messageSource, this.errorMessage, (message, failFast) -> {
+ if (failFast) {
+ throw new FailFastAssertionException(message);
+ }
+ }).code(getErrorMessageType().getErrorCode()).schemaLocation(this.schemaLocation)
+ .evaluationPath(this.evaluationPath).type(this.keyword != null ? this.keyword.getValue() : null)
+ .messageKey(getErrorMessageType().getErrorCodeValue());
+ }
+
+ protected ErrorMessageType getErrorMessageType() {
+ return this.errorMessageType;
+ }
+
+ protected void parseErrorCode(String errorCodeKey) {
+ if (errorCodeKey != null && this.parentSchema != null) {
+ JsonNode errorCodeNode = this.parentSchema.getSchemaNode().get(errorCodeKey);
+ if (errorCodeNode != null && errorCodeNode.isTextual()) {
+ String errorCodeText = errorCodeNode.asText();
+ if (StringUtils.isNotBlank(errorCodeText)) {
+ this.errorMessageType = CustomErrorMessageType.of(errorCodeText);
+ }
+ }
+ }
+ }
+
+ protected void updateValidatorType(ValidatorTypeCode validatorTypeCode) {
+ updateKeyword(validatorTypeCode);
+ updateErrorMessageType(validatorTypeCode);
+ }
+
+ protected void updateErrorMessageType(ErrorMessageType errorMessageType) {
+ this.errorMessageType = errorMessageType;
+ }
+
+ protected void updateKeyword(Keyword keyword) {
+ this.keyword = keyword;
+ if (this.keyword != null) {
+ if (this.customErrorMessagesEnabled && keyword != null && parentSchema != null) {
+ this.errorMessage = getErrorMessage(parentSchema.getSchemaNode(), keyword.getValue());
+ }
+ parseErrorCode(getErrorCodeKey(keyword.getValue()));
+ }
+ }
+
+ /**
+ * Gets the custom error message to use.
+ *
+ * @param schemaNode the schema node
+ * @param keyword the keyword
+ * @return the custom error message
+ */
+ protected Map<String, String> getErrorMessage(JsonNode schemaNode, String keyword) {
+ final JsonSchema parentSchema = this.parentSchema;
+ final JsonNode message = getMessageNode(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<String, String> result = new LinkedHashMap<>();
+ messageNode.fields().forEachRemaining(entry -> {
+ result.put(entry.getKey(), entry.getValue().textValue());
+ });
+ if (!result.isEmpty()) {
+ return result;
+ }
+ }
+ }
+ }
+ return Collections.emptyMap();
+ }
+
+ protected JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema, String pname) {
+ if (schemaNode.get("message") != null && schemaNode.get("message").get(pname) != null) {
+ return schemaNode.get("message");
+ }
+ JsonNode messageNode;
+ messageNode = schemaNode.get("message");
+ if (messageNode == null && parentSchema != null) {
+ messageNode = parentSchema.schemaNode.get("message");
+ if (messageNode == null) {
+ return getMessageNode(parentSchema.schemaNode, parentSchema.getParentSchema(), pname);
+ }
+ }
+ return messageNode;
+ }
+
+ protected String getErrorCodeKey(String keyword) {
+ if (keyword != null) {
+ return keyword + "ErrorCode";
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ValidationResult.java b/src/main/java/com/networknt/schema/ValidationResult.java
new file mode 100644
index 0000000..7c04196
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidationResult.java
@@ -0,0 +1,43 @@
+/*
+ * 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<ValidationMessage> validationMessages;
+
+ private ExecutionContext executionContext;
+
+ public ValidationResult(Set<ValidationMessage> validationMessages, ExecutionContext executionContext) {
+ super();
+ this.validationMessages = validationMessages;
+ this.executionContext = executionContext;
+ }
+
+ public Set<ValidationMessage> getValidationMessages() {
+ return validationMessages;
+ }
+
+ public ExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ public CollectorContext getCollectorContext() {
+ return getExecutionContext().getCollectorContext();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java
new file mode 100644
index 0000000..b522bd8
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidatorState.java
@@ -0,0 +1,91 @@
+/*
+ * 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 {
+ /**
+ * 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;
+
+ /**
+ * Constructor for validation state.
+ */
+ public ValidatorState() {
+ }
+
+ /**
+ * Constructor for validation state.
+ *
+ * @param walkEnabled whether walk is enabled
+ * @param validationEnabled whether validation is enabled
+ */
+ public ValidatorState(boolean walkEnabled, boolean validationEnabled) {
+ this.isWalkEnabled = walkEnabled;
+ this.isValidationEnabled = validationEnabled;
+ }
+
+ 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
new file mode 100644
index 0000000..dda56d1
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidatorTypeCode.java
@@ -0,0 +1,190 @@
+/*
+ * 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.SpecVersion.VersionFlag;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@FunctionalInterface
+interface ValidatorFactory {
+ JsonValidator newInstance(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext);
+}
+
+enum VersionCode {
+ None(new SpecVersion.VersionFlag[] { }),
+ AllVersions(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V4, SpecVersion.VersionFlag.V6, SpecVersion.VersionFlag.V7, SpecVersion.VersionFlag.V201909, SpecVersion.VersionFlag.V202012 }),
+ MinV6(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V6, SpecVersion.VersionFlag.V7, SpecVersion.VersionFlag.V201909, SpecVersion.VersionFlag.V202012 }),
+ MinV6MaxV7(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V6, SpecVersion.VersionFlag.V7 }),
+ MinV7(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V7, SpecVersion.VersionFlag.V201909, SpecVersion.VersionFlag.V202012 }),
+ MaxV7(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V4, SpecVersion.VersionFlag.V6, SpecVersion.VersionFlag.V7 }),
+ MaxV201909(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V4, SpecVersion.VersionFlag.V6, SpecVersion.VersionFlag.V7, SpecVersion.VersionFlag.V201909 }),
+ MinV201909(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V201909, SpecVersion.VersionFlag.V202012 }),
+ MinV202012(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V202012 }),
+ V201909(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V201909 }),
+ V7(new SpecVersion.VersionFlag[] { SpecVersion.VersionFlag.V7 });
+
+ private final EnumSet<VersionFlag> versions;
+
+ VersionCode(SpecVersion.VersionFlag[] versionFlags) {
+ this.versions = EnumSet.noneOf(VersionFlag.class);
+ for (VersionFlag flag: versionFlags) {
+ this.versions.add(flag);
+ }
+ }
+
+ EnumSet<VersionFlag> getVersions() {
+ return this.versions;
+ }
+}
+
+public enum ValidatorTypeCode implements Keyword, ErrorMessageType {
+ ADDITIONAL_PROPERTIES("additionalProperties", "1001", AdditionalPropertiesValidator::new, VersionCode.MaxV7),
+ ALL_OF("allOf", "1002", AllOfValidator::new, VersionCode.MaxV7),
+ ANY_OF("anyOf", "1003", AnyOfValidator::new, VersionCode.MaxV7),
+ CONST("const", "1042", ConstValidator::new, VersionCode.MinV6MaxV7),
+ CONTAINS("contains", "1043", ContainsValidator::new, VersionCode.MinV6MaxV7),
+ CONTENT_ENCODING("contentEncoding", "1052", ContentEncodingValidator::new, VersionCode.V7),
+ CONTENT_MEDIA_TYPE("contentMediaType", "1053", ContentMediaTypeValidator::new, VersionCode.V7),
+ DEPENDENCIES("dependencies", "1007", DependenciesValidator::new, VersionCode.AllVersions),
+ DEPENDENT_REQUIRED("dependentRequired", "1045", DependentRequired::new, VersionCode.None),
+ DEPENDENT_SCHEMAS("dependentSchemas", "1046", DependentSchemas::new, VersionCode.None),
+ DISCRIMINATOR("discriminator", "2001", DiscriminatorValidator::new, VersionCode.None),
+ DYNAMIC_REF("$dynamicRef", "1051", DynamicRefValidator::new, VersionCode.None),
+ ENUM("enum", "1008", EnumValidator::new, VersionCode.MaxV7),
+ EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", ExclusiveMaximumValidator::new, VersionCode.MinV6MaxV7),
+ EXCLUSIVE_MINIMUM("exclusiveMinimum", "1039", ExclusiveMinimumValidator::new, VersionCode.MinV6MaxV7),
+ FALSE("false", "1041", FalseValidator::new, VersionCode.MinV6),
+ FORMAT("format", "1009", null, VersionCode.MaxV7) {
+ @Override public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ throw new UnsupportedOperationException("Use FormatKeyword instead");
+ }
+ },
+ ID("id", "1036", null, VersionCode.AllVersions),
+ IF_THEN_ELSE("if", "1037", IfValidator::new, VersionCode.V7),
+ ITEMS_202012("items", "1010", ItemsValidator202012::new, VersionCode.None),
+ ITEMS("items", "1010", ItemsValidator::new, VersionCode.MaxV7),
+ MAX_CONTAINS("maxContains", "1006", MinMaxContainsValidator::new, VersionCode.None),
+ MAX_ITEMS("maxItems", "1012", MaxItemsValidator::new, VersionCode.MaxV7),
+ MAX_LENGTH("maxLength", "1013", MaxLengthValidator::new, VersionCode.MaxV7),
+ MAX_PROPERTIES("maxProperties", "1014", MaxPropertiesValidator::new, VersionCode.MaxV7),
+ MAXIMUM("maximum", "1011", MaximumValidator::new, VersionCode.MaxV7),
+ MIN_CONTAINS("minContains", "1049", MinMaxContainsValidator::new, VersionCode.None),
+ MIN_ITEMS("minItems", "1016", MinItemsValidator::new, VersionCode.MaxV7),
+ MIN_LENGTH("minLength", "1017", MinLengthValidator::new, VersionCode.MaxV7),
+ MIN_PROPERTIES("minProperties", "1018", MinPropertiesValidator::new, VersionCode.MaxV7),
+ MINIMUM("minimum", "1015", MinimumValidator::new, VersionCode.MaxV7),
+ MULTIPLE_OF("multipleOf", "1019", MultipleOfValidator::new, VersionCode.MaxV7),
+ NOT_ALLOWED("notAllowed", "1033", NotAllowedValidator::new, VersionCode.AllVersions),
+ NOT("not", "1020", NotValidator::new, VersionCode.MaxV7),
+ ONE_OF("oneOf", "1022", OneOfValidator::new, VersionCode.MaxV7),
+ PATTERN_PROPERTIES("patternProperties", "1024", PatternPropertiesValidator::new, VersionCode.MaxV7),
+ PATTERN("pattern", "1023", PatternValidator::new, VersionCode.MaxV7),
+ PREFIX_ITEMS("prefixItems", "1048", PrefixItemsValidator::new, VersionCode.None),
+ PROPERTIES("properties", "1025", PropertiesValidator::new, VersionCode.MaxV7),
+ PROPERTYNAMES("propertyNames", "1044", PropertyNamesValidator::new, VersionCode.MinV6MaxV7),
+ READ_ONLY("readOnly", "1032", ReadOnlyValidator::new, VersionCode.V7),
+ RECURSIVE_REF("$recursiveRef", "1050", RecursiveRefValidator::new, VersionCode.None),
+ REF("$ref", "1026", RefValidator::new, VersionCode.MaxV7),
+ REQUIRED("required", "1028", RequiredValidator::new, VersionCode.MaxV7),
+ TRUE("true", "1040", TrueValidator::new, VersionCode.MinV6),
+ TYPE("type", "1029", TypeValidator::new, VersionCode.MaxV7),
+ UNEVALUATED_ITEMS("unevaluatedItems", "1021", UnevaluatedItemsValidator::new, VersionCode.None),
+ UNEVALUATED_PROPERTIES("unevaluatedProperties","1047",UnevaluatedPropertiesValidator::new,VersionCode.None),
+ UNION_TYPE("unionType", "1030", UnionTypeValidator::new, VersionCode.None),
+ UNIQUE_ITEMS("uniqueItems", "1031", UniqueItemsValidator::new, VersionCode.MaxV7),
+ WRITE_ONLY("writeOnly", "1027", WriteOnlyValidator::new, VersionCode.V7),
+ ;
+
+ private static final Map<String, ValidatorTypeCode> CONSTANTS = new HashMap<>();
+
+ static {
+ for (ValidatorTypeCode c : values()) {
+ CONSTANTS.put(c.value, c);
+ }
+ }
+
+ private final String value;
+ private final String errorCode;
+ private final ValidatorFactory validatorFactory;
+ private final VersionCode versionCode;
+
+ private ValidatorTypeCode(String value, String errorCode, ValidatorFactory validatorFactory, VersionCode versionCode) {
+ this.value = value;
+ this.errorCode = errorCode;
+ this.validatorFactory = validatorFactory;
+ this.versionCode = versionCode;
+ }
+
+ public static List<ValidatorTypeCode> getKeywords(SpecVersion.VersionFlag versionFlag) {
+ final List<ValidatorTypeCode> result = new ArrayList<>();
+ for (ValidatorTypeCode keyword : values()) {
+ if (keyword.getVersionCode().getVersions().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);
+ }
+ return constant;
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
+ if (this.validatorFactory == null) {
+ throw new UnsupportedOperationException("No suitable validator for " + getValue());
+ }
+ return validatorFactory.newInstance(schemaLocation, evaluationPath, schemaNode, parentSchema,
+ validationContext);
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public String getValue() {
+ return this.value;
+ }
+
+ @Override
+ public String getErrorCode() {
+ return this.errorCode;
+ }
+
+ public VersionCode getVersionCode() {
+ return this.versionCode;
+ }
+
+ @Override
+ public String getErrorCodeValue() {
+ return getValue();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Version201909.java b/src/main/java/com/networknt/schema/Version201909.java
new file mode 100644
index 0000000..9a9efed
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Version201909.java
@@ -0,0 +1,47 @@
+package com.networknt.schema;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Draft 2019-09 dialect.
+ */
+public class Version201909 implements JsonSchemaVersion {
+ private static final String IRI = SchemaId.V201909;
+ private static final String ID = "$id";
+ private static final Map<String, Boolean> VOCABULARY;
+
+ static {
+ Map<String, Boolean> 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 JsonMetaSchema INSTANCE;
+ static {
+ INSTANCE = JsonMetaSchema.builder(IRI)
+ .specification(SpecVersion.VersionFlag.V201909)
+ .idKeyword(ID)
+ .formats(Formats.DEFAULT)
+ .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V201909))
+ // keywords that may validly exist, but have no validation aspect to them
+ .keywords(Arrays.asList(
+ new NonValidationKeyword("definitions")
+ ))
+ .vocabularies(VOCABULARY)
+ .build();
+ }
+ }
+
+ @Override
+ public JsonMetaSchema getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Version202012.java b/src/main/java/com/networknt/schema/Version202012.java
new file mode 100644
index 0000000..6e75cc6
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Version202012.java
@@ -0,0 +1,48 @@
+package com.networknt.schema;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Draft 2020-12 dialect.
+ */
+public class Version202012 implements JsonSchemaVersion {
+ private static final String IRI = SchemaId.V202012;
+ private static final String ID = "$id";
+ private static final Map<String, Boolean> VOCABULARY;
+
+ static {
+ Map<String, Boolean> 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 JsonMetaSchema INSTANCE;
+ static {
+ INSTANCE = JsonMetaSchema.builder(IRI)
+ .specification(SpecVersion.VersionFlag.V202012)
+ .idKeyword(ID)
+ .formats(Formats.DEFAULT)
+ .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V202012))
+ // keywords that may validly exist, but have no validation aspect to them
+ .keywords(Arrays.asList(
+ new NonValidationKeyword("definitions")
+ ))
+ .vocabularies(VOCABULARY)
+ .build();
+ }
+ }
+
+ @Override
+ public JsonMetaSchema getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Version4.java b/src/main/java/com/networknt/schema/Version4.java
new file mode 100644
index 0000000..2640626
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Version4.java
@@ -0,0 +1,38 @@
+package com.networknt.schema;
+
+import java.util.Arrays;
+
+/**
+ * Draft 4 dialect.
+ */
+public class Version4 implements JsonSchemaVersion {
+ private static final String IRI = SchemaId.V4;
+ private static final String ID = "id";
+
+ private static class Holder {
+ private static final JsonMetaSchema INSTANCE;
+ static {
+ INSTANCE = JsonMetaSchema.builder(IRI)
+ .specification(SpecVersion.VersionFlag.V4)
+ .idKeyword(ID)
+ .formats(Formats.DEFAULT)
+ .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V4))
+ // 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")
+ ))
+ .build();
+ }
+ }
+
+ public JsonMetaSchema getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Version6.java b/src/main/java/com/networknt/schema/Version6.java
new file mode 100644
index 0000000..3b30822
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Version6.java
@@ -0,0 +1,39 @@
+package com.networknt.schema;
+
+import java.util.Arrays;
+
+/**
+ * Draft 6 dialect.
+ */
+public class Version6 implements JsonSchemaVersion {
+ private static final String IRI = SchemaId.V6;
+ // Draft 6 uses "$id"
+ private static final String ID = "$id";
+
+ private static class Holder {
+ private static final JsonMetaSchema INSTANCE;
+ static {
+ INSTANCE = JsonMetaSchema.builder(IRI)
+ .specification(SpecVersion.VersionFlag.V6)
+ .idKeyword(ID)
+ .formats(Formats.DEFAULT)
+ .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V6))
+ // 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 JsonMetaSchema getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Version7.java b/src/main/java/com/networknt/schema/Version7.java
new file mode 100644
index 0000000..de8d2d7
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Version7.java
@@ -0,0 +1,41 @@
+package com.networknt.schema;
+
+import java.util.Arrays;
+
+/**
+ * Draft 7 dialect.
+ */
+public class Version7 implements JsonSchemaVersion {
+ private static final String IRI = SchemaId.V7;
+ private static final String ID = "$id";
+
+ private static class Holder {
+ private static final JsonMetaSchema INSTANCE;
+ static {
+ INSTANCE = JsonMetaSchema.builder(IRI)
+ .specification(SpecVersion.VersionFlag.V7)
+ .idKeyword(ID)
+ .formats(Formats.DEFAULT)
+ .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V7))
+ // 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();
+ }
+ }
+
+ @Override
+ public JsonMetaSchema getInstance() {
+ return Holder.INSTANCE;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/Vocabularies.java b/src/main/java/com/networknt/schema/Vocabularies.java
new file mode 100644
index 0000000..9e02e93
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Vocabularies.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 java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Vocabularies.
+ */
+public class Vocabularies {
+ private static final Map<String, Vocabulary> VALUES;
+
+ static {
+ Map<String, Vocabulary> mapping = new HashMap<>();
+ mapping.put(Vocabulary.V201909_CORE.getIri(), Vocabulary.V201909_CORE);
+ mapping.put(Vocabulary.V201909_APPLICATOR.getIri(), Vocabulary.V201909_APPLICATOR);
+ mapping.put(Vocabulary.V201909_VALIDATION.getIri(), Vocabulary.V201909_VALIDATION);
+ mapping.put(Vocabulary.V201909_META_DATA.getIri(), Vocabulary.V201909_META_DATA);
+ mapping.put(Vocabulary.V201909_FORMAT.getIri(), Vocabulary.V201909_FORMAT);
+ mapping.put(Vocabulary.V201909_CONTENT.getIri(), Vocabulary.V201909_CONTENT);
+
+ mapping.put(Vocabulary.V202012_CORE.getIri(), Vocabulary.V202012_CORE);
+ mapping.put(Vocabulary.V202012_APPLICATOR.getIri(), Vocabulary.V202012_APPLICATOR);
+ mapping.put(Vocabulary.V202012_UNEVALUATED.getIri(), Vocabulary.V202012_UNEVALUATED);
+ mapping.put(Vocabulary.V202012_VALIDATION.getIri(), Vocabulary.V202012_VALIDATION);
+ mapping.put(Vocabulary.V202012_META_DATA.getIri(), Vocabulary.V202012_META_DATA);
+ mapping.put(Vocabulary.V202012_FORMAT_ANNOTATION.getIri(), Vocabulary.V202012_FORMAT_ANNOTATION);
+ mapping.put(Vocabulary.V202012_FORMAT_ASSERTION.getIri(), Vocabulary.V202012_FORMAT_ASSERTION);
+ mapping.put(Vocabulary.V202012_CONTENT.getIri(), Vocabulary.V202012_CONTENT);
+
+ 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.java b/src/main/java/com/networknt/schema/Vocabulary.java
new file mode 100644
index 0000000..fd73958
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Vocabulary.java
@@ -0,0 +1,155 @@
+/*
+ * 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.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represents a vocabulary in meta-schema.
+ * <p>
+ * This contains the IRI and the keywords in the vocabulary.
+ */
+public class Vocabulary {
+
+ // 2019-09
+ public static final Vocabulary V201909_CORE = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/core",
+ new NonValidationKeyword("$id"), new NonValidationKeyword("$schema"), new NonValidationKeyword("$anchor"),
+ ValidatorTypeCode.REF, ValidatorTypeCode.RECURSIVE_REF, new NonValidationKeyword("$recursiveAnchor"),
+ new NonValidationKeyword("$vocabulary"), new NonValidationKeyword("$comment"),
+ new NonValidationKeyword("$defs"));
+ public static final Vocabulary V201909_APPLICATOR = new Vocabulary(
+ "https://json-schema.org/draft/2019-09/vocab/applicator", new NonValidationKeyword("additionalItems"),
+ ValidatorTypeCode.UNEVALUATED_ITEMS, ValidatorTypeCode.ITEMS, ValidatorTypeCode.CONTAINS,
+ ValidatorTypeCode.ADDITIONAL_PROPERTIES, ValidatorTypeCode.UNEVALUATED_PROPERTIES,
+ ValidatorTypeCode.PROPERTIES, ValidatorTypeCode.PATTERN_PROPERTIES, ValidatorTypeCode.DEPENDENT_SCHEMAS,
+ ValidatorTypeCode.PROPERTYNAMES, ValidatorTypeCode.IF_THEN_ELSE, new NonValidationKeyword("then"),
+ new NonValidationKeyword("else"), ValidatorTypeCode.ALL_OF, ValidatorTypeCode.ANY_OF,
+ ValidatorTypeCode.ONE_OF, ValidatorTypeCode.NOT);
+ public static final Vocabulary V201909_VALIDATION = new Vocabulary(
+ "https://json-schema.org/draft/2019-09/vocab/validation", ValidatorTypeCode.MULTIPLE_OF,
+ ValidatorTypeCode.MAXIMUM, ValidatorTypeCode.EXCLUSIVE_MAXIMUM, ValidatorTypeCode.MINIMUM,
+ ValidatorTypeCode.EXCLUSIVE_MINIMUM, ValidatorTypeCode.MAX_LENGTH, ValidatorTypeCode.MIN_LENGTH,
+ ValidatorTypeCode.PATTERN, ValidatorTypeCode.MAX_ITEMS, ValidatorTypeCode.MIN_ITEMS,
+ ValidatorTypeCode.UNIQUE_ITEMS, ValidatorTypeCode.MAX_CONTAINS, ValidatorTypeCode.MIN_CONTAINS,
+ ValidatorTypeCode.MAX_PROPERTIES, ValidatorTypeCode.MIN_PROPERTIES, ValidatorTypeCode.REQUIRED,
+ ValidatorTypeCode.DEPENDENT_REQUIRED, ValidatorTypeCode.CONST, ValidatorTypeCode.ENUM,
+ ValidatorTypeCode.TYPE);
+ public static final Vocabulary V201909_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"),
+ ValidatorTypeCode.READ_ONLY, ValidatorTypeCode.WRITE_ONLY, new AnnotationKeyword("examples"));
+ public static final Vocabulary V201909_FORMAT = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/format",
+ ValidatorTypeCode.FORMAT);
+ public static final Vocabulary V201909_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 V202012_CORE = new Vocabulary("https://json-schema.org/draft/2020-12/vocab/core",
+ new NonValidationKeyword("$id"), new NonValidationKeyword("$schema"), ValidatorTypeCode.REF,
+ new NonValidationKeyword("$anchor"), ValidatorTypeCode.DYNAMIC_REF,
+ new NonValidationKeyword("$dynamicAnchor"), new NonValidationKeyword("$vocabulary"),
+ new NonValidationKeyword("$comment"), new NonValidationKeyword("$defs"));
+ public static final Vocabulary V202012_APPLICATOR = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/applicator", ValidatorTypeCode.PREFIX_ITEMS,
+ ValidatorTypeCode.ITEMS_202012, ValidatorTypeCode.CONTAINS, ValidatorTypeCode.ADDITIONAL_PROPERTIES,
+ ValidatorTypeCode.PROPERTIES, ValidatorTypeCode.PATTERN_PROPERTIES, ValidatorTypeCode.DEPENDENT_SCHEMAS,
+ ValidatorTypeCode.PROPERTYNAMES, ValidatorTypeCode.IF_THEN_ELSE, new NonValidationKeyword("then"),
+ new NonValidationKeyword("else"), ValidatorTypeCode.ALL_OF, ValidatorTypeCode.ANY_OF,
+ ValidatorTypeCode.ONE_OF, ValidatorTypeCode.NOT);
+ public static final Vocabulary V202012_UNEVALUATED = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated", ValidatorTypeCode.UNEVALUATED_ITEMS,
+ ValidatorTypeCode.UNEVALUATED_PROPERTIES);
+ public static final Vocabulary V202012_VALIDATION = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/validation", ValidatorTypeCode.TYPE, ValidatorTypeCode.CONST,
+ ValidatorTypeCode.ENUM, ValidatorTypeCode.MULTIPLE_OF, ValidatorTypeCode.MAXIMUM,
+ ValidatorTypeCode.EXCLUSIVE_MAXIMUM, ValidatorTypeCode.MINIMUM, ValidatorTypeCode.EXCLUSIVE_MINIMUM,
+ ValidatorTypeCode.MAX_LENGTH, ValidatorTypeCode.MIN_LENGTH, ValidatorTypeCode.PATTERN,
+ ValidatorTypeCode.MAX_ITEMS, ValidatorTypeCode.MIN_ITEMS, ValidatorTypeCode.UNIQUE_ITEMS,
+ ValidatorTypeCode.MAX_CONTAINS, ValidatorTypeCode.MIN_CONTAINS, ValidatorTypeCode.MAX_PROPERTIES,
+ ValidatorTypeCode.MIN_PROPERTIES, ValidatorTypeCode.REQUIRED, ValidatorTypeCode.DEPENDENT_REQUIRED);
+ public static final Vocabulary V202012_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"),
+ ValidatorTypeCode.READ_ONLY, ValidatorTypeCode.WRITE_ONLY, new AnnotationKeyword("examples"));
+ public static final Vocabulary V202012_FORMAT_ANNOTATION = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/format-annotation", ValidatorTypeCode.FORMAT);
+ public static final Vocabulary V202012_FORMAT_ASSERTION = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/format-assertion", ValidatorTypeCode.FORMAT);
+ public static final Vocabulary V202012_CONTENT = new Vocabulary(
+ "https://json-schema.org/draft/2020-12/vocab/content", new AnnotationKeyword("contentEncoding"),
+ new AnnotationKeyword("contentMediaType"), new AnnotationKeyword("contentSchema"));
+
+ private final String iri;
+ private final Set<Keyword> keywords;
+
+ /**
+ * Constructor.
+ *
+ * @param iri the iri
+ * @param keywords the keywords
+ */
+ public Vocabulary(String iri, Keyword... keywords) {
+ this.iri = iri;
+ this.keywords = new LinkedHashSet<>();
+ for (Keyword keyword : keywords) {
+ this.keywords.add(keyword);
+ }
+ }
+
+ /**
+ * The iri of the vocabulary.
+ *
+ * @return the iri
+ */
+ public String getIri() {
+ return iri;
+ }
+
+ /**
+ * The keywords in the vocabulary.
+ *
+ * @return the keywords
+ */
+ public Set<Keyword> getKeywords() {
+ return keywords;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(iri, 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(iri, other.iri) && Objects.equals(keywords, other.keywords);
+ }
+
+ @Override
+ public String toString() {
+ return "Vocabulary [iri=" + iri + ", keywords=" + keywords + "]";
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/VocabularyFactory.java b/src/main/java/com/networknt/schema/VocabularyFactory.java
new file mode 100644
index 0000000..a451c0d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/VocabularyFactory.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;
+
+/**
+ * Factory for {@link Vocabulary}.
+ */
+@FunctionalInterface
+public interface VocabularyFactory {
+ /**
+ * Gets the vocabulary given the vocabulary iri.
+ *
+ * @param iri the vocabulary iri
+ * @return the vocabulary
+ */
+ Vocabulary getVocabulary(String iri);
+}
diff --git a/src/main/java/com/networknt/schema/WriteOnlyValidator.java b/src/main/java/com/networknt/schema/WriteOnlyValidator.java
new file mode 100644
index 0000000..e13da5a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/WriteOnlyValidator.java
@@ -0,0 +1,37 @@
+package com.networknt.schema;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * {@link JsonValidator} for writeOnly.
+ */
+public class WriteOnlyValidator extends BaseJsonValidator {
+ private static final Logger logger = LoggerFactory.getLogger(WriteOnlyValidator.class);
+
+ private final boolean writeOnly;
+
+ public WriteOnlyValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.WRITE_ONLY, validationContext);
+
+ this.writeOnly = validationContext.getConfig().isWriteOnly();
+ logger.debug("Loaded WriteOnlyValidator for property {} as {}", parentSchema, "write mode");
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ debug(logger, node, rootNode, instanceLocation);
+ if (this.writeOnly) {
+ return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
+ .locale(executionContext.getExecutionConfig().getLocale())
+ .failFast(executionContext.isFailFast()).build());
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotation.java b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotation.java
new file mode 100644
index 0000000..086bc35
--- /dev/null
+++ b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotation.java
@@ -0,0 +1,164 @@
+/*
+ * 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.JsonNodePath;
+import com.networknt.schema.Keyword;
+import com.networknt.schema.SchemaLocation;
+
+/**
+ * The annotation.
+ */
+public class JsonNodeAnnotation {
+ private final String keyword;
+ private final JsonNodePath instanceLocation;
+ private final SchemaLocation schemaLocation;
+ private final JsonNodePath evaluationPath;
+ private final Object value;
+
+ public JsonNodeAnnotation(String keyword, JsonNodePath instanceLocation, SchemaLocation schemaLocation,
+ JsonNodePath 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 JsonNodePath 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 JsonNodePath getEvaluationPath() {
+ return evaluationPath;
+ }
+
+ /**
+ * The attached value(s).
+ *
+ * @param <T> the value type
+ * @return the value
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getValue() {
+ return (T) value;
+ }
+
+ @Override
+ public String toString() {
+ return "JsonNodeAnnotation [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;
+ JsonNodeAnnotation other = (JsonNodeAnnotation) 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 JsonNodePath instanceLocation;
+ private SchemaLocation schemaLocation;
+ private JsonNodePath 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(JsonNodePath instanceLocation) {
+ this.instanceLocation = instanceLocation;
+ return this;
+ }
+
+ public Builder schemaLocation(SchemaLocation schemaLocation) {
+ this.schemaLocation = schemaLocation;
+ return this;
+ }
+
+ public Builder evaluationPath(JsonNodePath evaluationPath) {
+ this.evaluationPath = evaluationPath;
+ return this;
+ }
+
+ public Builder value(Object value) {
+ this.value = value;
+ return this;
+ }
+
+ public JsonNodeAnnotation build() {
+ return new JsonNodeAnnotation(keyword, instanceLocation, schemaLocation, evaluationPath, value);
+ }
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotationPredicate.java b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotationPredicate.java
new file mode 100644
index 0000000..5f356af
--- /dev/null
+++ b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotationPredicate.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.JsonNodePath;
+import com.networknt.schema.SchemaLocation;
+
+/**
+ * A predicate for filtering annotations.
+ */
+public class JsonNodeAnnotationPredicate implements Predicate<JsonNodeAnnotation> {
+ final Predicate<JsonNodePath> instanceLocationPredicate;
+ final Predicate<JsonNodePath> evaluationPathPredicate;
+ final Predicate<SchemaLocation> schemaLocationPredicate;
+ final Predicate<String> keywordPredicate;
+ final Predicate<Object> 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 JsonNodeAnnotationPredicate(Predicate<JsonNodePath> instanceLocationPredicate,
+ Predicate<JsonNodePath> evaluationPathPredicate, Predicate<SchemaLocation> schemaLocationPredicate,
+ Predicate<String> keywordPredicate, Predicate<Object> valuePredicate) {
+ super();
+ this.instanceLocationPredicate = instanceLocationPredicate;
+ this.evaluationPathPredicate = evaluationPathPredicate;
+ this.schemaLocationPredicate = schemaLocationPredicate;
+ this.keywordPredicate = keywordPredicate;
+ this.valuePredicate = valuePredicate;
+ }
+
+ @Override
+ public boolean test(JsonNodeAnnotation 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<JsonNodePath> getInstanceLocationPredicate() {
+ return instanceLocationPredicate;
+ }
+
+ /**
+ * Gets the predicate to filter on evaluationPath.
+ *
+ * @return the predicate
+ */
+ public Predicate<JsonNodePath> getEvaluationPathPredicate() {
+ return evaluationPathPredicate;
+ }
+
+ /**
+ * Gets the predicate to filter on schemaLocation.
+ *
+ * @return the predicate
+ */
+ public Predicate<SchemaLocation> getSchemaLocationPredicate() {
+ return schemaLocationPredicate;
+ }
+
+ /**
+ * Gets the predicate to filter on keyword.
+ *
+ * @return the predicate
+ */
+ public Predicate<String> getKeywordPredicate() {
+ return keywordPredicate;
+ }
+
+ /**
+ * Gets the predicate to filter on value.
+ *
+ * @return the predicate
+ */
+ public Predicate<Object> 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 JsonNodeAnnotationPredicate}.
+ */
+ public static class Builder {
+ Predicate<JsonNodePath> instanceLocationPredicate;
+ Predicate<JsonNodePath> evaluationPathPredicate;
+ Predicate<SchemaLocation> schemaLocationPredicate;
+ Predicate<String> keywordPredicate;
+ Predicate<Object> valuePredicate;
+
+ public Builder instanceLocation(Predicate<JsonNodePath> instanceLocationPredicate) {
+ this.instanceLocationPredicate = instanceLocationPredicate;
+ return this;
+ }
+
+ public Builder evaluationPath(Predicate<JsonNodePath> evaluationPathPredicate) {
+ this.evaluationPathPredicate = evaluationPathPredicate;
+ return this;
+ }
+
+ public Builder schema(Predicate<SchemaLocation> schemaLocationPredicate) {
+ this.schemaLocationPredicate = schemaLocationPredicate;
+ return this;
+ }
+
+ public Builder keyword(Predicate<String> keywordPredicate) {
+ this.keywordPredicate = keywordPredicate;
+ return this;
+ }
+
+ public Builder value(Predicate<Object> valuePredicate) {
+ this.valuePredicate = valuePredicate;
+ return this;
+ }
+
+ public JsonNodeAnnotationPredicate build() {
+ return new JsonNodeAnnotationPredicate(instanceLocationPredicate, evaluationPathPredicate,
+ schemaLocationPredicate, keywordPredicate, valuePredicate);
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotations.java b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotations.java
new file mode 100644
index 0000000..4e4f582
--- /dev/null
+++ b/src/main/java/com/networknt/schema/annotation/JsonNodeAnnotations.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.JsonNodePath;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * The JSON Schema annotations.
+ *
+ * @see <a href=
+ * "https://github.com/json-schema-org/json-schema-spec/issues/530">Details
+ * of annotation collection</a>
+ */
+public class JsonNodeAnnotations {
+
+ /**
+ * Stores the annotations.
+ * <p>
+ * instancePath to annotation
+ */
+ private final Map<JsonNodePath, List<JsonNodeAnnotation>> values = new LinkedHashMap<>();
+
+ /**
+ * Gets the annotations.
+ * <p>
+ * instancePath to annotation
+ *
+ * @return the annotations
+ */
+ public Map<JsonNodePath, List<JsonNodeAnnotation>> asMap() {
+ return this.values;
+ }
+
+ /**
+ * Puts the annotation.
+ *
+ * @param annotation the annotation
+ */
+ public void put(JsonNodeAnnotation 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<JsonNodePath, List<JsonNodeAnnotation>> annotations) {
+ Map<String, Map<String, Map<String, Object>>> results = new LinkedHashMap<>();
+ for (List<JsonNodeAnnotation> list : annotations.values()) {
+ for (JsonNodeAnnotation annotation : list) {
+ String keyword = annotation.getKeyword();
+ String instancePath = annotation.getInstanceLocation().toString();
+ String evaluationPath = annotation.getEvaluationPath().toString();
+ Map<String, Object> 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/format/AbstractFormat.java b/src/main/java/com/networknt/schema/format/AbstractFormat.java
new file mode 100644
index 0000000..73d85e9
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/AbstractFormat.java
@@ -0,0 +1,42 @@
+/*
+ * 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.schema.ExecutionContext;
+
+/**
+ * Used for Formats that do not need to use the {@link ExecutionContext}.
+ */
+@Deprecated
+public abstract class AbstractFormat extends BaseFormat {
+ /**
+ * Constructor.
+ *
+ * @param name the name
+ * @param errorMessageDescription the error message description
+ */
+ public AbstractFormat(String name, String errorMessageDescription) {
+ super(name, errorMessageDescription);
+ }
+
+ @Override
+ public boolean matches(ExecutionContext executionContext, String value) {
+ return matches(value);
+ }
+
+ abstract public boolean matches(String value);
+}
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 0000000..0b0748b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java
@@ -0,0 +1,31 @@
+package com.networknt.schema.format;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * {@link AbstractFormat} for RFC 3986.
+ */
+public abstract class AbstractRFC3986Format implements Format {
+ @Override
+ public final boolean matches(ExecutionContext executionContext, String value) {
+ try {
+ URI uri = new URI(value);
+ return validate(uri);
+ } catch (URISyntaxException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Determines if the uri matches the format.
+ *
+ * @param uri the uri to match
+ * @return true if matches
+ */
+ protected abstract boolean validate(URI uri);
+
+}
diff --git a/src/main/java/com/networknt/schema/format/BaseFormat.java b/src/main/java/com/networknt/schema/format/BaseFormat.java
new file mode 100644
index 0000000..d8310bb
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/BaseFormat.java
@@ -0,0 +1,43 @@
+/*
+ * 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.schema.Format;
+
+/**
+ * Base implementation of {@link Format}.
+ */
+@Deprecated
+public abstract class BaseFormat implements Format {
+ private final String name;
+ private final String errorMessageDescription;
+
+ public BaseFormat(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/format/BaseFormatJsonValidator.java b/src/main/java/com/networknt/schema/format/BaseFormatJsonValidator.java
new file mode 100644
index 0000000..6412506
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/BaseFormatJsonValidator.java
@@ -0,0 +1,55 @@
+package com.networknt.schema.format;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.BaseJsonValidator;
+import com.networknt.schema.ErrorMessageType;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.Keyword;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.ValidationContext;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public abstract class BaseFormatJsonValidator extends BaseJsonValidator {
+ protected final boolean assertionsEnabled;
+
+ public BaseFormatJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ErrorMessageType errorMessageType, Keyword keyword,
+ ValidationContext validationContext) {
+ super(schemaLocation, evaluationPath, schemaNode, parentSchema, errorMessageType, keyword, validationContext, false);
+ VersionFlag dialect = this.validationContext.getMetaSchema().getSpecification();
+ if (dialect == null || dialect.getVersionFlagValue() < VersionFlag.V201909.getVersionFlagValue()) {
+ assertionsEnabled = true;
+ } else {
+ // Check vocabulary
+ assertionsEnabled = isFormatAssertionVocabularyEnabled(dialect,
+ this.validationContext.getMetaSchema().getVocabularies());
+ }
+ }
+
+ protected boolean isFormatAssertionVocabularyEnabled() {
+ return isFormatAssertionVocabularyEnabled(this.validationContext.getMetaSchema().getSpecification(),
+ this.validationContext.getMetaSchema().getVocabularies());
+ }
+
+ protected boolean isFormatAssertionVocabularyEnabled(VersionFlag specification, Map<String, Boolean> vocabularies) {
+ if (VersionFlag.V202012.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 (VersionFlag.V201909.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;
+ }
+ 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 0000000..c5d921c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/DateFormat.java
@@ -0,0 +1,33 @@
+package com.networknt.schema.format;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * 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 0000000..a739c9c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/DateTimeFormat.java
@@ -0,0 +1,82 @@
+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.Format;
+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.
+ * <p>
+ * 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<String> 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/DurationFormat.java b/src/main/java/com/networknt/schema/format/DurationFormat.java
new file mode 100644
index 0000000..ca8d0e1
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/DurationFormat.java
@@ -0,0 +1,47 @@
+package com.networknt.schema.format;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+import com.networknt.schema.ValidationContext;
+
+/**
+ * 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, ValidationContext validationContext, String duration) {
+ if (null == duration) {
+ return true;
+ }
+
+ if (duration.endsWith("P") || duration.endsWith("T")) {
+ return false;
+ }
+
+ Pattern pattern = isStrictValidation(validationContext) ? STRICT : LAX;
+ Matcher matcher = pattern.matcher(duration);
+ return matcher.matches();
+ }
+
+ protected boolean isStrictValidation(ValidationContext validationContext) {
+ return validationContext.getConfig().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 0000000..3a329e4
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/EmailFormat.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.format;
+
+import com.networknt.org.apache.commons.validator.routines.EmailValidator;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * 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/IPv6AwareEmailValidator.java b/src/main/java/com/networknt/schema/format/IPv6AwareEmailValidator.java
new file mode 100644
index 0000000..c9e7fc4
--- /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.
+ * <p>
+ * 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 0000000..ddcd2ce
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/IPv6Format.java
@@ -0,0 +1,37 @@
+package com.networknt.schema.format;
+
+import java.util.regex.Pattern;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * 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 0000000..855ba21
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/IdnEmailFormat.java
@@ -0,0 +1,35 @@
+package com.networknt.schema.format;
+
+import com.networknt.org.apache.commons.validator.routines.EmailValidator;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * 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 0000000..5d1ec7e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/IdnHostnameFormat.java
@@ -0,0 +1,26 @@
+package com.networknt.schema.format;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+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 || value.isEmpty()) 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/IriFormat.java b/src/main/java/com/networknt/schema/format/IriFormat.java
new file mode 100644
index 0000000..150741d
--- /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.getQuery();
+ 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 0000000..45f8bd9
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/IriReferenceFormat.java
@@ -0,0 +1,37 @@
+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.getQuery();
+ if (query != null) {
+ // [ and ] must be percent encoded
+ if (query.indexOf('[') != -1 || query.indexOf(']') != -1) {
+ return false;
+ }
+ }
+ 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 0000000..79fa917
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/PatternFormat.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+import com.networknt.schema.Format;
+
+/**
+ * Format using a regex pattern.
+ */
+public class PatternFormat implements Format {
+ private final String name;
+ private final Pattern pattern;
+ private final String messageKey;
+ private final String errorMessageDescription;
+
+ /**
+ * Constructor.
+ * <p>
+ * Use {@link #of(String, String, String)} instead.
+ *
+ * @param name the name
+ * @param regex the regex
+ * @param errorMessageDescription the error message description
+ */
+ @Deprecated
+ public PatternFormat(String name, String regex, String errorMessageDescription) {
+ this.name = name;
+ this.errorMessageDescription = errorMessageDescription != null ? errorMessageDescription : regex;
+ this.messageKey = "format";
+ this.pattern = Pattern.compile(regex);
+ }
+
+ private PatternFormat(String name, String regex, String errorMessageDescription, String messageKey) {
+ this.name = name;
+ this.errorMessageDescription = errorMessageDescription != null ? errorMessageDescription : regex;
+ 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, null, 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;
+ }
+
+ @Override
+ public String getErrorMessageDescription() {
+ return this.errorMessageDescription;
+ }
+}
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 0000000..c6f778b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/RegexFormat.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed under the Apache License, Version 2.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.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.Format;
+
+/**
+ * Format for regex.
+ */
+public class RegexFormat implements Format {
+ @Override
+ public boolean matches(ExecutionContext executionContext, String value) {
+ if (null == value) return true;
+ try {
+ Pattern.compile(value);
+ return true;
+
+ } catch (PatternSyntaxException 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/TimeFormat.java b/src/main/java/com/networknt/schema/format/TimeFormat.java
new file mode 100644
index 0000000..7ded636
--- /dev/null
+++ b/src/main/java/com/networknt/schema/format/TimeFormat.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed under the Apache License, Version 2.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 com.networknt.schema.Format;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
+import static java.time.temporal.ChronoField.*;
+
+/**
+ * Format for time.
+ * <p>
+ * 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) - offset / 60;
+ long min = accessor.getLong(MINUTE_OF_HOUR) - offset % 60;
+ long sec = accessor.getLong(SECOND_OF_MINUTE);
+
+ if (min < 0) {
+ --hr;
+ min += 60;
+ }
+ if (hr < 0) {
+ hr += 24;
+ }
+
+ boolean isStandardTimeRange = (sec <= 59 && min <= 59 && hr <= 23);
+ boolean isSpecialCaseEndOfDay = (sec == 60 && min == 59 && hr == 23);
+
+ return isStandardTimeRange
+ || isSpecialCaseEndOfDay;
+
+ } catch (DateTimeException e) {
+ return false;
+ }
+ }
+
+ @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 0000000..81d9034
--- /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.getQuery();
+ 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 0000000..d48650f
--- /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.getQuery();
+ 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 0000000..e62a73c
--- /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 0000000..0c92310
--- /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", "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<Locale> SUPPORTED_LOCALES = of(SUPPORTED_LANGUAGE_TAGS);
+
+ /**
+ * Gets the supported locales.
+ *
+ * @return the supported locales
+ */
+ public static List<Locale> getSupportedLocales() {
+ return SUPPORTED_LOCALES;
+ }
+
+ /**
+ * Gets a list of {@link Locale} by language tags.
+ *
+ * @param languageTags for the locales
+ * @return the locales
+ */
+ public static List<Locale> 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<Locale> 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<LanguageRange> priorityList, Collection<Locale> locales,
+ FilteringMode filteringMode) {
+ Locale result = Locale.lookup(priorityList, locales);
+ if (result != null) {
+ return result;
+ }
+ List<Locale> 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 0000000..03e4a79
--- /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 0000000..aaeb8aa
--- /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<String> 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<String>) 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 0000000..761bc92
--- /dev/null
+++ b/src/main/java/com/networknt/schema/i18n/ResourceBundleMessageSource.java
@@ -0,0 +1,137 @@
+/*
+ * 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 Map<String, Map<Locale, ResourceBundle>> resourceBundleMap = new ConcurrentHashMap<>();
+
+ /**
+ * Message Cache. locale -> key -> message.
+ */
+ private Map<Locale, Map<String, String>> messageMap = new ConcurrentHashMap<>();
+
+ /**
+ * Message Format Cache. locale -> message -> messageFormat.
+ * <p>
+ * Note that Message Format is not threadsafe.
+ */
+ private Map<Locale, Map<String, MessageFormat>> messageFormatMap = new ConcurrentHashMap<>();
+
+ private final List<String> baseNames;
+
+ public ResourceBundleMessageSource(String... baseName) {
+ this.baseNames = Arrays.asList(baseName);
+ }
+
+ @Override
+ public String getMessage(String key, Supplier<String> 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<String, MessageFormat> map = messageFormatMap.computeIfAbsent(locale, l -> {
+ return new ConcurrentHashMap<>();
+ });
+ return map.computeIfAbsent(message, m -> {
+ return 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<String, String> map = messageMap.computeIfAbsent(locale, l -> new ConcurrentHashMap<>());
+ return map.computeIfAbsent(key, k -> {
+ return 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<String> 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<Locale, ResourceBundle> getResourceBundle(String baseName) {
+ return resourceBundleMap.computeIfAbsent(baseName, key -> {
+ return 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/output/HierarchicalOutputUnitFormatter.java b/src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java
new file mode 100644
index 0000000..a557b32
--- /dev/null
+++ b/src/main/java/com/networknt/schema/output/HierarchicalOutputUnitFormatter.java
@@ -0,0 +1,174 @@
+/*
+ * 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.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.ValidationContext;
+import com.networknt.schema.ValidationMessage;
+
+/**
+ * HierarchicalOutputUnitFormatter.
+ */
+public class HierarchicalOutputUnitFormatter {
+ public static OutputUnit format(JsonSchema jsonSchema, Set<ValidationMessage> validationMessages,
+ ExecutionContext executionContext, ValidationContext validationContext) {
+
+ OutputUnit root = new OutputUnit();
+ root.setValid(validationMessages.isEmpty());
+
+ root.setInstanceLocation(validationContext.getConfig().getPathType().getRoot());
+ root.setEvaluationPath(validationContext.getConfig().getPathType().getRoot());
+ root.setSchemaLocation(jsonSchema.getSchemaLocation().toString());
+
+ OutputUnitData data = OutputUnitData.from(validationMessages, executionContext);
+
+ Map<OutputUnitKey, Boolean> valid = data.getValid();
+ Map<OutputUnitKey, Map<String, Object>> errors = data.getErrors();
+ Map<OutputUnitKey, Map<String, Object>> annotations = data.getAnnotations();
+ Map<OutputUnitKey, Map<String, Object>> droppedAnnotations = data.getDroppedAnnotations();
+
+ // Evaluation path to output unit
+ Map<JsonNodePath, Map<JsonNodePath, OutputUnit>> index = new LinkedHashMap<>();
+ Map<JsonNodePath, OutputUnit> r = new LinkedHashMap<>();
+ r.put(new JsonNodePath(validationContext.getConfig().getPathType()), root);
+ index.put(new JsonNodePath(validationContext.getConfig().getPathType()), r);
+
+ // Get all the evaluation paths with data
+ // This is a map of evaluation path to instance location
+ Map<JsonNodePath, Set<JsonNodePath>> 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<OutputUnitKey, Map<String, Object>> 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<OutputUnitKey, Map<String, Object>> 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<OutputUnitKey, Map<String, Object>> 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;
+ }
+
+ /**
+ * 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<JsonNodePath, Map<JsonNodePath, OutputUnit>> index,
+ Map<JsonNodePath, Set<JsonNodePath>> keys, OutputUnit root) {
+ if (index.containsKey(key.getEvaluationPath())) {
+ return;
+ }
+ // Ensure the path is created
+ JsonNodePath path = key.getEvaluationPath();
+ Deque<JsonNodePath> stack = new ArrayDeque<>();
+ while (path != null && path.getElement(-1) != null) {
+ stack.push(path);
+ path = path.getParent();
+ }
+
+ OutputUnit parent = root;
+ while (!stack.isEmpty()) {
+ JsonNodePath 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 (JsonNodePath 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<JsonNodePath, OutputUnit> result = index.get(current);
+ if (result != null) {
+ for (Entry<JsonNodePath, OutputUnit> 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 0000000..290a1a5
--- /dev/null
+++ b/src/main/java/com/networknt/schema/output/ListOutputUnitFormatter.java
@@ -0,0 +1,98 @@
+/*
+ * 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.Set;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.ValidationContext;
+import com.networknt.schema.ValidationMessage;
+
+/**
+ * ListOutputUnitFormatter.
+ */
+public class ListOutputUnitFormatter {
+ public static OutputUnit format(Set<ValidationMessage> validationMessages, ExecutionContext executionContext,
+ ValidationContext validationContext) {
+ OutputUnit root = new OutputUnit();
+ root.setValid(validationMessages.isEmpty());
+
+ OutputUnitData data = OutputUnitData.from(validationMessages, executionContext);
+
+ Map<OutputUnitKey, Boolean> valid = data.getValid();
+ Map<OutputUnitKey, Map<String, Object>> errors = data.getErrors();
+ Map<OutputUnitKey, Map<String, Object>> annotations = data.getAnnotations();
+ Map<OutputUnitKey, Map<String, Object>> droppedAnnotations = data.getDroppedAnnotations();
+
+ // Process the list
+ for (Entry<OutputUnitKey, Boolean> 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<String, Object> errorMap = errors.get(key);
+ if (errorMap != null && !errorMap.isEmpty()) {
+ if (output.getErrors() == null) {
+ output.setErrors(new LinkedHashMap<>());
+ }
+ for (Entry<String, Object> errorEntry : errorMap.entrySet()) {
+ output.getErrors().put(errorEntry.getKey(), errorEntry.getValue());
+ }
+ }
+
+ // Annotations
+ Map<String, Object> annotationsMap = annotations.get(key);
+ if (annotationsMap != null && !annotationsMap.isEmpty()) {
+ if (output.getAnnotations() == null) {
+ output.setAnnotations(new LinkedHashMap<>());
+ }
+ for (Entry<String, Object> annotationEntry : annotationsMap.entrySet()) {
+ output.getAnnotations().put(annotationEntry.getKey(), annotationEntry.getValue());
+ }
+ }
+
+ // Dropped Annotations
+ Map<String, Object> droppedAnnotationsMap = droppedAnnotations.get(key);
+ if (droppedAnnotationsMap != null && !droppedAnnotationsMap.isEmpty()) {
+ if (output.getDroppedAnnotations() == null) {
+ output.setDroppedAnnotations(new LinkedHashMap<>());
+ }
+ for (Entry<String, Object> droppedAnnotationEntry : droppedAnnotationsMap.entrySet()) {
+ output.getDroppedAnnotations().put(droppedAnnotationEntry.getKey(),
+ droppedAnnotationEntry.getValue());
+ }
+ }
+
+ List<OutputUnit> details = root.getDetails();
+ if (details == null) {
+ details = new ArrayList<>();
+ root.setDetails(details);
+ }
+ details.add(output);
+ }
+
+ return root;
+ }
+}
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 0000000..413bce0
--- /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 0000000..fa63534
--- /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 href=
+ * "https://github.com/json-schema-org/json-schema-spec/blob/main/jsonschema-validation-output-machines.md">A
+ * Specification for Machine-Readable Output for JSON Schema Validation and
+ * Annotation</a>
+ */
+@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<String, Object> errors = null;
+
+ private Map<String, Object> annotations = null;
+
+ private Map<String, Object> droppedAnnotations = null;
+
+ private List<OutputUnit> 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<String, Object> getErrors() {
+ return errors;
+ }
+
+ public void setErrors(Map<String, Object> errors) {
+ this.errors = errors;
+ }
+
+ public Map<String, Object> getAnnotations() {
+ return annotations;
+ }
+
+ public void setAnnotations(Map<String, Object> annotations) {
+ this.annotations = annotations;
+ }
+
+ public Map<String, Object> getDroppedAnnotations() {
+ return droppedAnnotations;
+ }
+
+ public void setDroppedAnnotations(Map<String, Object> droppedAnnotations) {
+ this.droppedAnnotations = droppedAnnotations;
+ }
+
+ public List<OutputUnit> getDetails() {
+ return details;
+ }
+
+ public void setDetails(List<OutputUnit> 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 0000000..e52d3a1
--- /dev/null
+++ b/src/main/java/com/networknt/schema/output/OutputUnitData.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.output;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.ValidationMessage;
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+
+/**
+ * Output Unit Data.
+ */
+public class OutputUnitData {
+ private final Map<OutputUnitKey, Boolean> valid = new LinkedHashMap<>();
+ private final Map<OutputUnitKey, Map<String, Object>> errors = new LinkedHashMap<>();
+ private final Map<OutputUnitKey, Map<String, Object>> annotations = new LinkedHashMap<>();
+ private final Map<OutputUnitKey, Map<String, Object>> droppedAnnotations = new LinkedHashMap<>();
+
+ public Map<OutputUnitKey, Boolean> getValid() {
+ return valid;
+ }
+
+ public Map<OutputUnitKey, Map<String, Object>> getErrors() {
+ return errors;
+ }
+
+ public Map<OutputUnitKey, Map<String, Object>> getAnnotations() {
+ return annotations;
+ }
+
+ public Map<OutputUnitKey, Map<String, Object>> getDroppedAnnotations() {
+ return droppedAnnotations;
+ }
+
+ public static String formatMessage(String message) {
+ int index = message.indexOf(":");
+ if (index != -1) {
+ int length = message.length();
+ while (index + 1 < length) {
+ if (message.charAt(index + 1) == ' ') {
+ index++;
+ } else {
+ break;
+ }
+ }
+ return message.substring(index + 1);
+ }
+ return message;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static OutputUnitData from(Set<ValidationMessage> validationMessages, ExecutionContext executionContext) {
+ OutputUnitData data = new OutputUnitData();
+
+ Map<OutputUnitKey, Boolean> valid = data.valid;
+ Map<OutputUnitKey, Map<String, Object>> errors = data.errors;
+ Map<OutputUnitKey, Map<String, Object>> annotations = data.annotations;
+ Map<OutputUnitKey, Map<String, Object>> droppedAnnotations = data.droppedAnnotations;
+
+ for (ValidationMessage assertion : validationMessages) {
+ SchemaLocation assertionSchemaLocation = new SchemaLocation(assertion.getSchemaLocation().getAbsoluteIri(),
+ assertion.getSchemaLocation().getFragment().getParent());
+ OutputUnitKey key = new OutputUnitKey(assertion.getEvaluationPath().getParent(),
+ assertionSchemaLocation, assertion.getInstanceLocation());
+ valid.put(key, false);
+ Map<String, Object> errorMap = errors.computeIfAbsent(key, k -> new LinkedHashMap<>());
+ Object value = errorMap.get(assertion.getType());
+ if (value == null) {
+ errorMap.put(assertion.getType(), formatMessage(assertion.getMessage()));
+ } else {
+ // Existing error, make it into a list
+ if (value instanceof List) {
+ ((List<String>) value).add(formatMessage(assertion.getMessage()));
+ } else {
+ List<String> values = new ArrayList<>();
+ values.add(value.toString());
+ values.add(formatMessage(assertion.getMessage()));
+ errorMap.put(assertion.getType(), values);
+ }
+ }
+ }
+
+ for (List<JsonNodeAnnotation> annotationsResult : executionContext.getAnnotations().asMap().values()) {
+ for (JsonNodeAnnotation 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.getResults().isValid(annotation.getInstanceLocation(),
+ annotation.getEvaluationPath());
+ valid.put(key, validResult);
+ if (validResult) {
+ // annotations
+ Map<String, Object> annotationMap = annotations.computeIfAbsent(key,
+ k -> new LinkedHashMap<>());
+ annotationMap.put(annotation.getKeyword(), annotation.getValue());
+ } else {
+ // dropped annotations
+ Map<String, Object> 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 0000000..760f758
--- /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.JsonNodePath;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * Output Unit Key.
+ */
+public class OutputUnitKey {
+ @JsonSerialize(using = ToStringSerializer.class)
+ final JsonNodePath evaluationPath;
+ @JsonSerialize(using = ToStringSerializer.class)
+ final SchemaLocation schemaLocation;
+ @JsonSerialize(using = ToStringSerializer.class)
+ final JsonNodePath instanceLocation;
+
+ public OutputUnitKey(JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNodePath instanceLocation) {
+ super();
+ this.evaluationPath = evaluationPath;
+ this.schemaLocation = schemaLocation;
+ this.instanceLocation = instanceLocation;
+ }
+
+ public JsonNodePath getEvaluationPath() {
+ return evaluationPath;
+ }
+
+ public SchemaLocation getSchemaLocation() {
+ return schemaLocation;
+ }
+
+ public JsonNodePath 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/regex/JDKRegularExpression.java b/src/main/java/com/networknt/schema/regex/JDKRegularExpression.java
new file mode 100644
index 0000000..6be0c7e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/JDKRegularExpression.java
@@ -0,0 +1,16 @@
+package com.networknt.schema.regex;
+
+import java.util.regex.Pattern;
+
+class JDKRegularExpression implements RegularExpression {
+ private final Pattern pattern;
+
+ JDKRegularExpression(String regex) {
+ this.pattern = Pattern.compile(regex);
+ }
+
+ @Override
+ public boolean matches(String value) {
+ return this.pattern.matcher(value).find();
+ }
+} \ No newline at end of file
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 0000000..bad4251
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/JoniRegularExpression.java
@@ -0,0 +1,33 @@
+package com.networknt.schema.regex;
+
+import java.nio.charset.StandardCharsets;
+
+import org.jcodings.specific.UTF8Encoding;
+import org.joni.Option;
+import org.joni.Regex;
+import org.joni.Syntax;
+
+class JoniRegularExpression implements RegularExpression {
+ private final Regex pattern;
+
+ JoniRegularExpression(String regex) {
+ // Joni is too liberal on some constructs
+ String s = regex
+ .replace("\\d", "[0-9]")
+ .replace("\\D", "[^0-9]")
+ .replace("\\w", "[a-zA-Z0-9_]")
+ .replace("\\W", "[^a-zA-Z0-9_]")
+ .replace("\\s", "[ \\f\\n\\r\\t\\v\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]")
+ .replace("\\S", "[^ \\f\\n\\r\\t\\v\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]");
+
+ byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
+ this.pattern = new Regex(bytes, 0, bytes.length, Option.SINGLELINE, UTF8Encoding.INSTANCE, Syntax.ECMAScript);
+ }
+
+ @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;
+ }
+
+} \ No newline at end of file
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 0000000..64c1ba2
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/RegularExpression.java
@@ -0,0 +1,16 @@
+package com.networknt.schema.regex;
+
+import com.networknt.schema.ValidationContext;
+
+@FunctionalInterface
+public interface RegularExpression {
+ boolean matches(String value);
+
+ static RegularExpression compile(String regex, ValidationContext validationContext) {
+ if (null == regex) return s -> true;
+ return validationContext.getConfig().isEcma262Validator()
+ ? new JoniRegularExpression(regex)
+ : new JDKRegularExpression(regex);
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java b/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java
new file mode 100644
index 0000000..1807f1e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.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 java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import com.networknt.schema.AbsoluteIri;
+
+/**
+ * Loads from classpath.
+ */
+public class ClasspathSchemaLoader implements SchemaLoader {
+
+ @Override
+ public InputStreamSource getSchema(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 = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = SchemaLoader.class.getClassLoader();
+ }
+ ClassLoader loader = classLoader;
+ if (name.startsWith("//")) {
+ name = name.substring(2);
+ }
+ String resource = name;
+ return () -> {
+ InputStream result = loader.getResourceAsStream(resource);
+ if (result == null) {
+ result = loader.getResourceAsStream(resource.substring(1));
+ }
+ if (result == null) {
+ throw new FileNotFoundException(iri);
+ }
+ return result;
+ };
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/resource/DefaultSchemaLoader.java b/src/main/java/com/networknt/schema/resource/DefaultSchemaLoader.java
new file mode 100644
index 0000000..a090719
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/DefaultSchemaLoader.java
@@ -0,0 +1,73 @@
+/*
+ * 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.List;
+
+import com.networknt.schema.AbsoluteIri;
+
+/**
+ * Default {@link SchemaLoader}.
+ */
+public class DefaultSchemaLoader implements SchemaLoader {
+ private static final List<SchemaLoader> DEFAULT;
+ private static final MetaSchemaMapper META_SCHEMA_MAPPER = new MetaSchemaMapper();
+
+ static {
+ List<SchemaLoader> result = new ArrayList<>();
+ result.add(new ClasspathSchemaLoader());
+ result.add(new UriSchemaLoader());
+ DEFAULT = result;
+ }
+
+ private final List<SchemaLoader> schemaLoaders;
+ private final List<SchemaMapper> schemaMappers;
+
+ public DefaultSchemaLoader(List<SchemaLoader> schemaLoaders, List<SchemaMapper> schemaMappers) {
+ this.schemaLoaders = schemaLoaders;
+ this.schemaMappers = schemaMappers;
+ }
+
+ @Override
+ public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
+ AbsoluteIri mappedResult = absoluteIri;
+ for (SchemaMapper mapper : schemaMappers) {
+ AbsoluteIri mapped = mapper.map(mappedResult);
+ if (mapped != null) {
+ mappedResult = mapped;
+ }
+ }
+ AbsoluteIri mapped = META_SCHEMA_MAPPER.map(absoluteIri);
+ if (mapped != null) {
+ mappedResult = mapped;
+ }
+ for (SchemaLoader loader : schemaLoaders) {
+ InputStreamSource result = loader.getSchema(mappedResult);
+ if (result != null) {
+ return result;
+ }
+ }
+ for (SchemaLoader loader : DEFAULT) {
+ InputStreamSource result = loader.getSchema(mappedResult);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+}
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 0000000..11831af
--- /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/MapSchemaLoader.java b/src/main/java/com/networknt/schema/resource/MapSchemaLoader.java
new file mode 100644
index 0000000..6d4b32f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/MapSchemaLoader.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 SchemaLoader}.
+ */
+public class MapSchemaLoader implements SchemaLoader {
+ private final Function<String, String> mappings;
+
+ /**
+ * Sets the schema data by absolute IRI.
+ *
+ * @param mappings the mappings
+ */
+ public MapSchemaLoader(Map<String, String> mappings) {
+ this(mappings::get);
+ }
+
+ /**
+ * Sets the schema data by absolute IRI function.
+ *
+ * @param mappings the mappings
+ */
+ public MapSchemaLoader(Function<String, String> mappings) {
+ this.mappings = mappings;
+ }
+
+ /**
+ * Sets the schema data by using two mapping functions.
+ * <p>
+ * Firstly to map the IRI to an object. If the object is null no mapping is
+ * performed.
+ * <p>
+ * Next to map the object to the schema data.
+ *
+ * @param <T> the type of the object
+ * @param mapIriToObject the mapping of IRI to object
+ * @param mapObjectToData the mappingof object to schema data
+ */
+ public <T> MapSchemaLoader(Function<String, T> mapIriToObject, Function<T, String> mapObjectToData) {
+ this.mappings = iri -> {
+ T result = mapIriToObject.apply(iri);
+ if (result != null) {
+ return mapObjectToData.apply(result);
+ }
+ return null;
+ };
+ }
+
+ @Override
+ public InputStreamSource getSchema(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/MapSchemaMapper.java b/src/main/java/com/networknt/schema/resource/MapSchemaMapper.java
new file mode 100644
index 0000000..a5e8825
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/MapSchemaMapper.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 SchemaMapper}.
+ */
+public class MapSchemaMapper implements SchemaMapper {
+ private final Function<String, String> mappings;
+
+ public MapSchemaMapper(Map<String, String> mappings) {
+ this(mappings::get);
+ }
+
+ public MapSchemaMapper(Function<String, String> mappings) {
+ this.mappings = mappings;
+ }
+
+ /**
+ * Apply the mapping function if the predicate is true.
+ *
+ * @param test the predicate
+ * @param mappings the mapping
+ */
+ public MapSchemaMapper(Predicate<String> test, Function<String, String> mappings) {
+ this.mappings = iri -> {
+ if (test.test(iri)) {
+ return mappings.apply(iri);
+ }
+ return null;
+ };
+ }
+
+ @Override
+ public AbsoluteIri map(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/MetaSchemaMapper.java b/src/main/java/com/networknt/schema/resource/MetaSchemaMapper.java
new file mode 100644
index 0000000..7881ece
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/MetaSchemaMapper.java
@@ -0,0 +1,45 @@
+/*
+ * 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 MetaSchemaMapper implements SchemaMapper {
+ 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 map(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/PrefixSchemaMapper.java b/src/main/java/com/networknt/schema/resource/PrefixSchemaMapper.java
new file mode 100644
index 0000000..46f4f80
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/PrefixSchemaMapper.java
@@ -0,0 +1,25 @@
+package com.networknt.schema.resource;
+
+import com.networknt.schema.AbsoluteIri;
+
+/**
+ * Prefix implementation of {@link SchemaMapper}.
+ */
+public class PrefixSchemaMapper implements SchemaMapper {
+ private final String source;
+ private final String replacement;
+
+ public PrefixSchemaMapper(String source, String replacement) {
+ this.source = source;
+ this.replacement = replacement;
+ }
+
+ @Override
+ public AbsoluteIri map(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/SchemaLoader.java b/src/main/java/com/networknt/schema/resource/SchemaLoader.java
new file mode 100644
index 0000000..86aa0f7
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/SchemaLoader.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;
+
+/**
+ * Schema Loader used to load a schema given the retrieval IRI.
+ */
+@FunctionalInterface
+public interface SchemaLoader {
+ /**
+ * Loads a schema given the retrieval IRI.
+ *
+ * @param absoluteIri the retrieval IRI
+ * @return the input stream source
+ */
+ InputStreamSource getSchema(AbsoluteIri absoluteIri);
+}
diff --git a/src/main/java/com/networknt/schema/resource/SchemaLoaders.java b/src/main/java/com/networknt/schema/resource/SchemaLoaders.java
new file mode 100644
index 0000000..dbe847c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/SchemaLoaders.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;
+
+/**
+ * Schema Loaders used to load a schema given the retrieval IRI.
+ */
+public class SchemaLoaders extends ArrayList<SchemaLoader> {
+ private static final long serialVersionUID = 1L;
+
+ public SchemaLoaders() {
+ super();
+ }
+
+ public SchemaLoaders(Collection<? extends SchemaLoader> c) {
+ super(c);
+ }
+
+ public SchemaLoaders(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private SchemaLoaders values = new SchemaLoaders();
+
+ 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 loaders.
+ *
+ * @param customizer the customizer
+ * @return the builder
+ */
+ public Builder values(Consumer<List<SchemaLoader>> customizer) {
+ customizer.accept(this.values);
+ return this;
+ }
+
+ /**
+ * Adds a schema loader.
+ *
+ * @param schemaLoader the schema loader
+ * @return the builder
+ */
+ public Builder add(SchemaLoader schemaLoader) {
+ this.values.add(schemaLoader);
+ 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<String, String> schemas) {
+ this.values.add(new MapSchemaLoader(schemas));
+ return this;
+ }
+
+ /**
+ * 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<String, String> schemas) {
+ this.values.add(new MapSchemaLoader(schemas));
+ return this;
+ }
+
+ /**
+ * Sets the schema data by using two mapping functions.
+ * <p>
+ * Firstly to map the IRI to an object. If the object is null no mapping is
+ * performed.
+ * <p>
+ * Next to map the object to the schema data.
+ *
+ * @param <T> 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 <T> Builder schemas(Function<String, T> mapIriToObject, Function<T, String> mapObjectToData) {
+ this.values.add(new MapSchemaLoader(mapIriToObject, mapObjectToData));
+ return this;
+ }
+
+ /**
+ * Builds a {@link SchemaLoaders}.
+ *
+ * @return the schema loaders
+ */
+ public SchemaLoaders build() {
+ return values;
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/resource/SchemaMapper.java b/src/main/java/com/networknt/schema/resource/SchemaMapper.java
new file mode 100644
index 0000000..be6813a
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/SchemaMapper.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 Mapper used to map an ID indicated by an absolute IRI to a retrieval
+ * IRI.
+ */
+@FunctionalInterface
+public interface SchemaMapper {
+ /**
+ * Maps an ID indicated by an absolute IRI to a retrieval IRI.
+ *
+ * @param absoluteIRI the ID
+ * @return the retrieval IRI or null if this mapper doesn't support the mapping
+ */
+ AbsoluteIri map(AbsoluteIri absoluteIRI);
+}
diff --git a/src/main/java/com/networknt/schema/resource/SchemaMappers.java b/src/main/java/com/networknt/schema/resource/SchemaMappers.java
new file mode 100644
index 0000000..1bdc79e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/SchemaMappers.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 SchemaMappers extends ArrayList<SchemaMapper> {
+ private static final long serialVersionUID = 1L;
+
+ public SchemaMappers() {
+ super();
+ }
+
+ public SchemaMappers(Collection<? extends SchemaMapper> c) {
+ super(c);
+ }
+
+ public SchemaMappers(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private SchemaMappers values = new SchemaMappers();
+
+ 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 mappers.
+ *
+ * @param customizer the customizer
+ * @return the builder
+ */
+ public Builder values(Consumer<List<SchemaMapper>> customizer) {
+ customizer.accept(this.values);
+ return this;
+ }
+
+ /**
+ * Adds a schema mapper.
+ *
+ * @param schemaMapper the schema mapper
+ * @return the builder
+ */
+ public Builder add(SchemaMapper schemaMapper) {
+ this.values.add(schemaMapper);
+ 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 PrefixSchemaMapper(source, replacement));
+ return this;
+ }
+
+ /**
+ * Sets the mappings.
+ *
+ * @param mappings the mappings
+ * @return the builder
+ */
+ public Builder mappings(Map<String, String> mappings) {
+ this.values.add(new MapSchemaMapper(mappings));
+ return this;
+ }
+
+ /**
+ * Sets the function that maps the IRI to another IRI.
+ *
+ * @param mappings the mappings
+ * @return the builder
+ */
+ public Builder mappings(Function<String, String> mappings) {
+ this.values.add(new MapSchemaMapper(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<String> test, Function<String, String> mappings) {
+ this.values.add(new MapSchemaMapper(test, mappings));
+ return this;
+ }
+
+ /**
+ * Builds a {@link SchemaMappers}
+ *
+ * @return the schema mappers
+ */
+ public SchemaMappers build() {
+ return values;
+ }
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/resource/UriSchemaLoader.java b/src/main/java/com/networknt/schema/resource/UriSchemaLoader.java
new file mode 100644
index 0000000..0825353
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/UriSchemaLoader.java
@@ -0,0 +1,110 @@
+/*
+ * 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 uri.
+ */
+public class UriSchemaLoader implements SchemaLoader {
+ @Override
+ public InputStreamSource getSchema(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.
+ * <p>
+ * 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.
+ * <p>
+ * 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("illegal URL redirect");
+ }
+ redir = true;
+ c = target.openConnection();
+ redirects++;
+ }
+ }
+ } while (redir);
+ return in;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/result/JsonNodeResult.java b/src/main/java/com/networknt/schema/result/JsonNodeResult.java
new file mode 100644
index 0000000..8b464d3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/result/JsonNodeResult.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.JsonNodePath;
+import com.networknt.schema.SchemaLocation;
+
+/**
+ * Sub schema results.
+ */
+public class JsonNodeResult {
+ private final JsonNodePath instanceLocation;
+ private final SchemaLocation schemaLocation;
+ private final JsonNodePath evaluationPath;
+ private final boolean valid;
+
+ public JsonNodeResult(JsonNodePath instanceLocation, SchemaLocation schemaLocation, JsonNodePath evaluationPath,
+ boolean valid) {
+ super();
+ this.instanceLocation = instanceLocation;
+ this.schemaLocation = schemaLocation;
+ this.evaluationPath = evaluationPath;
+ this.valid = valid;
+ }
+
+ public JsonNodePath getInstanceLocation() {
+ return instanceLocation;
+ }
+
+ public SchemaLocation getSchemaLocation() {
+ return schemaLocation;
+ }
+
+ public JsonNodePath 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;
+ JsonNodeResult other = (JsonNodeResult) 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/JsonNodeResults.java b/src/main/java/com/networknt/schema/result/JsonNodeResults.java
new file mode 100644
index 0000000..78b51f3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/result/JsonNodeResults.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.JsonNodePath;
+import com.networknt.schema.SchemaLocation;
+
+/**
+ * Sub schema results.
+ */
+public class JsonNodeResults {
+
+ /**
+ * Stores the invalid results.
+ */
+ private Map<JsonNodePath, List<JsonNodeResult>> values = new HashMap<>();
+
+ public void setResult(JsonNodePath instanceLocation, SchemaLocation schemaLocation, JsonNodePath evaluationPath,
+ boolean valid) {
+ JsonNodeResult result = new JsonNodeResult(instanceLocation, schemaLocation, evaluationPath, valid);
+ List<JsonNodeResult> v = values.computeIfAbsent(instanceLocation, k -> new ArrayList<>());
+ v.add(result);
+ }
+
+ public boolean isValid(JsonNodePath instanceLocation, JsonNodePath evaluationPath) {
+ List<JsonNodeResult> instance = values.get(instanceLocation);
+ if (instance != null) {
+ for (JsonNodeResult result : instance) {
+ if (evaluationPath.startsWith(result.getEvaluationPath())) {
+ if(!result.isValid()) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+}
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 0000000..7a24e29
--- /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/YamlMapperFactory.java b/src/main/java/com/networknt/schema/serialization/YamlMapperFactory.java
new file mode 100644
index 0000000..ea0b7a2
--- /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/utils/AbsoluteIris.java b/src/main/java/com/networknt/schema/utils/AbsoluteIris.java
new file mode 100644
index 0000000..1c06afc
--- /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 0000000..0f43f94
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/CachingSupplier.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ * <p>
+ * This is not threadsafe.
+ *
+ * @param <T> the type of results supplied by this supplier
+ */
+public class CachingSupplier<T> implements Supplier<T> {
+ private final Supplier<T> delegate;
+ private T cached = null;
+
+ public CachingSupplier(Supplier<T> supplier) {
+ this.delegate = supplier;
+ }
+
+ @Override
+ public T get() {
+ if (this.cached == null) {
+ this.cached = this.delegate.get();
+ }
+ return this.cached;
+ }
+}
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 0000000..e752a5c
--- /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/JsonNodeUtil.java b/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java
new file mode 100644
index 0000000..793856e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java
@@ -0,0 +1,180 @@
+package com.networknt.schema.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonType;
+import com.networknt.schema.PathType;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.SpecVersionDetector;
+import com.networknt.schema.TypeFactory;
+import com.networknt.schema.ValidationContext;
+
+public class JsonNodeUtil {
+ private static final long V6_VALUE = VersionFlag.V6.getVersionFlagValue();
+
+ 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 Collection<String> allPaths(PathType pathType, String root, JsonNode node) {
+ Collection<String> collector = new ArrayList<>();
+ visitNode(pathType, root, node, collector);
+ return collector;
+ }
+
+ private static void visitNode(PathType pathType, String root, JsonNode node, Collection<String> collector) {
+ if (node.isObject()) {
+ visitObject(pathType, root, node, collector);
+ } else if (node.isArray()) {
+ visitArray(pathType, root, node, collector);
+ }
+ }
+
+ private static void visitArray(PathType pathType, String root, JsonNode node, Collection<String> collector) {
+ int size = node.size();
+ for (int i = 0; i < size; ++i) {
+ String path = pathType.append(root, i);
+ collector.add(path);
+ visitNode(pathType, path, node.get(i), collector);
+ }
+ }
+
+ private static void visitObject(PathType pathType, String root, JsonNode node, Collection<String> collector) {
+ node.fields().forEachRemaining(entry -> {
+ String path = pathType.append(root, entry.getKey());
+ collector.add(path);
+ visitNode(pathType, path, entry.getValue(), collector);
+ });
+ }
+
+ 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;
+ }
+
+ public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, JsonSchema parentSchema, ValidationContext validationContext) {
+ SchemaValidatorsConfig config = validationContext.getConfig();
+ 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(validationContext)) {
+ return true;
+ }
+
+ if (nodeType == JsonType.NULL) {
+ if (parentSchema != null) {
+ JsonSchema grandParentSchema = parentSchema.getParentSchema();
+ if (grandParentSchema != null
+ && JsonNodeUtil.isNodeNullable(grandParentSchema.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 (StringChecker.isInteger(node.textValue())) {
+ return true;
+ }
+ } else if (schemaType == JsonType.BOOLEAN) {
+ if (StringChecker.isBoolean(node.textValue())) {
+ return true;
+ }
+ } else if (schemaType == JsonType.NUMBER) {
+ if (StringChecker.isNumeric(node.textValue())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+ return true;
+ }
+
+ private static long detectVersion(ValidationContext validationContext) {
+ String metaSchema = validationContext.getMetaSchema().getIri();
+ return SpecVersionDetector.detectOptionalVersion(metaSchema)
+ .orElse(VersionFlag.V4)
+ .getVersionFlagValue();
+ }
+
+ /**
+ * 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 StringChecker.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;
+ boolean refNode = false;
+
+ if (jsonSchema != null) {
+ if (jsonSchema.getSchemaNode() != null) {
+ typeNode = jsonSchema.getSchemaNode().get(TYPE);
+ enumNode = jsonSchema.getSchemaNode().get(ENUM);
+ }
+ refNode = REF.equals(jsonSchema.getEvaluationPath().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/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java
new file mode 100644
index 0000000..5653959
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java
@@ -0,0 +1,82 @@
+/*
+ * 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.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonNodePath;
+
+/**
+ * Utility methods for JsonNode.
+ */
+public class JsonNodes {
+ /**
+ * Gets the node found at the path.
+ *
+ * @param node the node
+ * @param path the path
+ * @return the node found at the path or null
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends JsonNode> T get(JsonNode node, JsonNodePath 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 node the node
+ * @param propertyOrIndex the property or index
+ * @return the node given the property or index
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends JsonNode> T get(JsonNode node, Object propertyOrIndex) {
+ JsonNode value = null;
+ if (propertyOrIndex instanceof Number) {
+ value = node.get(((Number) propertyOrIndex).intValue());
+ } else {
+ // In the case of string this represents an escaped json pointer and thus does not reflect the property directly
+ String unescaped = propertyOrIndex.toString();
+ if (unescaped.contains("~")) {
+ unescaped = unescaped.replace("~1", "/");
+ unescaped = unescaped.replace("~0", "~");
+ }
+ if (unescaped.contains("%")) {
+ try {
+ unescaped = URLDecoder.decode(unescaped, StandardCharsets.UTF_8.toString());
+ } catch (UnsupportedEncodingException e) {
+ // Do nothing
+ }
+ }
+
+ value = node.get(unescaped);
+ }
+ return (T) value;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java
new file mode 100644
index 0000000..9a6231f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.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.utils;
+
+import com.networknt.schema.DynamicRefValidator;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaRef;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.RecursiveRefValidator;
+import com.networknt.schema.RefValidator;
+
+/**
+ * Utility methods for JsonSchemaRef.
+ */
+public class JsonSchemaRefs {
+
+ /**
+ * Gets the ref.
+ *
+ * @param schema the schema
+ * @return the ref
+ */
+ public static JsonSchemaRef from(JsonSchema schema) {
+ for (JsonValidator validator : schema.getValidators()) {
+ if (validator instanceof RefValidator) {
+ return ((RefValidator) validator).getSchemaRef();
+ } else if (validator instanceof DynamicRefValidator) {
+ return ((DynamicRefValidator) validator).getSchemaRef();
+ } else if (validator instanceof RecursiveRefValidator) {
+ return ((RecursiveRefValidator) validator).getSchemaRef();
+ }
+ }
+ return null;
+ }
+
+}
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 0000000..76d0444
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/RFC5892.java
@@ -0,0 +1,400 @@
+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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 BiPredicate<String, Integer> RULE_ARABIC_INDIC_DIGITS_RULE = RFC5892::testArabicIndicDigit;
+ private static BiPredicate<String, Integer> RULE_EXTENDED_ARABIC_INDIC_DIGITS_RULE = RFC5892::testExtendedArabicIndicDigit;
+ private static BiPredicate<String, Integer> RULE_GREEK_LOWER_NUMERAL_SIGN = RFC5892::testGreekLowerNumeralSign;
+ private static BiPredicate<String, Integer> RULE_HEBREW_GERESH_GERSHAYIM = RFC5892::testHebrewPuncuation;
+ private static BiPredicate<String, Integer> RULE_KATAKANA_MIDDLE_DOT = RFC5892::testKatakanaMiddleDot;
+ private static BiPredicate<String, Integer> RULE_MIDDLE_DOT = RFC5892::testeMiddleDotRule;
+ private static BiPredicate<String, Integer> RULE_ZERO_WIDTH_JOINER = RFC5892::testZeroWidthJoiner;
+ private static BiPredicate<String, Integer> RULE_ZERO_WIDTH_NON_JOINER = RFC5892::testZeroWidthNonJoiner;
+
+ private static BiPredicate<String, Integer> ALLOWED_CHARACTER = RFC5892::testAllowedCharacter;
+
+ private static BiPredicate<String, Integer> LTR = RFC5892::testLTR;
+ private static BiPredicate<String, Integer> RTL = RFC5892::testRTL;
+
+ private static BiPredicate<String, Integer> 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
+ }
+
+ public static boolean isValid(String value) {
+ // RFC 5892 calls each segment in a host name a label. They are separated by '.'.
+ String[] labels = value.split("\\.");
+ 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<String, Integer> 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);
+ if (!isGreek(following)) return false;
+ }
+ 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);
+ if (!isHebrew(preceding)) return false;
+ }
+ 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);
+ if (!isKatakana(following)) return false;
+ }
+ 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);
+ if ('l' != preceding || 'l' != following) return false;
+ }
+ 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);
+ if (VIRAMA != preceding) return false;
+ }
+ 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);
+ if (!isJoinTypeRight(following) && !isJoinTypeDual(following)) return false;
+ }
+ 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/SetView.java b/src/main/java/com/networknt/schema/utils/SetView.java
new file mode 100644
index 0000000..0874907
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/SetView.java
@@ -0,0 +1,204 @@
+package com.networknt.schema.utils;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * View of a list of sets.
+ * <p>
+ * This is used for performance to reduce copies but breaks the semantics of the
+ * set where the elements must all be unique.
+ *
+ * @param <E> the type contains in the set
+ */
+public class SetView<E> implements Set<E> {
+ private final List<Set<E>> sets = new ArrayList<>();
+
+ /**
+ * Adds a set to the view.
+ *
+ * @param set to add to the view
+ * @return the view
+ */
+ public SetView<E> union(Set<E> set) {
+ if (set != null && !set.isEmpty()) {
+ this.sets.add(set);
+ }
+ return this;
+ }
+
+ @Override
+ public int size() {
+ int size = 0;
+ for (Set<E> set : sets) {
+ size += set.size();
+ }
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return sets.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ for (Set<E> set : sets) {
+ if (set.contains(o)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return new SetViewIterator<E>(this);
+ }
+
+ @Override
+ public Object[] toArray() {
+ int size = size();
+ Object[] result = new Object[size];
+ Iterator<?> iterator = iterator();
+ for (int x = 0; x < size; x++) {
+ result[x] = iterator.hasNext() ? iterator.next() : null;
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T[] toArray(T[] a) {
+ int size = size();
+ T[] result = size <= a.length ? a : (T[]) Array.newInstance(a.getClass().getComponentType(), size);
+ Iterator<?> iterator = iterator();
+ for (int x = 0; x < size; x++) {
+ result[x] = iterator.hasNext() ? (T) iterator.next() : null;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ for (Object e : c) {
+ if (!contains(e)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean add(E e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> coll) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> coll) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> coll) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Iterator.
+ *
+ * @param <E> the type contains in the set
+ */
+ public static class SetViewIterator<E> implements Iterator<E> {
+ private Iterator<Set<E>> sets = null;
+ private Iterator<E> current = null;
+
+ public SetViewIterator(SetView<E> view) {
+ this.sets = view.sets.iterator();
+ if (this.sets.hasNext()) {
+ this.current = this.sets.next().iterator();
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.current.hasNext()) {
+ return true;
+ }
+ while (this.sets.hasNext()) {
+ this.current = this.sets.next().iterator();
+ if (this.current.hasNext()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public E next() {
+ return this.current.next();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(sets);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Set)) {
+ return false;
+ }
+ Collection<?> collection = (Collection<?>) obj;
+ if (collection.size() != size()) {
+ return false;
+ }
+ try {
+ return containsAll(collection);
+ } catch (ClassCastException ignore) {
+ return false;
+ } catch (NullPointerException ignore) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append('[');
+ Iterator<E> iterator = iterator();
+ if (iterator.hasNext()) {
+ builder.append(iterator.next().toString());
+ }
+ while (iterator.hasNext()) {
+ builder.append(", ");
+ builder.append(iterator.next().toString());
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/networknt/schema/utils/StringChecker.java b/src/main/java/com/networknt/schema/utils/StringChecker.java
new file mode 100644
index 0000000..a4194d3
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/StringChecker.java
@@ -0,0 +1,104 @@
+package com.networknt.schema.utils;
+
+public class StringChecker {
+
+ 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';
+
+ 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 character = str.charAt(i++);
+
+ if (character == CHAR_0) {
+ // TODO: if leading zeros are supported (counter to JSON spec) handle it here
+ if (i < len) {
+ character = str.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 = str.charAt(i++);
+ }
+ } else {
+ return false;
+ }
+
+ if (character == DOT) {
+ if (i >= len) {
+ return false;
+ }
+ character = str.charAt(i++);
+ while (i < len && CHAR_0 <= character && character <= CHAR_9) {
+ character = str.charAt(i++);
+ }
+ }
+
+ if (character == CHAR_E || character == CHAR_e) {
+ if (i >= len) {
+ return false;
+ }
+ character = str.charAt(i++);
+ if (character == PLUS || character == MINUS) {
+ if (i >= len) {
+ return false;
+ }
+ character = str.charAt(i++);
+ }
+ while (i < len && CHAR_0 <= character && character <= CHAR_9) {
+ character = str.charAt(i++);
+ }
+ }
+
+ return i >= len && (CHAR_0 <= character && character <= CHAR_9);
+ }
+}
diff --git a/src/main/java/com/networknt/schema/utils/StringUtils.java b/src/main/java/com/networknt/schema/utils/StringUtils.java
new file mode 100644
index 0000000..230f92c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/StringUtils.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+public final class StringUtils {
+
+ private StringUtils() {
+ }
+
+ public static boolean isBlank(String s) {
+ return null == s || s.trim().isEmpty();
+ }
+
+ public static boolean isNotBlank(String s) {
+ return null != s && !s.trim().isEmpty();
+ }
+
+}
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 0000000..46b577e
--- /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<String, BitSet> 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 0000000..70812cc
--- /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/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java
new file mode 100644
index 0000000..92d564c
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java
@@ -0,0 +1,46 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.ValidationMessage;
+
+import java.util.List;
+import java.util.Set;
+
+public abstract class AbstractWalkListenerRunner implements WalkListenerRunner {
+
+ protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
+ return WalkEvent.builder().executionContext(executionContext).instanceLocation(instanceLocation)
+ .keyword(keyword).instanceNode(instanceNode)
+ .rootNode(rootNode).schema(schema).validator(validator).build();
+ }
+
+ protected boolean runPreWalkListeners(List<JsonSchemaWalkListener> walkListeners, WalkEvent walkEvent) {
+ boolean continueToWalkMethod = true;
+ if (walkListeners != null) {
+ for (JsonSchemaWalkListener walkListener : walkListeners) {
+ WalkFlow walkFlow = walkListener.onWalkStart(walkEvent);
+ if (WalkFlow.SKIP.equals(walkFlow) || WalkFlow.ABORT.equals(walkFlow)) {
+ continueToWalkMethod = false;
+ if (WalkFlow.ABORT.equals(walkFlow)) {
+ break;
+ }
+ }
+ }
+ }
+ return continueToWalkMethod;
+ }
+
+ protected void runPostWalkListeners(List<JsonSchemaWalkListener> walkListeners, WalkEvent walkEvent,
+ Set<ValidationMessage> validationMessages) {
+ if (walkListeners != null) {
+ for (JsonSchemaWalkListener walkListener : walkListeners) {
+ walkListener.onWalkEnd(walkEvent, validationMessages);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java
new file mode 100644
index 0000000..eba7f05
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java
@@ -0,0 +1,37 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.ValidationMessage;
+
+import java.util.List;
+import java.util.Set;
+
+public class DefaultItemWalkListenerRunner extends AbstractWalkListenerRunner {
+
+ private List<JsonSchemaWalkListener> itemWalkListeners;
+
+ public DefaultItemWalkListenerRunner(List<JsonSchemaWalkListener> itemWalkListeners) {
+ this.itemWalkListeners = itemWalkListeners;
+ }
+
+ @Override
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator 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, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation,
+ schema, validator);
+ 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
new file mode 100644
index 0000000..0435f53
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java
@@ -0,0 +1,48 @@
+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<String, List<JsonSchemaWalkListener>> keywordWalkListenersMap;
+
+ public DefaultKeywordWalkListenerRunner(Map<String, List<JsonSchemaWalkListener>> keywordWalkListenersMap) {
+ this.keywordWalkListenersMap = keywordWalkListenersMap;
+ }
+
+ @Override
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode,
+ JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
+ boolean continueRunningListenersAndWalk = true;
+ WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
+ // Run Listeners that are setup only for this keyword.
+ List<JsonSchemaWalkListener> currentKeywordListeners = keywordWalkListenersMap.get(keyword);
+ continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent);
+ if (continueRunningListenersAndWalk) {
+ // Run Listeners that are setup for all keywords.
+ List<JsonSchemaWalkListener> allKeywordListeners = keywordWalkListenersMap
+ .get(SchemaValidatorsConfig.ALL_KEYWORD_WALK_LISTENER_KEY);
+ runPreWalkListeners(allKeywordListeners, keywordWalkEvent);
+ }
+ return continueRunningListenersAndWalk;
+ }
+
+ @Override
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation,
+ JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
+ // Run Listeners that are setup only for this keyword.
+ List<JsonSchemaWalkListener> currentKeywordListeners = keywordWalkListenersMap.get(keyword);
+ runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages);
+ // Run Listeners that are setup for all keywords.
+ List<JsonSchemaWalkListener> 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
new file mode 100644
index 0000000..e10253f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java
@@ -0,0 +1,36 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.ValidationMessage;
+
+import java.util.List;
+import java.util.Set;
+
+public class DefaultPropertyWalkListenerRunner extends AbstractWalkListenerRunner {
+
+ private List<JsonSchemaWalkListener> propertyWalkListeners;
+
+ public DefaultPropertyWalkListenerRunner(List<JsonSchemaWalkListener> propertyWalkListeners) {
+ this.propertyWalkListeners = propertyWalkListeners;
+ }
+
+ @Override
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode,
+ JsonNodePath instanceLocation, JsonSchema schema, JsonValidator 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, JsonNodePath instanceLocation,
+ JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
+ runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages);
+
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java b/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java
new file mode 100644
index 0000000..1873879
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/JsonSchemaWalkListener.java
@@ -0,0 +1,17 @@
+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<ValidationMessage> validationMessages);
+}
diff --git a/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java b/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java
new file mode 100644
index 0000000..1a2b435
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java
@@ -0,0 +1,33 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.BaseJsonValidator;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+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. <br>
+ * <br>
+ * {@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
+ * sub-schemas.
+ *
+ * @param executionContext ExecutionContext
+ * @param node JsonNode
+ * @param rootNode JsonNode
+ * @param instanceLocation JsonNodePath
+ * @param shouldValidateSchema boolean
+ * @return a set of validation messages if shouldValidateSchema is true.
+ */
+ Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema);
+}
diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java
new file mode 100644
index 0000000..f0a2079
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java
@@ -0,0 +1,152 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonValidator;
+
+/**
+ * Encapsulation of Walk data that is passed into the {@link JsonSchemaWalkListener}.
+ */
+public class WalkEvent {
+
+ private ExecutionContext executionContext;
+ private JsonSchema schema;
+ private String keyword;
+ private JsonNode rootNode;
+ private JsonNode instanceNode;
+ private JsonNodePath instanceLocation;
+ private JsonValidator validator;
+
+ /**
+ * Gets the execution context.
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Gets the schema that will be used to evaluate the instance node.
+ * <p>
+ * For the keyword listener, this will allow getting the validator for the given keyword.
+ *
+ * @return the schema
+ */
+ public JsonSchema getSchema() {
+ return schema;
+ }
+
+ /**
+ * Gets the keyword.
+ *
+ * @return the keyword
+ */
+ public String getKeyword() {
+ return keyword;
+ }
+
+ /**
+ * Gets the root instance node.
+ * <p>
+ * 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;
+ }
+
+ /**
+ * Gets the instance node.
+ *
+ * @return the instance node
+ */
+ public JsonNode getInstanceNode() {
+ return instanceNode;
+ }
+
+ /**
+ * Gets the instance location of the instance node.
+ *
+ * @return the instance location of the instance node
+ */
+ public JsonNodePath getInstanceLocation() {
+ return instanceLocation;
+ }
+
+ /**
+ * Gets the validator that corresponds with the keyword.
+ *
+ * @return the validator
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends JsonValidator> T getValidator() {
+ return (T) this.validator;
+ }
+
+ @Override
+ public String toString() {
+ return "WalkEvent [evaluationPath=" + getSchema().getEvaluationPath() + ", schemaLocation="
+ + getSchema().getSchemaLocation() + ", instanceLocation=" + instanceLocation + "]";
+ }
+
+ static class WalkEventBuilder {
+
+ private WalkEvent walkEvent;
+
+ WalkEventBuilder() {
+ walkEvent = new WalkEvent();
+ }
+
+ public WalkEventBuilder executionContext(ExecutionContext executionContext) {
+ walkEvent.executionContext = executionContext;
+ return this;
+ }
+
+ public WalkEventBuilder schema(JsonSchema schema) {
+ walkEvent.schema = schema;
+ return this;
+ }
+
+ public WalkEventBuilder keyword(String keyword) {
+ walkEvent.keyword = keyword;
+ return this;
+ }
+
+ public WalkEventBuilder instanceNode(JsonNode node) {
+ walkEvent.instanceNode = node;
+ return this;
+ }
+
+ public WalkEventBuilder rootNode(JsonNode rootNode) {
+ walkEvent.rootNode = rootNode;
+ return this;
+ }
+
+ public WalkEventBuilder instanceLocation(JsonNodePath instanceLocation) {
+ walkEvent.instanceLocation = instanceLocation;
+ return this;
+ }
+
+ public WalkEventBuilder validator(JsonValidator validator) {
+ walkEvent.validator = validator;
+ return this;
+ }
+
+ public WalkEvent build() {
+ return walkEvent;
+ }
+
+ }
+
+ 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
new file mode 100644
index 0000000..af2da7b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/WalkFlow.java
@@ -0,0 +1,28 @@
+package com.networknt.schema.walk;
+
+public enum WalkFlow {
+
+ SKIP("SkipWalk", "Skip only the walk method, but continue invoking the other listeners"),
+
+ ABORT("Abort", "Aborts all the walk listeners and walk method itself"),
+
+ CONTINUE("ContinueToWalk", "continue to invoke the walk method and other listeners");
+
+ private String name;
+
+ private String description;
+
+ WalkFlow(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java
new file mode 100644
index 0000000..0476d8b
--- /dev/null
+++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java
@@ -0,0 +1,20 @@
+package com.networknt.schema.walk;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.ExecutionContext;
+import com.networknt.schema.JsonNodePath;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.ValidationMessage;
+
+import java.util.Set;
+
+public interface WalkListenerRunner {
+
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator);
+
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages);
+
+}
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 0000000..1d69fb5
--- /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 0000000..0d4f101
--- /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 0000000..9e167b1
--- /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 0000000..bcbb847
--- /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 0000000..bd3e763
--- /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 0000000..fb92c7f
--- /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 0000000..24a1cc4
--- /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 0000000..f6752a8
--- /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 0000000..eb708a5
--- /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 0000000..09bbfdd
--- /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 0000000..da04cff
--- /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 0000000..9f59677
--- /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 0000000..2248a0c
--- /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 0000000..ca69923
--- /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 0000000..2f6e056
--- /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 0000000..dfc092d
--- /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 0000000..51ef7ea
--- /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/meta-data b/src/main/resources/draft/2020-12/meta/meta-data
new file mode 100644
index 0000000..05cbc22
--- /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 0000000..5f62a3f
--- /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 0000000..606b87b
--- /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 0000000..d5e2d31
--- /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/jsv-messages.properties b/src/main/resources/jsv-messages.properties
new file mode 100644
index 0000000..8e94095
--- /dev/null
+++ b/src/main/resources/jsv-messages.properties
@@ -0,0 +1,70 @@
+$ref = {0}: has an error with ''refs''
+additionalItems = {0}: index ''{1}'' is not defined in the schema and the schema does not allow additional items
+additionalProperties = {0}: property ''{1}'' is not defined in the schema and the schema does not allow additional properties
+allOf = {0}: must be valid to all the schemas {1}
+anyOf = {0}: must be valid to any of the schemas {1}
+const = {0}: must be the constant value ''{1}''
+contains = {0}: does not contain an element that passes these validations: {2}
+contains.max = {0}: must contain at most {1} element(s) that passes these validations: {2}
+contains.min = {0}: must contain at least {1} element(s) that passes these validations: {2}
+dependencies = {0}: has an error with dependencies {1}
+dependentRequired = {0}: has a missing property ''{1}'' which is dependent required because ''{2}'' is present
+dependentSchemas = {0}: has an error with dependentSchemas {1}
+enum = {0}: does not have a value in the enumeration {1}
+exclusiveMaximum = {0}: must have an exclusive maximum value of {1}
+exclusiveMinimum = {0}: must have an exclusive minimum value of {1}
+false = {0}: schema for ''{1}'' is false
+format = {0}: does not match the {1} pattern {2}
+format.date = {0}: does not match the {1} pattern must be a valid RFC 3339 full-date
+format.date-time = {0}: does not match the {1} pattern must be a valid RFC 3339 date-time
+format.duration = {0}: does not match the {1} pattern must be a valid ISO 8601 duration
+format.email = {0}: does not match the {1} pattern must be a valid RFC 5321 Mailbox
+format.ipv4 = {0}: does not match the {1} pattern must be a valid RFC 2673 IP address
+format.ipv6 = {0}: does not match the {1} pattern must be a valid RFC 4291 IP address
+format.idn-email = {0}: does not match the {1} pattern must be a valid RFC 6531 Mailbox
+format.idn-hostname = {0}: does not match the {1} pattern must be a valid RFC 5890 internationalized hostname
+format.iri = {0}: does not match the {1} pattern must be a valid RFC 3987 IRI
+format.iri-reference = {0}: does not match the {1} pattern must be a valid RFC 3987 IRI-reference
+format.uri = {0}: does not match the {1} pattern must be a valid RFC 3986 URI
+format.uri-reference = {0}: does not match the {1} pattern must be a valid RFC 3986 URI-reference
+format.uri-template = {0}: does not match the {1} pattern must be a valid RFC 6570 URI Template
+format.uuid = {0}: does not match the {1} pattern must be a valid RFC 4122 UUID
+format.regex = {0}: does not match the {1} pattern must be a valid ECMA-262 regular expression
+format.time = {0}: does not match the {1} pattern must be a valid RFC 3339 time
+format.hostname = {0}: does not match the {1} pattern must be a valid RFC 1123 host name
+format.json-pointer = {0}: does not match the {1} pattern must be a valid RFC 6901 JSON Pointer
+format.relative-json-pointer = {0}: does not match the {1} pattern must be a valid IETF Relative JSON Pointer
+format.unknown = {0}: has an unknown format ''{1}''
+id = {0}: ''{1}'' is not a valid {2}
+items = {0}: index ''{1}'' is not defined in the schema and the schema does not allow additional items
+maxContains = {0}: must be a non-negative integer in {1}
+maxItems = {0}: must have at most {1} items but found {2}
+maxLength = {0}: must be at most {1} characters long
+maxProperties = {0}: must have at most {1} properties
+maximum = {0}: must have a maximum value of {1}
+minContains = {0}: must be a non-negative integer in {1}
+minContainsVsMaxContains = {0}: minContains must less than or equal to maxContains in {1}
+minItems = {0}: must have at least {1} items but found {2}
+minLength = {0}: must be at least {1} characters long
+minProperties = {0}: must have at least {1} properties
+minimum = {0}: must have a minimum value of {1}
+multipleOf = {0}: must be multiple of {1}
+not = {0}: must not be valid to the schema {1}
+notAllowed = {0}: property ''{1}'' is not allowed but it is in the data
+oneOf = {0}: must be valid to one and only one schema, but {1} are valid
+oneOf.indexes = {0}: must be valid to one and only one schema, but {1} are valid with indexes ''{2}''
+pattern = {0}: does not match the regex pattern {1}
+patternProperties = {0}: has some error with ''pattern properties''
+prefixItems = {0}: no validator found at this index
+properties = {0}: has an error with ''properties''
+propertyNames = {0}: property ''{1}'' name is not valid: {2}
+readOnly = {0}: is a readonly field, it cannot be changed
+required = {0}: required property ''{1}'' not found
+type = {0}: {1} found, {2} expected
+unevaluatedItems = {0}: index ''{1}'' is not evaluated and the schema does not allow unevaluated items
+unevaluatedProperties = {0}: property ''{1}'' is not evaluated and the schema does not allow unevaluated properties
+unionType = {0}: {1} found, {2} expected
+uniqueItems = {0}: must have only unique items in the array
+writeOnly = {0}: is a write-only field, it cannot appear in the data
+contentEncoding = {0}: does not match content encoding {1}
+contentMediaType = {0}: is not a content media type \ 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 0000000..2dd53b2
--- /dev/null
+++ b/src/main/resources/jsv-messages_ar.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u0628\u0647 \u062E\u0637\u0623 \u0641\u064A ''refs''
+additionalItems = {0}: \u0644\u0645 \u064A\u062A\u0645 \u062A\u0639\u0631\u064A\u0641 \u0627\u0644\u0641\u0647\u0631\u0633 ''{1}'' \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 = {0}: \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{1}'' \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 = {0}: \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 {1}
+anyOf = {0}: \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 {1}
+const = {0}: \u064A\u062C\u0628 \u0623\u0646 \u062A\u0643\u0648\u0646 \u0627\u0644\u0642\u064A\u0645\u0629 \u0627\u0644\u062B\u0627\u0628\u062A\u0629 ''{1}''
+contains = {0}: \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: {2}
+contains.max = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \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: {2}
+contains.min = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \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: {2}
+dependencies = {0}: \u064A\u0648\u062C\u062F \u062E\u0637\u0623 \u0641\u064A \u0627\u0644\u062A\u0628\u0639\u064A\u0627\u062A {1}
+dependentRequired = {0}: \u0628\u0647 \u062E\u0627\u0635\u064A\u0629 \u0645\u0641\u0642\u0648\u062F\u0629 ''{1}'' \u0648\u0647\u064A \u062A\u0627\u0628\u0639\u0629 \u0645\u0637\u0644\u0648\u0628\u0629 \u0644\u0623\u0646 ''{2}'' \u0645\u0648\u062C\u0648\u062F\u0629
+dependentSchemas = {0}: \u0628\u0647 \u062E\u0637\u0623 \u0641\u064AdependentSchemas {1}
+enum = {0}: \u0644\u0627 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0642\u064A\u0645\u0629 \u0641\u064A \u0627\u0644\u062A\u0639\u062F\u0627\u062F {1}
+exclusiveMaximum = {0}: \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 {1}
+exclusiveMinimum = {0}: \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 {1}
+false = {0}: \u0645\u062E\u0637\u0637 ''{1}'' \u063A\u064A\u0631 \u0635\u062D\u064A\u062D
+format = {0}: \u0644\u0627 \u064A\u0637\u0627\u0628\u0642 \u0627\u0644\u0646\u0645\u0637 {1} {2}
+format.date = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \u0648\u064A\u062C\u0628 \u0623\u0646 \u062A\u0643\u0648\u0646 \u0645\u062F\u0629 ISO 8601 \u0635\u0627\u0644\u062D\u0629
+format.email = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 RFC 3987 IRI \u0635\u0627\u0644\u062D\u064B\u0627
+format.iri-reference = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 RFC 4122 UUID \u0635\u0627\u0644\u062D\u064B\u0627
+format.regex = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \u0648\u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0648\u0642\u062A RFC 3339 \u0635\u0627\u0644\u062D\u064B\u0627
+format.hostname = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0646\u0645\u0637 {1} \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 = {0}: \u0644\u0647 \u062A\u0646\u0633\u064A\u0642 \u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641 ''{1}''
+id = {0}: ''{1}'' \u0644\u064A\u0633 {2} \u0635\u0627\u0644\u062D\u064B\u0627
+items = {0}: \u0644\u0645 \u064A\u062A\u0645 \u062A\u0639\u0631\u064A\u0641 \u0627\u0644\u0641\u0647\u0631\u0633 ''{1}'' \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 = {0}: \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 {1}
+maxItems = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \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 {2}
+maxLength = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0637\u0648\u0644\u0647 {1} \u062D\u0631\u0641\u064B\u0627 \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631
+maxProperties = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \u0645\u0646 \u0627\u0644\u062E\u0635\u0627\u0626\u0635 \u0639\u0644\u0649 \u0627\u0644\u0623\u0643\u062B\u0631
+maximum = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 \u0644\u0642\u064A\u0645\u0629 {1}
+minContains = {0}: \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 {1}
+minContainsVsMaxContains = {0}: \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 {1}
+minItems = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \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 {2}
+minLength = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0637\u0648\u0644\u0647 {1} \u062D\u0631\u0641\u064B\u0627 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644
+minProperties = {0}: \u064A\u062C\u0628 \u0623\u0646 \u062A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 {1} \u0645\u0646 \u0627\u0644\u062E\u0635\u0627\u0626\u0635 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644
+minimum = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u062F\u0646\u0649 \u0644\u0642\u064A\u0645\u0629 {1}
+multipleOf = {0}: \u064A\u062C\u0628 \u0623\u0646 \u064A\u0643\u0648\u0646 \u0645\u0646 \u0645\u0636\u0627\u0639\u0641\u0627\u062A {1}
+not = {0}: \u064A\u062C\u0628 \u0623\u0646 \u0644\u0627 \u064A\u0643\u0648\u0646 \u0635\u0627\u0644\u062D\u064B\u0627 \u0644\u0644\u0645\u062E\u0637\u0637 {1}
+notAllowed = {0}: \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{1}'' \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 = {0}: \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 {1} \u0635\u0627\u0644\u062D
+oneOf.indexes = {0}: \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 {1} \u0635\u0627\u0644\u062D \u0645\u0639 \u0627\u0644\u0641\u0647\u0627\u0631\u0633 ''{2}''
+pattern = {0}: \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 {1}
+patternProperties = {0}: \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 = {0}: \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 = {0}: \u064A\u0648\u062C\u062F \u062E\u0637\u0623 \u0641\u064A ''\u0627\u0644\u062E\u0635\u0627\u0626\u0635''
+propertyNames = {0}: \u0627\u0633\u0645 \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{1}'' \u063A\u064A\u0631 \u0635\u0627\u0644\u062D: {2}
+readOnly = {0}: \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 = {0}: \u0627\u0644\u062E\u0627\u0635\u064A\u0629 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 ''{1}'' \u063A\u064A\u0631 \u0645\u0648\u062C\u0648\u062F\u0629
+type = {0}: \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {1}\u060C \u0648\u0627\u0644\u0645\u062A\u0648\u0642\u0639 \u0647\u0648 {2}.
+unevaluatedItems = {0}: \u0644\u0645 \u064A\u062A\u0645 \u062A\u0642\u064A\u064A\u0645 \u0627\u0644\u0641\u0647\u0631\u0633 ''{1}'' \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 = {0}: \u0627\u0644\u062E\u0627\u0635\u064A\u0629 ''{1}'' \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 = {0}: \u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 {1}\u060C \u0648\u0627\u0644\u0645\u062A\u0648\u0642\u0639 \u0647\u0648 {2}.
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \u0644\u0627 \u064A\u0637\u0627\u0628\u0642 \u062A\u0631\u0645\u064A\u0632 \u0627\u0644\u0645\u062D\u062A\u0648\u0649 {1}
+contentMediaType = {0}: \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 0000000..49088a9
--- /dev/null
+++ b/src/main/resources/jsv-messages_cs.properties
@@ -0,0 +1,70 @@
+$ref = {0}: obsahuje chybu s ''refs''
+additionalItems = {0}: index ''{1}'' není ve schématu definován a schéma nepovoluje dal\u0161í polo\u017Eky
+additionalProperties = {0}: vlastnost ''{1}'' není ve schématu definována a schéma neumo\u017E\u0148uje dal\u0161í vlastnosti
+allOf = {0}: musí být platné pro v\u0161echna schémata {1}
+anyOf = {0}: musí být platné pro kterékoli ze schémat {1}
+const = {0}: musí být konstantní hodnota ''{1}''
+contains = {0}: neobsahuje prvek, který pro\u0161el t\u011Bmito ov\u011B\u0159eními: {2}
+contains.max = {0}: musí obsahovat nejvý\u0161e {1} prvk\u016F, které projdou t\u011Bmito ov\u011B\u0159eními: {2}
+contains.min = {0}: musí obsahovat alespo\u0148 {1} prvk\u016F, které projdou t\u011Bmito ov\u011B\u0159eními: {2}
+dependencies = {0}: obsahuje chybu se závislostmi {1}
+dependentRequired = {0}: má chyb\u011Bjící vlastnost ''{1}'', která je závislá povinná, proto\u017Ee ''{2}'' je p\u0159ítomen
+dependentSchemas = {0}: obsahuje chybu s dependentSchemas {1}
+enum = {0}: nemá hodnotu ve vý\u010Dtu {1}
+exclusiveMaximum = {0}: musí mít exkluzivní maximální hodnotu {1}
+exclusiveMinimum = {0}: musí mít exkluzivní minimální hodnotu {1}
+false = {0}: schéma pro ''{1}'' je nepravdivé
+format = {0}: neodpovídá vzoru {1} {2}
+format.date = {0}: neodpovídá vzoru {1} musí být platné plné datum RFC 3339
+format.date-time = {0}: neodpovídá vzoru {1} musí být platné datum a \u010Das RFC 3339
+format.duration = {0}: neodpovídá vzoru {1}, musí mít platnou dobu trvání ISO 8601
+format.email = {0}: neodpovídá vzoru {1} musí být platná po\u0161tovní schránka RFC 5321
+format.ipv4 = {0}: neodpovídá vzoru {1} musí být platná IP adresa RFC 2673
+format.ipv6 = {0}: neodpovídá vzoru {1} musí být platná IP adresa RFC 4291
+format.idn-email = {0}: neodpovídá vzoru {1} musí být platná po\u0161tovní schránka RFC 6531
+format.idn-hostname = {0}: neodpovídá vzoru {1}, musí být platným internacionalizovaným názvem hostitele RFC 5890
+format.iri = {0}: neodpovídá vzoru {1} musí být platný RFC 3987 IRI
+format.iri-reference = {0}: neodpovídá vzoru {1} musí být platný RFC 3987 IRI-reference
+format.uri = {0}: neodpovídá vzoru {1} musí být platný RFC 3986 URI
+format.uri-reference = {0}: neodpovídá vzoru {1} musí být platný RFC 3986 URI odkaz
+format.uri-template = {0}: neodpovídá vzoru {1} musí být platná \u0161ablona URI RFC 6570
+format.uuid = {0}: neodpovídá vzoru {1} musí být platný RFC 4122 UUID
+format.regex = {0}: neodpovídá vzoru {1} musí být platný regulární výraz ECMA-262
+format.time = {0}: neodpovídá vzoru {1} musí být platný \u010Das RFC 3339
+format.hostname = {0}: neodpovídá vzoru {1} musí být platný název hostitele RFC 1123
+format.json-pointer = {0}: neodpovídá vzoru {1} musí být platný RFC 6901 JSON ukazatel
+format.relative-json-pointer = {0}: neodpovídá vzoru {1} musí být platný IETF relativní ukazatel JSON
+format.unknown = {0}: má neznámý formát ''{1}''
+id = {0}: ''{1}'' není platný {2}
+items = {0}: index ''{1}'' není ve schématu definován a schéma neumo\u017E\u0148uje dal\u0161í polo\u017Eky
+maxContains = {0}: musí být nezáporné celé \u010Díslo v {1}
+maxItems = {0}: musí mít maximáln\u011B {1} polo\u017Eek, ale nalezeno {2}
+maxLength = {0}: musí mít maximáln\u011B {1} znak\u016F
+maxProperties = {0}: musí mít maximáln\u011B {1} vlastností
+maximum = {0}: musí mít maximální hodnotu {1}
+minContains = {0}: musí být nezáporné celé \u010Díslo v {1}
+minContainsVsMaxContains = {0}: minContains musí být men\u0161í nebo roven maxContains v {1}
+minItems = {0}: musí mít alespo\u0148 {1} polo\u017Eek, ale nalezeno {2}
+minLength = {0}: musí mít alespo\u0148 {1} znak\u016F
+minProperties = {0}: musí mít alespo\u0148 {1} vlastností
+minimum = {0}: musí mít minimální hodnotu {1}
+multipleOf = {0}: musí být násobkem {1}
+not = {0}: nesmí být platné pro schéma {1}
+notAllowed = {0}: vlastnost ''{1}'' není povolena, ale je v datech
+oneOf = {0}: musí být platné pro jedno a pouze jedno schéma, ale {1} jsou platné
+oneOf.indexes = {0}: musí být platné pro jedno a pouze jedno schéma, ale {1} jsou platné s indexy ''{2}''
+pattern = {0}: neodpovídá vzoru regulárního výrazu {1}
+patternProperties = {0}: obsahuje n\u011Bjakou chybu s ''vlastnostmi vzoru''
+prefixItems = {0}: v tomto indexu nebyl nalezen \u017Eádný validátor
+properties = {0}: obsahuje chybu s ''vlastnosti''
+propertyNames = {0}: název vlastnosti ''{1}'' není platný: {2}
+readOnly = {0}: je pole pouze pro \u010Dtení, nelze jej zm\u011Bnit
+required = {0}: po\u017Eadovaná vlastnost ''{1}'' nebyla nalezena
+type = {0}: nalezeno {1}, o\u010Dekáváno {2}
+unevaluatedItems = {0}: index ''{1}'' není vyhodnocen a schéma nepovoluje neohodnocené polo\u017Eky
+unevaluatedProperties = {0}: vlastnost ''{1}'' není vyhodnocena a schéma nepovoluje neohodnocené vlastnosti
+unionType = {0}: nalezeno {1}, o\u010Dekáváno {2}
+uniqueItems = {0}: musí mít v poli pouze jedine\u010Dné polo\u017Eky
+writeOnly = {0}: je pole pouze pro zápis, nem\u016F\u017Ee se objevit v datech
+contentEncoding = {0}: neodpovídá kódování obsahu {1}
+contentMediaType = {0}: 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 0000000..458269d
--- /dev/null
+++ b/src/main/resources/jsv-messages_da.properties
@@ -0,0 +1,70 @@
+$ref = {0}: har en fejl med ''refs''
+additionalItems = {0}: indeks ''{1}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere elementer
+additionalProperties = {0}: egenskaben ''{1}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere egenskaber
+allOf = {0}: skal være gyldig for alle skemaerne {1}
+anyOf = {0}: skal være gyldig for et hvilket som helst af skemaerne {1}
+const = {0}: skal være den konstante værdi ''{1}''
+contains = {0}: indeholder ikke et element, der består disse valideringer: {2}
+contains.max = {0}: må højst indeholde {1} element(er), der består disse valideringer: {2}
+contains.min = {0}: skal indeholde mindst {1} element(er), der består disse valideringer: {2}
+dependencies = {0}: har en fejl med afhængigheder {1}
+dependentRequired = {0}: har en manglende egenskab ''{1}'', som er afhængig påkrævet, fordi ''{2}'' er til stede
+dependentSchemas = {0}: har en fejl med dependentSchemas {1}
+enum = {0}: har ikke en værdi i opregningen {1}
+exclusiveMaximum = {0}: skal have en eksklusiv maksimumværdi på {1}
+exclusiveMinimum = {0}: skal have en eksklusiv minimumsværdi på {1}
+false = {0}: skemaet for ''{1}'' er falsk
+format = {0}: matcher ikke {1}-mønsteret {2}
+format.date = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3339 fuld-dato
+format.date-time = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3339 dato-tid
+format.duration = {0}: matcher ikke {1}-mønsteret skal være en gyldig ISO 8601-varighed
+format.email = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 5321-postkasse
+format.ipv4 = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 2673 IP-adresse
+format.ipv6 = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 4291 IP-adresse
+format.idn-email = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 6531-postkasse
+format.idn-hostname = {0}: matcher ikke {1}-mønsteret skal være et gyldigt RFC 5890 internationaliseret værtsnavn
+format.iri = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3987 IRI
+format.iri-reference = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3987 IRI-reference
+format.uri = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3986 URI
+format.uri-reference = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3986 URI-reference
+format.uri-template = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 6570 URI-skabelon
+format.uuid = {0}: matcher ikke {1}-mønsteret skal være et gyldigt RFC 4122 UUID
+format.regex = {0}: matcher ikke {1}-mønsteret skal være et gyldigt ECMA-262 regulært udtryk
+format.time = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 3339-tid
+format.hostname = {0}: matcher ikke {1}-mønsteret skal være et gyldigt RFC 1123-værtsnavn
+format.json-pointer = {0}: matcher ikke {1}-mønsteret skal være en gyldig RFC 6901 JSON-peger
+format.relative-json-pointer = {0}: matcher ikke {1}-mønsteret skal være en gyldig IETF Relativ JSON-peger
+format.unknown = {0}: har et ukendt format ''{1}''
+id = {0}: ''{1}'' er ikke en gyldig {2}
+items = {0}: indeks ''{1}'' er ikke defineret i skemaet, og skemaet tillader ikke yderligere elementer
+maxContains = {0}: skal være et ikke-negativt heltal i {1}
+maxItems = {0}: må højst have {1} varer, men fundet {2}
+maxLength = {0}: må højst være på {1} tegn
+maxProperties = {0}: må højst have {1} egenskaber
+maximum = {0}: skal have en maksimal værdi på {1}
+minContains = {0}: skal være et ikke-negativt heltal i {1}
+minContainsVsMaxContains = {0}: minContains skal være mindre end eller lig med maxContains i {1}
+minItems = {0}: skal have mindst {1} elementer, men fundet {2}
+minLength = {0}: skal være mindst {1} tegn lang
+minProperties = {0}: skal have mindst {1} egenskaber
+minimum = {0}: skal have en minimumsværdi på {1}
+multipleOf = {0}: skal være multiplum af {1}
+not = {0}: må ikke være gyldig for skemaet {1}
+notAllowed = {0}: egenskaben ''{1}'' er ikke tilladt, men den er i dataene
+oneOf = {0}: skal være gyldig for ét og kun ét skema, men {1} er gyldige
+oneOf.indexes = {0}: skal være gyldig for ét og kun ét skema, men {1} er gyldige med indekser ''{2}''
+pattern = {0}: matcher ikke regex-mønsteret {1}
+patternProperties = {0}: har en fejl med ''mønsteregenskaber''
+prefixItems = {0}: ingen validator fundet i dette indeks
+properties = {0}: har en fejl med ''egenskaber''
+propertyNames = {0}: Ejendommen ''{1}'' navn er ikke gyldigt: {2}
+readOnly = {0}: er et skrivebeskyttet felt, det kan ikke ændres
+required = {0}: påkrævet egenskab ''{1}'' blev ikke fundet
+type = {0}: {1} fundet, {2} forventet
+unevaluatedItems = {0}: indeks ''{1}'' evalueres ikke, og skemaet tillader ikke uevaluerede elementer
+unevaluatedProperties = {0}: egenskaben ''{1}'' evalueres ikke, og skemaet tillader ikke uevaluerede egenskaber
+unionType = {0}: {1} fundet, {2} forventet
+uniqueItems = {0}: må kun have unikke elementer i arrayet
+writeOnly = {0}: er et skrivebeskyttet felt, det kan ikke vises i dataene
+contentEncoding = {0}: matcher ikke indholdskodning {1}
+contentMediaType = {0}: er ikke et indholds mig
diff --git a/src/main/resources/jsv-messages_de.properties b/src/main/resources/jsv-messages_de.properties
new file mode 100644
index 0000000..13d2806
--- /dev/null
+++ b/src/main/resources/jsv-messages_de.properties
@@ -0,0 +1,70 @@
+$ref = {0}: hat einen Fehler mit ''refs''
+additionalItems = {0}: Index \u201E{1}\u201C ist im Schema nicht definiert und das Schema lässt keine zusätzlichen Elemente zu
+additionalProperties = {0}: Eigenschaft ''{1}'' ist im Schema nicht definiert und das Schema lässt keine zusätzlichen Eigenschaften zu
+allOf = {0}: muss für alle Schemas {1} gültig sein
+anyOf = {0}: muss für eines der Schemas {1} gültig sein
+const = {0}: muss der konstante Wert ''{1}'' sein
+contains = {0}: enthält kein Element, das diese Validierungen besteht: {2}
+contains.max = {0}: muss höchstens {1} Elemente enthalten, die diese Validierungen bestehen: {2}
+contains.min = {0}: muss mindestens {1} Elemente enthalten, die diese Validierungen bestehen: {2}
+dependencies = {0}: Es liegt ein Fehler mit den Abhängigkeiten {1} vor.
+dependentRequired = {0}: Es fehlt eine Eigenschaft \u201E{1}\u201C, die abhängig ist, da \u201E{2}\u201C vorhanden ist
+dependentSchemas = {0}: Es liegt ein Fehler mit dependenceSchemas {1} vor.
+enum = {0}: hat keinen Wert in der Aufzählung {1}
+exclusiveMaximum = {0}: muss einen exklusiven Maximalwert von {1} haben
+exclusiveMinimum = {0}: muss einen exklusiven Mindestwert von {1} haben
+false = {0}: Schema für ''{1}'' ist falsch
+format = {0}: entspricht nicht dem Muster {1} {2}
+format.date = {0}: stimmt nicht mit dem {1}-Muster überein, muss ein gültiges RFC 3339-Volldatum sein
+format.date-time = {0}: entspricht nicht dem {1}-Muster. Es muss sich um ein gültiges RFC 3339-Datum/Uhrzeit-Format handeln
+format.duration = {0}: stimmt nicht mit dem {1}-Muster überein, muss eine gültige ISO 8601-Dauer sein
+format.email = {0}: entspricht nicht dem {1}-Muster. Es muss sich um ein gültiges RFC 5321-Postfach handeln
+format.ipv4 = {0}: entspricht nicht dem {1}-Muster. Es muss sich um eine gültige RFC 2673-IP-Adresse handeln
+format.ipv6 = {0}: entspricht nicht dem {1}-Muster. Es muss sich um eine gültige RFC 4291-IP-Adresse handeln
+format.idn-email = {0}: entspricht nicht dem {1}-Muster. Es muss sich um ein gültiges RFC 6531-Postfach handeln
+format.idn-hostname = {0}: entspricht nicht dem {1}-Muster. Es muss sich um einen gültigen internationalisierten RFC 5890-Hostnamen handeln
+format.iri = {0}: entspricht nicht dem {1}-Muster, muss ein gültiger RFC 3987 IRI sein
+format.iri-reference = {0}: entspricht nicht dem {1}-Muster muss eine gültige RFC 3987 IRI-Referenz sein
+format.uri = {0}: entspricht nicht dem {1}-Muster muss ein gültiger RFC 3986-URI sein
+format.uri-reference = {0}: stimmt nicht mit dem {1}-Muster überein, muss eine gültige RFC 3986-URI-Referenz sein
+format.uri-template = {0}: entspricht nicht dem {1}-Muster. Es muss sich um eine gültige RFC 6570-URI-Vorlage handeln
+format.uuid = {0}: stimmt nicht mit dem {1}-Muster überein, muss eine gültige RFC 4122-UUID sein
+format.regex = {0}: entspricht nicht dem Muster {1} muss ein gültiger regulärer ECMA-262-Ausdruck sein
+format.time = {0}: entspricht nicht dem {1}-Muster muss eine gültige RFC 3339-Zeit sein
+format.hostname = {0}: entspricht nicht dem {1}-Muster. Es muss sich um einen gültigen RFC 1123-Hostnamen handeln
+format.json-pointer = {0}: stimmt nicht mit dem {1}-Muster überein, muss ein gültiger RFC 6901-JSON-Zeiger sein
+format.relative-json-pointer = {0}: stimmt nicht mit dem {1}-Muster überein, muss ein gültiger relativer IETF-JSON-Zeiger sein
+format.unknown = {0}: hat ein unbekanntes Format ''{1}''
+id = {0}: ''{1}'' ist kein gültiger {2}
+items = {0}: Index ''{1}'' ist im Schema nicht definiert und das Schema lässt keine zusätzlichen Elemente zu
+maxContains = {0}: muss eine nicht negative Ganzzahl in {1} sein
+maxItems = {0}: muss höchstens {1} Elemente haben, aber {2} gefunden
+maxLength = {0}: darf höchstens {1} Zeichen lang sein
+maxProperties = {0}: darf höchstens {1} Eigenschaften haben
+maximum = {0}: muss einen Maximalwert von {1} haben
+minContains = {0}: muss eine nicht negative Ganzzahl in {1} sein
+minContainsVsMaxContains = {0}: minContains muss kleiner oder gleich maxContains in {1} sein
+minItems = {0}: muss mindestens {1} Elemente haben, aber {2} gefunden
+minLength = {0}: muss mindestens {1} Zeichen lang sein
+minProperties = {0}: muss mindestens {1} Eigenschaften haben
+minimum = {0}: muss einen Mindestwert von {1} haben
+multipleOf = {0}: muss ein Vielfaches von {1} sein
+not = {0}: darf für das Schema {1} nicht gültig sein
+notAllowed = {0}: Eigenschaft ''{1}'' ist nicht zulässig, befindet sich aber in den Daten
+oneOf = {0}: muss für ein und nur ein Schema gültig sein, aber {1} sind gültig
+oneOf.indexes = {0}: muss für ein und nur ein Schema gültig sein, aber {1} sind mit den Indizes \u201E{2}\u201C gültig.
+pattern = {0}: stimmt nicht mit dem Regex-Muster {1} überein
+patternProperties = {0}: Es liegt ein Fehler mit den \u201EMustereigenschaften\u201C vor.
+prefixItems = {0}: Bei diesem Index wurde kein Validator gefunden
+properties = {0}: Es liegt ein Fehler mit \u201EProperties\u201C vor.
+propertyNames = {0}: Der Name der Eigenschaft \u201E{1}\u201C ist ungültig: {2}
+readOnly = {0}: ist ein schreibgeschütztes Feld, es kann nicht geändert werden
+required = {0}: erforderliche Eigenschaft ''{1}'' nicht gefunden
+type = {0}: {1} gefunden, {2} erwartet
+unevaluatedItems = {0}: Index ''{1}'' wird nicht ausgewertet und das Schema lässt keine nicht ausgewerteten Elemente zu
+unevaluatedProperties = {0}: Eigenschaft ''{1}'' wird nicht ausgewertet und das Schema lässt keine nicht ausgewerteten Eigenschaften zu
+unionType = {0}: {1} gefunden, {2} erwartet
+uniqueItems = {0}: Das Array darf nur eindeutige Elemente enthalten
+writeOnly = {0}: ist ein schreibgeschütztes Feld, es kann nicht in den Daten erscheinen
+contentEncoding = {0}: stimmt nicht mit der Inhaltskodierung {1} überein
+contentMediaType = {0}: ist kein Inhalt für mich
diff --git a/src/main/resources/jsv-messages_en.properties b/src/main/resources/jsv-messages_en.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/main/resources/jsv-messages_en.properties
diff --git a/src/main/resources/jsv-messages_fa.properties b/src/main/resources/jsv-messages_fa.properties
new file mode 100644
index 0000000..d8a1ff7
--- /dev/null
+++ b/src/main/resources/jsv-messages_fa.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u062F\u0627\u0631\u0627\u06CC \u062E\u0637\u0627 \u0628\u0627 "refs" \u0627\u0633\u062A
+additionalItems = {0}: \u0646\u0645\u0627\u06CC\u0647 ''{1}'' \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 = {0}: \u062E\u0627\u0635\u06CC\u062A ''{1}'' \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 = {0}: \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 {1}
+anyOf = {0}: \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 {1}
+const = {0}: \u0628\u0627\u06CC\u062F \u0645\u0642\u062F\u0627\u0631 \u062B\u0627\u0628\u062A "{1}" \u0628\u0627\u0634\u062F
+contains = {0}: \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: {2}
+contains.max = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u062D\u0627\u0648\u06CC {1} \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: {2}
+contains.min = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0634\u0627\u0645\u0644 {1} \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: {2}
+dependencies = {0}: \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u062E\u0637\u0627 \u062F\u0631 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC {1}
+dependentRequired = {0}: \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u0648\u06CC\u0698\u06AF\u06CC \u06AF\u0645 \u0634\u062F\u0647 "{1}" \u0627\u0633\u062A \u06A9\u0647 \u0628\u0647 \u062F\u0644\u06CC\u0644 \u0648\u062C\u0648\u062F "{2}" \u0648\u0627\u0628\u0633\u062A\u0647 \u0627\u0633\u062A
+dependentSchemas = {0}: \u062F\u0627\u0631\u0627\u06CC \u062E\u0637\u0627 \u0628\u0627 dependentSchemas {1}
+enum = {0}: \u0645\u0642\u062F\u0627\u0631\u06CC \u062F\u0631 \u0634\u0645\u0627\u0631\u0634 {1} \u0646\u062F\u0627\u0631\u062F
+exclusiveMaximum = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u0645\u0642\u062F\u0627\u0631 \u0627\u0646\u062D\u0635\u0627\u0631\u06CC {1} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+exclusiveMinimum = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0645\u0642\u062F\u0627\u0631 \u0627\u0646\u062D\u0635\u0627\u0631\u06CC {1} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+false = {0}: \u0637\u0631\u062D\u0648\u0627\u0631\u0647 ''{1}'' \u0646\u0627\u062F\u0631\u0633\u062A \u0627\u0633\u062A
+format = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} {2} \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F
+format.date = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC {1} \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 = {0}: \u062F\u0627\u0631\u0627\u06CC \u0642\u0627\u0644\u0628 \u0646\u0627\u0634\u0646\u0627\u062E\u062A\u0647 ''{1}''
+id = {0}: "{1}" \u06CC\u06A9 {2} \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A
+items = {0}: \u0646\u0645\u0627\u06CC\u0647 ''{1}'' \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 = {0}: \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0639\u062F\u062F \u0635\u062D\u06CC\u062D \u063A\u06CC\u0631 \u0645\u0646\u0641\u06CC \u062F\u0631 {1} \u0628\u0627\u0634\u062F
+maxItems = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {1} \u0645\u0648\u0631\u062F \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F \u0627\u0645\u0627 {2} \u06CC\u0627\u0641\u062A \u0634\u0648\u062F
+maxLength = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {1} \u06A9\u0627\u0631\u0627\u06A9\u062A\u0631 \u0628\u0627\u0634\u062F
+maxProperties = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 {1} \u0648\u06CC\u0698\u06AF\u06CC \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+maximum = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u06A9\u062B\u0631 \u0645\u0642\u062F\u0627\u0631 {1} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+minContains = {0}: \u0628\u0627\u06CC\u062F \u06CC\u06A9 \u0639\u062F\u062F \u0635\u062D\u06CC\u062D \u063A\u06CC\u0631 \u0645\u0646\u0641\u06CC \u062F\u0631 {1} \u0628\u0627\u0634\u062F
+minContainsVsMaxContains = {0}: minContains \u0628\u0627\u06CC\u062F \u06A9\u0645\u062A\u0631 \u06CC\u0627 \u0645\u0633\u0627\u0648\u06CC maxContains \u062F\u0631 {1}
+minItems = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {1} \u0645\u0648\u0631\u062F \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F \u0627\u0645\u0627 {2} \u067E\u06CC\u062F\u0627 \u0634\u062F\u0647 \u0628\u0627\u0634\u062F
+minLength = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {1} \u06A9\u0627\u0631\u0627\u06A9\u062A\u0631 \u0628\u0627\u0634\u062F
+minProperties = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 {1} \u0648\u06CC\u0698\u06AF\u06CC \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+minimum = {0}: \u0628\u0627\u06CC\u062F \u062D\u062F\u0627\u0642\u0644 \u0645\u0642\u062F\u0627\u0631 {1} \u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F
+multipleOf = {0}: \u0628\u0627\u06CC\u062F \u0645\u0636\u0631\u0628 {1} \u0628\u0627\u0634\u062F
+not = {0}: \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 {1}
+notAllowed = {0}: \u0648\u06CC\u0698\u06AF\u06CC "{1}" \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 = {0}: \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 {1} \u0645\u0639\u062A\u0628\u0631 \u0647\u0633\u062A\u0646\u062F
+oneOf.indexes = {0}: \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 {1} \u0628\u0627 \u0646\u0645\u0627\u06CC\u0647 \u0647\u0627\u06CC ''{2}'' \u0645\u0639\u062A\u0628\u0631 \u0647\u0633\u062A\u0646\u062F
+pattern = {0}: \u0628\u0627 \u0627\u0644\u06AF\u0648\u06CC regex \u0645\u0637\u0627\u0628\u0642\u062A \u0646\u062F\u0627\u0631\u062F {1}
+patternProperties = {0}: \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 = {0}: \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 = {0}: \u062F\u0627\u0631\u0627\u06CC \u06CC\u06A9 \u062E\u0637\u0627 \u0628\u0627 "properties" \u0627\u0633\u062A
+propertyNames = {0}: \u0646\u0627\u0645 \u0648\u06CC\u0698\u06AF\u06CC ''{1}'' \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A: {2}
+readOnly = {0}: \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 = {0}: \u0648\u06CC\u0698\u06AF\u06CC \u0645\u0648\u0631\u062F \u0646\u06CC\u0627\u0632 ''{1}'' \u06CC\u0627\u0641\u062A \u0646\u0634\u062F
+type = {0}: {1} \u06CC\u0627\u0641\u062A \u0634\u062F\u060C {2} \u0645\u0648\u0631\u062F \u0627\u0646\u062A\u0638\u0627\u0631 \u0628\u0648\u062F
+unevaluatedItems = {0}: \u0646\u0645\u0627\u06CC\u0647 ''{1}'' \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 = {0}: \u0648\u06CC\u0698\u06AF\u06CC ''{1}'' \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}: {1} \u06CC\u0627\u0641\u062A \u0634\u062F\u060C {2} \u0645\u0648\u0631\u062F \u0627\u0646\u062A\u0638\u0627\u0631 \u0628\u0648\u062F
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \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 {1}
+contentMediaType = {0}: \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 0000000..a6498eb
--- /dev/null
+++ b/src/main/resources/jsv-messages_fi.properties
@@ -0,0 +1,70 @@
+$ref = {0}: siinä on virhe "viittauksilla"
+additionalItems = {0}: hakemistoa ''{1}'' ei ole määritetty skeemassa, eikä skeema salli lisäkohteita
+additionalProperties = {0}: ominaisuutta ''{1}'' ei ole määritetty skeemassa, eikä skeema salli lisäominaisuuksia
+allOf = {0}: täytyy olla voimassa kaikissa malleissa {1}
+anyOf = {0}: täytyy olla kelvollinen mille tahansa skeemalle {1}
+const = {0}: on oltava vakioarvo ''{1}''
+contains = {0}: ei sisällä elementtiä, joka läpäisee seuraavat tarkistukset: {2}
+contains.max = {0}: saa sisältää enintään {1} elementtiä, jotka läpäisevät nämä tarkistukset: {2}
+contains.min = {0}: sisältää vähintään {1} elementtiä, jotka läpäisevät nämä tarkistukset: {2}
+dependencies = {0}: siinä on virhe riippuvuuksien {1} kanssa
+dependentRequired = {0}: puuttuu ominaisuus "{1}", joka on riippuvainen, koska "{2}" on läsnä
+dependentSchemas = {0}: sisältää virheen dependentSchemasissa {1}
+enum = {0}: sillä ei ole arvoa luettelossa {1}
+exclusiveMaximum = {0}: eksklusiivisen enimmäisarvon on oltava {1}
+exclusiveMinimum = {0}: on oltava yksinomainen vähimmäisarvo {1}
+false = {0}: kaava kohteelle ''{1}'' on epätosi
+format = {0}: ei vastaa mallia {1} {2}
+format.date = {0}: ei vastaa mallia {1}, ja sen on oltava kelvollinen RFC 3339:n täysi päivämäärä
+format.date-time = {0}: ei vastaa mallia {1}, ja sen on oltava kelvollinen RFC 3339 päivämäärä-aika
+format.duration = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen ISO 8601 -kesto
+format.email = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 5321 -postilaatikko
+format.ipv4 = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 2673 IP-osoite
+format.ipv6 = {0}: ei vastaa mallia {1}, ja sen on oltava kelvollinen RFC 4291 IP-osoite
+format.idn-email = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 6531 -postilaatikko
+format.idn-hostname = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 5890:n kansainvälistetty isäntänimi
+format.iri = {0}: ei vastaa mallia {1}, ja sen on oltava kelvollinen RFC 3987 IRI
+format.iri-reference = {0}: ei vastaa {1}-mallia, on oltava kelvollinen RFC 3987 IRI-viite
+format.uri = {0}: ei vastaa mallia {1}, ja sen on oltava kelvollinen RFC 3986 URI
+format.uri-reference = {0}: ei vastaa {1}-mallia, täytyy olla kelvollinen RFC 3986 URI-viite
+format.uri-template = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 6570 URI-malli
+format.uuid = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 4122 UUID
+format.regex = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen ECMA-262-säännöllinen lauseke
+format.time = {0}: ei vastaa mallia {1}, sen on oltava kelvollinen RFC 3339 -aika
+format.hostname = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 1123 -isäntänimi
+format.json-pointer = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen RFC 6901 JSON-osoitin
+format.relative-json-pointer = {0}: ei vastaa mallia {1}. Sen on oltava kelvollinen IETF:n suhteellinen JSON-osoitin
+format.unknown = {0}: sillä on tuntematon muoto ''{1}''
+id = {0}: ''{1}'' ei ole kelvollinen {2}
+items = {0}: hakemistoa ''{1}'' ei ole määritetty skeemassa, eikä skeema salli lisäkohteita
+maxContains = {0}: täytyy olla ei-negatiivinen kokonaisluku ryhmässä {1}
+maxItems = {0}: saa olla enintään {1} kohdetta, mutta löytyi {2}
+maxLength = {0}: saa olla enintään {1} merkkiä pitkä
+maxProperties = {0}: saa olla enintään {1} ominaisuutta
+maximum = {0}: maksimiarvon on oltava {1}
+minContains = {0}: täytyy olla ei-negatiivinen kokonaisluku ryhmässä {1}
+minContainsVsMaxContains = {0}: minContains on pienempi tai yhtä suuri kuin maxContains kohteessa {1}
+minItems = {0}: on oltava vähintään {1} kohdetta, mutta löydetty {2}
+minLength = {0}: on oltava vähintään {1} merkkiä pitkä
+minProperties = {0}: on oltava vähintään {1} ominaisuutta
+minimum = {0}: vähimmäisarvon on oltava {1}
+multipleOf = {0}: täytyy olla {1}:n kerrannainen
+not = {0}: ei saa olla kelvollinen kaavalle {1}
+notAllowed = {0}: ominaisuus ''{1}'' ei ole sallittu, mutta se on tiedoissa
+oneOf = {0}: täytyy olla voimassa vain yhdelle skeemalle, mutta {1} ovat kelvollisia
+oneOf.indexes = {0}: täytyy olla voimassa vain yhdelle skeemalle, mutta {1} ovat kelvollisia indeksien ''{2}'' kanssa
+pattern = {0}: ei vastaa säännöllisen lausekkeen mallia {1}
+patternProperties = {0}: siinä on virhe komennolla "pattern properties"
+prefixItems = {0}: tästä hakemistosta ei löytynyt vahvistusta
+properties = {0}: sisältää virheen ominaisuuksissa
+propertyNames = {0}: ominaisuuden ''{1}'' nimi ei kelpaa: {2}
+readOnly = {0}: on vain luku -kenttä, sitä ei voi muuttaa
+required = {0}: vaadittua ominaisuutta ''{1}'' ei löydy
+type = {0}: {1} löydetty, {2} odotettu
+unevaluatedItems = {0}: hakemistoa ''{1}'' ei arvioida, eikä skeema salli arvioimattomia kohteita
+unevaluatedProperties = {0}: ominaisuutta ''{1}'' ei arvioida ja skeema ei salli arvioimattomia ominaisuuksia
+unionType = {0}: {1} löydetty, {2} odotettu
+uniqueItems = {0}: taulukossa saa olla vain yksilöllisiä kohteita
+writeOnly = {0}: on vain kirjoituskenttä, se ei voi näkyä tiedoissa
+contentEncoding = {0}: ei vastaa sisällön koodausta {1}
+contentMediaType = {0}: ei ole sisältöni
diff --git a/src/main/resources/jsv-messages_fr.properties b/src/main/resources/jsv-messages_fr.properties
new file mode 100644
index 0000000..aff459e
--- /dev/null
+++ b/src/main/resources/jsv-messages_fr.properties
@@ -0,0 +1,70 @@
+$ref = {0}: il y a une erreur avec ''refs''
+additionalItems = {0}: l''index ''{1}'' n''est pas défini dans le schéma et le schéma n''autorise pas les éléments supplémentaires
+additionalProperties = {0}: la propriété ''{1}'' n''est pas définie dans le schéma et le schéma n''autorise pas de propriétés supplémentaires
+allOf = {0}: doit être valide pour tous les schémas {1}
+anyOf = {0}: doit être valide pour l''un des schémas {1}
+const = {0}: doit être la valeur constante ''{1}''
+contains = {0}: ne contient aucun élément qui réussit ces validations : {2}
+contains.max = {0}: doit contenir au plus {1} élément(s) qui réussissent ces validations : {2}
+contains.min = {0}: doit contenir au moins {1} élément(s) qui réussissent ces validations : {2}
+dependencies = {0}: il y a une erreur avec les dépendances {1}
+dependentRequired = {0}: a une propriété manquante « {1} » qui est dépendante requise car « {2} » est présente
+dependentSchemas = {0}: il y a une erreur avecdependentSchemas {1}
+enum = {0}: n''a pas de valeur dans l''énumération {1}
+exclusiveMaximum = {0}: doit avoir une valeur maximale exclusive de {1}
+exclusiveMinimum = {0}: doit avoir une valeur minimale exclusive de {1}
+false = {0}: le schéma de ''{1}'' est faux
+format = {0}: ne correspond pas au modèle {1} {2}
+format.date = {0}: ne correspond pas au modèle {1} doit être une date complète RFC 3339 valide
+format.date-time = {0}: ne correspond pas au modèle {1} doit être une date-heure RFC 3339 valide
+format.duration = {0}: ne correspond pas au modèle {1} doit être une durée ISO 8601 valide
+format.email = {0}: ne correspond pas au modèle {1} doit être une boîte aux lettres RFC 5321 valide
+format.ipv4 = {0}: ne correspond pas au modèle {1} doit être une adresse IP RFC 2673 valide
+format.ipv6 = {0}: ne correspond pas au modèle {1} doit être une adresse IP RFC 4291 valide
+format.idn-email = {0}: ne correspond pas au modèle {1} doit être une boîte aux lettres RFC 6531 valide
+format.idn-hostname = {0}: ne correspond pas au modèle {1} doit être un nom d''hôte internationalisé RFC 5890 valide
+format.iri = {0}: ne correspond pas au modèle {1} doit être un IRI RFC 3987 valide
+format.iri-reference = {0}: ne correspond pas au modèle {1} doit être une référence IRI RFC 3987 valide
+format.uri = {0}: ne correspond pas au modèle {1} doit être un URI RFC 3986 valide
+format.uri-reference = {0}: ne correspond pas au modèle {1} doit être une référence URI RFC 3986 valide
+format.uri-template = {0}: ne correspond pas au modèle {1} doit être un modèle d''URI RFC 6570 valide
+format.uuid = {0}: ne correspond pas au modèle {1} doit être un UUID RFC 4122 valide
+format.regex = {0}: ne correspond pas au modèle {1} doit être une expression régulière ECMA-262 valide
+format.time = {0}: ne correspond pas au modèle {1} doit être une heure RFC 3339 valide
+format.hostname = {0}: ne correspond pas au modèle {1} doit être un nom d''hôte RFC 1123 valide
+format.json-pointer = {0}: ne correspond pas au modèle {1} doit être un pointeur JSON RFC 6901 valide
+format.relative-json-pointer = {0}: ne correspond pas au modèle {1} doit être un pointeur JSON relatif IETF valide
+format.unknown = {0}: a un format inconnu ''{1}''
+id = {0}: ''{1}'' n''est pas un {2} valide
+items = {0}: l''index ''{1}'' n''est pas défini dans le schéma et le schéma n''autorise pas d''éléments supplémentaires
+maxContains = {0}: doit être un entier non négatif dans {1}
+maxItems = {0}: doit contenir au plus {1} éléments mais trouvé {2}
+maxLength = {0}: doit contenir au plus {1} caractères
+maxProperties = {0}: doit avoir au plus {1} propriétés
+maximum = {0}: doit avoir une valeur maximale de {1}
+minContains = {0}: doit être un entier non négatif dans {1}
+minContainsVsMaxContains = {0}: minContains doit être inférieur ou égal à maxContains dans {1}
+minItems = {0}: doit avoir au moins {1} éléments mais trouvé {2}
+minLength = {0}: doit contenir au moins {1} caractères
+minProperties = {0}: doit avoir au moins {1} propriétés
+minimum = {0}: doit avoir une valeur minimale de {1}
+multipleOf = {0}: doit être un multiple de {1}
+not = {0}: ne doit pas être valide pour le schéma {1}
+notAllowed = {0}: la propriété ''{1}'' n''est pas autorisée mais elle est dans les données
+oneOf = {0}: doit être valide pour un et un seul schéma, mais {1} sont valides
+oneOf.indexes = {0}: doit être valide pour un et un seul schéma, mais {1} sont valides avec les index ''{2}''
+pattern = {0}: ne correspond pas au modèle d''expression régulière {1}
+patternProperties = {0}: il y a une erreur avec les « propriétés du modèle »
+prefixItems = {0}: aucun validateur trouvé à cet index
+properties = {0}: il y a une erreur avec « propriétés »
+propertyNames = {0}: le nom de la propriété ''{1}'' n''est pas valide : {2}
+readOnly = {0}: est un champ en lecture seule, il ne peut pas être modifié
+required = {0}: propriété requise ''{1}'' introuvable
+type = {0}: {1} trouvé, {2} attendu
+unevaluatedItems = {0}: l''index ''{1}'' n''est pas évalué et le schéma n''autorise pas les éléments non évalués
+unevaluatedProperties = {0}: la propriété ''{1}'' n''est pas évaluée et le schéma n''autorise pas les propriétés non évaluées
+unionType = {0}: {1} trouvé, {2} attendu
+uniqueItems = {0}: ne doit avoir que des éléments uniques dans le tableau
+writeOnly = {0}: est un champ en écriture seule, il ne peut pas apparaître dans les données
+contentEncoding = {0}: ne correspond pas à l''encodage du contenu {1}
+contentMediaType = {0}: 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 0000000..d33a294
--- /dev/null
+++ b/src/main/resources/jsv-messages_he.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD ''refs''
+additionalItems = {0}: \u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{1}'' \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 = {0}: \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{1}'' \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 = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05DB\u05DC \u05D4\u05E1\u05DB\u05DE\u05D5\u05EA {1}
+anyOf = {0}: \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 {1}
+const = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D4\u05E2\u05E8\u05DA \u05D4\u05E7\u05D1\u05D5\u05E2 ''{1}''
+contains = {0}: \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: {2}
+contains.max = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05DB\u05D9\u05DC \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {1} \u05E8\u05DB\u05D9\u05D1\u05D9\u05DD \u05E9\u05E2\u05D5\u05D1\u05E8\u05D9\u05DD \u05D0\u05D9\u05DE\u05D5\u05EA\u05D9\u05DD \u05D0\u05DC\u05D4: {2}
+contains.min = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05DB\u05D9\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {1} \u05E8\u05DB\u05D9\u05D1\u05D9\u05DD \u05E9\u05E2\u05D5\u05D1\u05E8\u05D9\u05DD \u05D0\u05D9\u05DE\u05D5\u05EA\u05D9\u05DD \u05D0\u05DC\u05D4: {2}
+dependencies = {0}: \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD \u05EA\u05DC\u05D5\u05D9\u05D5\u05EA {1}
+dependentRequired = {0}: \u05D7\u05E1\u05E8 \u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{1}'' \u05D0\u05E9\u05E8 \u05D3\u05E8\u05D5\u05E9 \u05EA\u05DC\u05D5\u05D9 \u05DB\u05D9 ''{2}'' \u05E7\u05D9\u05D9\u05DD
+dependentSchemas = {0}: \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD dependentSchemas {1}
+enum = {0}: \u05D0\u05D9\u05DF \u05E2\u05E8\u05DA \u05D1\u05E1\u05E4\u05D9\u05E8\u05D4 {1}
+exclusiveMaximum = {0}: \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 {1}
+exclusiveMinimum = {0}: \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 {1}
+false = {0}: \u05D4\u05E1\u05DB\u05D9\u05DE\u05D4 \u05E2\u05D1\u05D5\u05E8 ''{1}'' \u05D4\u05D9\u05D0 \u05E9\u05E7\u05E8
+format = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} {2}
+format.date = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E9\u05DA ISO 8601 \u05D7\u05D5\u05E7\u05D9
+format.email = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05D3\u05E4\u05D5\u05E1 {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 3987 IRI \u05D7\u05D5\u05E7\u05D9
+format.iri-reference = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 3986 URI \u05D7\u05D5\u05E7\u05D9
+format.uri-reference = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA RFC 4122 UUID \u05D7\u05D5\u05E7\u05D9
+format.regex = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D6\u05DE\u05DF RFC 3339 \u05D7\u05D5\u05E7\u05D9
+format.hostname = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05E9\u05DD \u05DE\u05D0\u05E8\u05D7 RFC 1123 \u05D7\u05D5\u05E7\u05D9
+format.json-pointer = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05DC\u05EA\u05D1\u05E0\u05D9\u05EA {1} \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 = {0}: \u05D9\u05E9 \u05E4\u05D5\u05E8\u05DE\u05D8 \u05DC\u05D0 \u05D9\u05D3\u05D5\u05E2 ''{1}''
+id = {0}: ''{1}'' \u05D0\u05D9\u05E0\u05D5 {2} \u05D7\u05D5\u05E7\u05D9
+items = {0}: \u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{1}'' \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 = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E1\u05E4\u05E8 \u05E9\u05DC\u05DD \u05DC\u05D0 \u05E9\u05DC\u05D9\u05DC\u05D9 \u05D1-{1}
+maxItems = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05DB\u05DC\u05D5\u05DC \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {1} \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05D0\u05DA \u05E0\u05DE\u05E6\u05D0\u05D5 {2}
+maxLength = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05D0\u05D5\u05E8\u05DA \u05E9\u05DC {1} \u05EA\u05D5\u05D5\u05D9\u05DD \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8
+maxProperties = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05DB\u05DC \u05D4\u05D9\u05D5\u05EA\u05E8 {1} \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD
+maximum = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05E7\u05E1\u05D9\u05DE\u05DC\u05D9 \u05E9\u05DC {1}
+minContains = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DE\u05E1\u05E4\u05E8 \u05E9\u05DC\u05DD \u05DC\u05D0 \u05E9\u05DC\u05D9\u05DC\u05D9 \u05D1-{1}
+minContainsVsMaxContains = {0}: minContains \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05E7\u05D8\u05DF \u05D0\u05D5 \u05E9\u05D5\u05D5\u05D4 \u05DC-maxContains \u05D1-{1}
+minItems = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05DB\u05DC\u05D5\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {1} \u05E4\u05E8\u05D9\u05D8\u05D9\u05DD \u05D0\u05DA \u05E0\u05DE\u05E6\u05D0\u05D5 {2}
+minLength = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05D0\u05D5\u05E8\u05DA \u05E9\u05DC \u05DC\u05E4\u05D7\u05D5\u05EA {1} \u05EA\u05D5\u05D5\u05D9\u05DD
+minProperties = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05E4\u05D7\u05D5\u05EA {1} \u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD
+minimum = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D1\u05E2\u05DC \u05E2\u05E8\u05DA \u05DE\u05D9\u05E0\u05D9\u05DE\u05DC\u05D9 \u05E9\u05DC {1}
+multipleOf = {0}: \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DB\u05E4\u05D5\u05DC\u05D4 \u05E9\u05DC {1}
+not = {0}: \u05DC\u05D0 \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA \u05D7\u05D5\u05E7\u05D9 \u05DC\u05E1\u05DB\u05D9\u05DE\u05D4 {1}
+notAllowed = {0}: \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{1}'' \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 = {0}: \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 {1} \u05D7\u05D5\u05E7\u05D9\u05D9\u05DD
+oneOf.indexes = {0}: \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 {1} \u05EA\u05E7\u05E4\u05D9\u05DD \u05E2\u05DD \u05D4\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1\u05D9\u05DD ''{2}''
+pattern = {0}: \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 {1}
+patternProperties = {0}: \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 = {0}: \u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0 \u05DE\u05D0\u05DE\u05EA \u05D1\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 \u05D6\u05D4
+properties = {0}: \u05D9\u05E9 \u05E9\u05D2\u05D9\u05D0\u05D4 \u05E2\u05DD ''\u05DE\u05D0\u05E4\u05D9\u05D9\u05E0\u05D9\u05DD''
+propertyNames = {0}: \u05E9\u05DD \u05D4\u05E0\u05DB\u05E1 ''{1}'' \u05D0\u05D9\u05E0\u05D5 \u05D7\u05D5\u05E7\u05D9: {2}
+readOnly = {0}: \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 = {0}: \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF \u05D4\u05E0\u05D3\u05E8\u05E9 ''{1}'' \u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0
+type = {0}: {1} \u05E0\u05DE\u05E6\u05D0, {2} \u05E6\u05E4\u05D5\u05D9
+unevaluatedItems = {0}: \u05D4\u05D0\u05D9\u05E0\u05D3\u05E7\u05E1 ''{1}'' \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 = {0}: \u05D4\u05DE\u05D0\u05E4\u05D9\u05D9\u05DF ''{1}'' \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}: {1} \u05E0\u05DE\u05E6\u05D0, {2} \u05E6\u05E4\u05D5\u05D9
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \u05D0\u05D9\u05E0\u05D5 \u05EA\u05D5\u05D0\u05DD \u05D0\u05EA \u05E7\u05D9\u05D3\u05D5\u05D3 \u05D4\u05EA\u05D5\u05DB\u05DF {1}
+contentMediaType = {0}: \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 0000000..ec55eac
--- /dev/null
+++ b/src/main/resources/jsv-messages_hr.properties
@@ -0,0 +1,70 @@
+$ref = {0}: ima gre\u0161ku s ''refs''
+additionalItems = {0}: indeks ''{1}'' nije definiran u shemi i shema ne dopu\u0161ta dodatne stavke
+additionalProperties = {0}: svojstvo ''{1}'' nije definirano u shemi i shema ne dopu\u0161ta dodatna svojstva
+allOf = {0}: mora biti va\u017Ee\u0107e za sve sheme {1}
+anyOf = {0}: mora biti va\u017Ee\u0107e za bilo koju od shema {1}
+const = {0}: mora biti konstantna vrijednost ''{1}''
+contains = {0}: ne sadr\u017Ei element koji prolazi ove provjere: {2}
+contains.max = {0}: mora sadr\u017Eavati najvi\u0161e {1} elemenata koji prolaze ove provjere: {2}
+contains.min = {0}: mora sadr\u017Eavati najmanje {1} elementa koji prolaze ove provjere: {2}
+dependencies = {0}: ima pogre\u0161ku s ovisnostima {1}
+dependentRequired = {0}: nedostaje svojstvo ''{1}'' koje je ovisno potrebno jer je prisutan ''{2}''
+dependentSchemas = {0}: ima pogre\u0161ku s dependentSchemas {1}
+enum = {0}: nema vrijednost u enumeraciji {1}
+exclusiveMaximum = {0}: mora imati isklju\u010Divu maksimalnu vrijednost od {1}
+exclusiveMinimum = {0}: mora imati isklju\u010Divu minimalnu vrijednost od {1}
+false = {0}: shema za ''{1}'' je false
+format = {0}: ne odgovara {1} uzorku {2}
+format.date = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i puni datum RFC 3339
+format.date-time = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 3339 datum-vrijeme
+format.duration = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107e ISO 8601 trajanje
+format.email = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i po\u0161tanski sandu\u010Di\u0107 RFC 5321
+format.ipv4 = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107a RFC 2673 IP adresa
+format.ipv6 = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107a RFC 4291 IP adresa
+format.idn-email = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i po\u0161tanski sandu\u010Di\u0107 RFC 6531
+format.idn-hostname = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 5890 internacionalizirani naziv glavnog ra\u010Dunala
+format.iri = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 3987 IRI
+format.iri-reference = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107a RFC 3987 IRI referenca
+format.uri = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 3986 URI
+format.uri-reference = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107a RFC 3986 URI referenca
+format.uri-template = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 6570 URI predlo\u017Eak
+format.uuid = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 4122 UUID
+format.regex = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i regularni izraz ECMA-262
+format.time = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107e RFC 3339 vrijeme
+format.hostname = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107e RFC 1123 ime glavnog ra\u010Dunala
+format.json-pointer = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i RFC 6901 JSON pokaziva\u010D
+format.relative-json-pointer = {0}: ne odgovara uzorku {1} mora biti va\u017Ee\u0107i IETF relativni JSON pokaziva\u010D
+format.unknown = {0}: ima nepoznati format ''{1}''
+id = {0}: ''{1}'' nije valjan {2}
+items = {0}: indeks ''{1}'' nije definiran u shemi i shema ne dopu\u0161ta dodatne stavke
+maxContains = {0}: mora biti nenegativan cijeli broj u {1}
+maxItems = {0}: mora imati najvi\u0161e {1} stavki, ali prona\u0111eno {2}
+maxLength = {0}: mora imati najvi\u0161e {1} znakova
+maxProperties = {0}: mora imati najvi\u0161e {1} svojstava
+maximum = {0}: mora imati najve\u0107u vrijednost od {1}
+minContains = {0}: mora biti nenegativan cijeli broj u {1}
+minContainsVsMaxContains = {0}: minContains mora biti manji od ili jednak maxContains u {1}
+minItems = {0}: mora imati najmanje {1} stavki, ali prona\u0111eno {2}
+minLength = {0}: mora imati najmanje {1} znakova
+minProperties = {0}: mora imati najmanje {1} svojstava
+minimum = {0}: mora imati minimalnu vrijednost od {1}
+multipleOf = {0}: mora biti vi\u0161estruko od {1}
+not = {0}: ne smije biti va\u017Ee\u0107e za shemu {1}
+notAllowed = {0}: svojstvo ''{1}'' nije dopu\u0161teno, ali je u podacima
+oneOf = {0}: mora biti va\u017Ee\u0107e za jednu i samo jednu shemu, ali {1} su va\u017Ee\u0107e
+oneOf.indexes = {0}: mora biti va\u017Ee\u0107e za jednu i samo jednu shemu, ali {1} su va\u017Ee\u0107e s indeksima ''{2}''
+pattern = {0}: ne odgovara uzorku regularnog izraza {1}
+patternProperties = {0}: ima neke gre\u0161ke sa ''svojstvima uzorka''
+prefixItems = {0}: validator nije prona\u0111en u ovom indeksu
+properties = {0}: ima gre\u0161ku sa ''svojstvima''
+propertyNames = {0}: ime svojstva ''{1}'' nije va\u017Ee\u0107e: {2}
+readOnly = {0}: polje je samo za \u010Ditanje, ne mo\u017Ee se mijenjati
+required = {0}: potrebno svojstvo ''{1}'' nije prona\u0111eno
+type = {0}: {1} prona\u0111eno, {2} o\u010Dekivano
+unevaluatedItems = {0}: indeks ''{1}'' se ne procjenjuje i shema ne dopu\u0161ta neprocijenjene stavke
+unevaluatedProperties = {0}: svojstvo ''{1}'' se ne procjenjuje i shema ne dopu\u0161ta neprocijenjena svojstva
+unionType = {0}: {1} prona\u0111eno, {2} o\u010Dekivano
+uniqueItems = {0}: mora imati samo jedinstvene stavke u nizu
+writeOnly = {0}: polje je samo za pisanje, ne mo\u017Ee se pojaviti u podacima
+contentEncoding = {0}: ne odgovara kodiranju sadr\u017Eaja {1}
+contentMediaType = {0}: 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 0000000..88485c6
--- /dev/null
+++ b/src/main/resources/jsv-messages_hu.properties
@@ -0,0 +1,70 @@
+$ref = {0}: hibás a ''refs''
+additionalItems = {0}: a ''{1}'' index nincs megadva a sémában, és a séma nem engedélyez további elemeket
+additionalProperties = {0}: a ''{1}'' tulajdonság nincs megadva a sémában, és a séma nem engedélyez további tulajdonságokat
+allOf = {0}: érvényesnek kell lennie az összes sémára {1}
+anyOf = {0}: érvényesnek kell lennie a(z) {1} sémák bármelyikére
+const = {0}: a ''{1}'' állandó értéknek kell lennie
+contains = {0}: nem tartalmaz olyan elemet, amely átmegy a következ\u0151 ellen\u0151rzéseken: {2}
+contains.max = {0}: legfeljebb {1} olyan elemet tartalmazhat, amely megfelel a következ\u0151 ellen\u0151rzéseknek: {2}
+contains.min = {0}: tartalmaznia kell legalább {1} olyan elemet, amely átmegy a következ\u0151 ellen\u0151rzéseken: {2}
+dependencies = {0}: hiba van a(z) {1} függ\u0151ségekkel
+dependentRequired = {0}: hiányzik a(z) ''{1}'' tulajdonsága, amely szükséges, mert a ''{2}'' jelen van
+dependentSchemas = {0}: hibás a dependentSchemas {1}
+enum = {0}: nincs értéke a(z) {1} felsorolásban
+exclusiveMaximum = {0}: kizárólagos maximális értéke {1}
+exclusiveMinimum = {0}: kizárólagos minimális értéke {1}
+false = {0}: ''{1}'' séma hamis
+format = {0}: nem egyezik a {1} mintával {2}
+format.date = {0}: nem egyezik a(z) {1} mintával, érvényes RFC 3339 teljes dátumúnak kell lennie
+format.date-time = {0}: nem egyezik a {1} mintával, érvényes RFC 3339 dátum-id\u0151
+format.duration = {0}: nem egyezik a {1} mintával, érvényes ISO 8601 id\u0151tartamnak kell lennie
+format.email = {0}: nem egyezik a {1} mintával, érvényes RFC 5321-es postafióknak kell lennie
+format.ipv4 = {0}: nem egyezik a(z) {1} mintával, érvényes RFC 2673 IP-címnek kell lennie
+format.ipv6 = {0}: nem egyezik a {1} mintával, érvényes RFC 4291 IP-címnek kell lennie
+format.idn-email = {0}: nem egyezik a {1} mintával, érvényes RFC 6531-es postafióknak kell lennie
+format.idn-hostname = {0}: nem egyezik a(z) {1} mintával, érvényes RFC 5890 nemzetköziesített gazdagépnévnek kell lennie
+format.iri = {0}: nem egyezik a {1} mintával, érvényes RFC 3987 IRI-nek kell lennie
+format.iri-reference = {0}: nem egyezik a {1} mintával, érvényes RFC 3987 IRI-hivatkozásnak kell lennie
+format.uri = {0}: nem egyezik a {1} mintával, érvényes RFC 3986 URI-nek kell lennie
+format.uri-reference = {0}: nem egyezik a {1} mintával, érvényes RFC 3986 URI-hivatkozásnak kell lennie
+format.uri-template = {0}: nem egyezik a {1} mintával, érvényes RFC 6570 URI-sablonnak kell lennie
+format.uuid = {0}: nem egyezik a {1} mintával, érvényes RFC 4122 UUID-nek kell lennie
+format.regex = {0}: nem egyezik a {1} mintával, érvényes ECMA-262 reguláris kifejezésnek kell lennie
+format.time = {0}: nem egyezik a {1} mintával, érvényes RFC 3339 id\u0151nek kell lennie
+format.hostname = {0}: nem egyezik a(z) {1} mintával, érvényes RFC 1123 gazdagépnévnek kell lennie
+format.json-pointer = {0}: nem egyezik a {1} mintával, érvényes RFC 6901 JSON-mutatónak kell lennie
+format.relative-json-pointer = {0}: nem egyezik a {1} mintával, érvényes IETF relatív JSON-mutatónak kell lennie
+format.unknown = {0}: ismeretlen formátuma: ''{1}''
+id = {0}: a ''{1}'' nem érvényes {2}
+items = {0}: a ''{1}'' index nincs megadva a sémában, és a séma nem engedélyez további elemeket
+maxContains = {0}: egy nem negatív egész számnak kell lennie a következ\u0151ben: {1}
+maxItems = {0}: legfeljebb {1} elemet tartalmazhat, de {2} található
+maxLength = {0}: legfeljebb {1} karakter hosszúságú lehet
+maxProperties = {0}: legfeljebb {1} tulajdonsággal kell rendelkeznie
+maximum = {0}: maximum {1} értékkel kell rendelkeznie
+minContains = {0}: egy nem negatív egész számnak kell lennie a következ\u0151ben: {1}
+minContainsVsMaxContains = {0}: A minContains értékének kisebbnek vagy egyenl\u0151nek kell lennie, mint a maxContains a következ\u0151ben: {1}
+minItems = {0}: legalább {1} elemnek kell lennie, de {2} található
+minLength = {0}: legalább {1} karakter hosszúságúnak kell lennie
+minProperties = {0}: legalább {1} tulajdonsággal kell rendelkeznie
+minimum = {0}: legalább {1} értékkel kell rendelkeznie
+multipleOf = {0}: {1} többszörösének kell lennie
+not = {0}: nem érvényes a(z) {1} sémára
+notAllowed = {0}: a ''{1}'' tulajdonság nem engedélyezett, de benne van az adatokban
+oneOf = {0}: érvényesnek kell lennie egy és csak egy sémára, de a {1} érvényes
+oneOf.indexes = {0}: érvényesnek kell lennie egy és csak egy sémára, de a {1} érvényes a következ\u0151 indexekkel: ''{2}''
+pattern = {0}: nem egyezik a következ\u0151 regex mintával: {1}
+patternProperties = {0}: hibás a "minta tulajdonságai"
+prefixItems = {0}: ezen az indexen nem található érvényesít\u0151
+properties = {0}: hibás a "tulajdonságok"
+propertyNames = {0}: a(z) ''{1}'' tulajdonság neve érvénytelen: {2}
+readOnly = {0}: csak olvasható mez\u0151, nem módosítható
+required = {0}: a(z) ''{1}'' kötelez\u0151 tulajdonság nem található
+type = {0}: {1} található, {2} várható
+unevaluatedItems = {0}: a ''{1}'' index nincs kiértékelve, és a séma nem engedélyezi az értékeletlen elemeket
+unevaluatedProperties = {0}: a(z) ''{1}'' tulajdonság nincs kiértékelve, és a séma nem engedélyezi az értékeletlen tulajdonságokat
+unionType = {0}: {1} található, {2} várható
+uniqueItems = {0}: csak egyedi elemek lehetnek a tömbben
+writeOnly = {0}: csak írható mez\u0151, nem jelenhet meg az adatokban
+contentEncoding = {0}: nem egyezik a következ\u0151 tartalomkódolással: {1}
+contentMediaType = {0}: 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 0000000..87efce7
--- /dev/null
+++ b/src/main/resources/jsv-messages_it.properties
@@ -0,0 +1,70 @@
+$ref = {0}: ha un errore con ''refs''
+additionalItems = {0}: l''indice ''{1}'' non è definito nello schema e lo schema non consente elementi aggiuntivi
+additionalProperties = {0}: la proprietà ''{1}'' non è definita nello schema e lo schema non consente proprietà aggiuntive
+allOf = {0}: deve essere valido per tutti gli schemi {1}
+anyOf = {0}: deve essere valido per uno qualsiasi degli schemi {1}
+const = {0}: deve essere il valore costante ''{1}''
+contains = {0}: non contiene un elemento che supera queste convalide: {2}
+contains.max = {0}: deve contenere al massimo {1} elemento/i che supera queste convalide: {2}
+contains.min = {0}: deve contenere almeno {1} elemento/i che supera queste convalide: {2}
+dependencies = {0}: presenta un errore con le dipendenze {1}
+dependentRequired = {0}: ha una proprietà mancante ''{1}'' che è dipendente obbligatoria perché ''{2}'' è presente
+dependentSchemas = {0}: ha un errore con dipendentiSchemas {1}
+enum = {0}: non ha un valore nell''enumerazione {1}
+exclusiveMaximum = {0}: deve avere un valore massimo esclusivo di {1}
+exclusiveMinimum = {0}: deve avere un valore minimo esclusivo di {1}
+false = {0}: lo schema per ''{1}'' è falso
+format = {0}: non corrisponde al modello {1} {2}
+format.date = {0}: non corrisponde al modello {1} deve essere una data completa RFC 3339 valida
+format.date-time = {0}: non corrisponde al modello {1} deve essere una data-ora RFC 3339 valida
+format.duration = {0}: non corrisponde al modello {1} deve essere una durata ISO 8601 valida
+format.email = {0}: non corrisponde al modello {1} deve essere una casella di posta RFC 5321 valida
+format.ipv4 = {0}: non corrisponde al modello {1} deve essere un indirizzo IP RFC 2673 valido
+format.ipv6 = {0}: non corrisponde al modello {1} deve essere un indirizzo IP RFC 4291 valido
+format.idn-email = {0}: non corrisponde al modello {1} deve essere una casella di posta RFC 6531 valida
+format.idn-hostname = {0}: non corrisponde al modello {1} deve essere un nome host internazionalizzato RFC 5890 valido
+format.iri = {0}: non corrisponde al modello {1} deve essere un IRI RFC 3987 valido
+format.iri-reference = {0}: non corrisponde al modello {1} deve essere un riferimento IRI RFC 3987 valido
+format.uri = {0}: non corrisponde al modello {1} deve essere un URI RFC 3986 valido
+format.uri-reference = {0}: non corrisponde al modello {1} deve essere un riferimento URI RFC 3986 valido
+format.uri-template = {0}: non corrisponde al modello {1} deve essere un modello URI RFC 6570 valido
+format.uuid = {0}: non corrisponde al modello {1} deve essere un UUID RFC 4122 valido
+format.regex = {0}: non corrisponde al modello {1} deve essere un''espressione regolare ECMA-262 valida
+format.time = {0}: non corrisponde al modello {1} deve essere un''ora RFC 3339 valida
+format.hostname = {0}: non corrisponde al modello {1} deve essere un nome host RFC 1123 valido
+format.json-pointer = {0}: non corrisponde al modello {1} deve essere un puntatore JSON RFC 6901 valido
+format.relative-json-pointer = {0}: non corrisponde al modello {1} deve essere un puntatore JSON relativo IETF valido
+format.unknown = {0}: ha un formato sconosciuto ''{1}''
+id = {0}: ''{1}'' non è un {2} valido
+items = {0}: l''indice ''{1}'' non è definito nello schema e lo schema non consente elementi aggiuntivi
+maxContains = {0}: deve essere un numero intero non negativo in {1}
+maxItems = {0}: deve avere al massimo {1} elementi ma trovati {2}
+maxLength = {0}: deve contenere al massimo {1} caratteri
+maxProperties = {0}: deve avere al massimo {1} proprietà
+maximum = {0}: deve avere un valore massimo di {1}
+minContains = {0}: deve essere un numero intero non negativo in {1}
+minContainsVsMaxContains = {0}: minContains deve essere minore o uguale a maxContains in {1}
+minItems = {0}: deve avere almeno {1} elementi ma trovati {2}
+minLength = {0}: deve contenere almeno {1} caratteri
+minProperties = {0}: deve avere almeno {1} proprietà
+minimum = {0}: deve avere un valore minimo di {1}
+multipleOf = {0}: deve essere multiplo di {1}
+not = {0}: non deve essere valido per lo schema {1}
+notAllowed = {0}: la proprietà ''{1}'' non è consentita ma è nei dati
+oneOf = {0}: deve essere valido per uno e un solo schema, ma {1} sono validi
+oneOf.indexes = {0}: deve essere valido per uno e un solo schema, ma {1} sono validi con gli indici ''{2}''
+pattern = {0}: non corrisponde al pattern regex {1}
+patternProperties = {0}: presenta qualche errore con le ''proprietà del modello''
+prefixItems = {0}: nessun validatore trovato in questo indice
+properties = {0}: presenta un errore con ''proprietà''
+propertyNames = {0}: il nome della proprietà ''{1}'' non è valido: {2}
+readOnly = {0}: è un campo di sola lettura, non può essere modificato
+required = {0}: proprietà richiesta ''{1}'' non trovata
+type = {0}: {1} trovato, {2} previsto
+unevaluatedItems = {0}: l''indice ''{1}'' non viene valutato e lo schema non consente elementi non valutati
+unevaluatedProperties = {0}: la proprietà ''{1}'' non viene valutata e lo schema non consente proprietà non valutate
+unionType = {0}: {1} trovato, {2} previsto
+uniqueItems = {0}: deve contenere solo elementi univoci nell''array
+writeOnly = {0}: è un campo di sola scrittura, non può apparire nei dati
+contentEncoding = {0}: non corrisponde alla codifica del contenuto {1}
+contentMediaType = {0}: 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 0000000..15a6b6a
--- /dev/null
+++ b/src/main/resources/jsv-messages_ja.properties
@@ -0,0 +1,70 @@
+$ref = {0}: ''refs'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059
+additionalItems = {0}: \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{1}'' \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 = {0}: \u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \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 = {0}: \u3059\u3079\u3066\u306E\u30B9\u30AD\u30FC\u30DE {1} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+anyOf = {0}: \u3044\u305A\u308C\u304B\u306E\u30B9\u30AD\u30FC\u30DE {1} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+const = {0}: \u5B9A\u6570\u5024 ''{1}'' \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+contains = {0}: \u6B21\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u8981\u7D20\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093: {2}
+contains.max = {0}: \u3053\u308C\u3089\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u8981\u7D20\u306F\u6700\u5927\u3067\u3082 {1} \u500B\u542B\u307E\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {2}
+contains.min = {0}: \u3053\u308C\u3089\u306E\u691C\u8A3C\u306B\u5408\u683C\u3059\u308B\u5C11\u306A\u304F\u3068\u3082 {1} \u500B\u306E\u8981\u7D20\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {2}
+dependencies = {0}: \u4F9D\u5B58\u95A2\u4FC2 {1} \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059
+dependentRequired = {0}: \u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \u304C\u3042\u308A\u307E\u305B\u3093\u3002''{2}'' \u304C\u5B58\u5728\u3059\u308B\u305F\u3081\u3001\u4F9D\u5B58\u95A2\u4FC2\u304C\u5FC5\u8981\u3067\u3059\u3002
+dependentSchemas = {0}:dependentSchemas {1} \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059
+enum = {0}: \u5217\u6319\u578B {1} \u306B\u5024\u304C\u3042\u308A\u307E\u305B\u3093
+exclusiveMaximum = {0}: \u6392\u4ED6\u7684\u306A\u6700\u5927\u5024\u306F {1} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+exclusiveMinimum = {0}: \u6392\u4ED6\u7684\u306A\u6700\u5C0F\u5024\u306F {1} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+false = {0}: ''{1}'' \u306E\u30B9\u30AD\u30FC\u30DE\u306F false \u3067\u3059
+format = {0}: {1} \u30D1\u30BF\u30FC\u30F3 {2} \u306B\u4E00\u81F4\u3057\u307E\u305B\u3093
+format.date = {0}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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 = {0}: \u4E0D\u660E\u306A\u5F62\u5F0F ''{1}'' \u304C\u3042\u308A\u307E\u3059
+id = {0}: ''{1}'' \u306F\u6709\u52B9\u306A {2} \u3067\u306F\u3042\u308A\u307E\u305B\u3093
+items = {0}: \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{1}'' \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}: {1} \u306B\u306F\u8CA0\u3067\u306A\u3044\u6574\u6570\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+maxItems = {0}: \u30A2\u30A4\u30C6\u30E0\u306F\u6700\u5927\u3067\u3082 {1} \u500B\u5FC5\u8981\u3067\u3059\u304C\u3001{2} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F
+maxLength = {0}: \u9577\u3055\u306F\u6700\u5927 {1} \u6587\u5B57\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+maxProperties = {0}: \u6700\u5927\u3067 {1} \u500B\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u304C\u5FC5\u8981\u3067\u3059
+maximum = {0}: \u6700\u5927\u5024\u306F {1} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+minContains = {0}: {1} \u306B\u306F\u8CA0\u3067\u306A\u3044\u6574\u6570\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+minContainsVsMaxContains = {0}: minContains \u306F {1} \u306E maxContains \u4EE5\u4E0B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+minItems = {0}: \u5C11\u306A\u304F\u3068\u3082 {1} \u500B\u306E\u9805\u76EE\u304C\u5FC5\u8981\u3067\u3059\u304C\u3001{2} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F
+minLength = {0}: \u5C11\u306A\u304F\u3068\u3082 {1} \u6587\u5B57\u306E\u9577\u3055\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+minProperties = {0}: \u5C11\u306A\u304F\u3068\u3082 {1} \u500B\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u304C\u5FC5\u8981\u3067\u3059
+minimum = {0}: \u6700\u5C0F\u5024\u306F {1} \u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+multipleOf = {0}: {1} \u306E\u500D\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+not = {0}: \u30B9\u30AD\u30FC\u30DE {1} \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3042\u3063\u3066\u306F\u306A\u308A\u307E\u305B\u3093
+notAllowed = {0}: \u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \u306F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u304C\u3001\u30C7\u30FC\u30BF\u5185\u306B\u3042\u308A\u307E\u3059
+oneOf = {0}: 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{1} \u306F\u6709\u52B9\u3067\u3059
+oneOf.indexes = {0}: 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{1} \u306F\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{2}'' \u306B\u5BFE\u3057\u3066\u6709\u52B9\u3067\u3059
+pattern = {0}: \u6B63\u898F\u8868\u73FE\u30D1\u30BF\u30FC\u30F3 {1} \u306B\u4E00\u81F4\u3057\u307E\u305B\u3093
+patternProperties = {0}: ''\u30D1\u30BF\u30FC\u30F3 \u30D7\u30ED\u30D1\u30C6\u30A3'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059
+prefixItems = {0}: \u3053\u306E\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3067\u30D0\u30EA\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093
+properties = {0}: ''\u30D7\u30ED\u30D1\u30C6\u30A3'' \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059
+propertyNames = {0}: \u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \u306E\u540D\u524D\u304C\u7121\u52B9\u3067\u3059: {2}
+readOnly = {0}: \u306F\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u3059\u3002\u5909\u66F4\u3067\u304D\u307E\u305B\u3093
+required = {0}: \u5FC5\u9808\u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093
+type = {0}: {1} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3001{2} \u304C\u4E88\u671F\u3055\u308C\u307E\u3057\u305F
+unevaluatedItems = {0}: \u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ''{1}'' \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 = {0}: \u30D7\u30ED\u30D1\u30C6\u30A3 ''{1}'' \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}: {1} \u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3001{2} \u304C\u4E88\u671F\u3055\u308C\u307E\u3057\u305F
+uniqueItems = {0}: \u914D\u5217\u306B\u306F\u4E00\u610F\u306E\u9805\u76EE\u306E\u307F\u3092\u542B\u3081\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
+writeOnly = {0}: \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 = {0}: \u30B3\u30F3\u30C6\u30F3\u30C4 \u30A8\u30F3\u30B3\u30FC\u30C7\u30A3\u30F3\u30B0 {1} \u3068\u4E00\u81F4\u3057\u307E\u305B\u3093
+contentMediaType = {0}: \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 0000000..7c03d5d
--- /dev/null
+++ b/src/main/resources/jsv-messages_ko.properties
@@ -0,0 +1,70 @@
+$ref = {0}: ''refs''\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4.
+additionalItems = {0}: \uC0C9\uC778 ''{1}''\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}: ''{1}'' \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 = {0}: \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8\uC5D0 \uC720\uD6A8\uD574\uC57C \uD569\uB2C8\uB2E4. {1}
+anyOf = {0}: \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8 {1}\uC5D0 \uC720\uD6A8\uD574\uC57C \uD569\uB2C8\uB2E4.
+const = {0}: \uC0C1\uC218 \uAC12 ''{1}''\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+contains = {0}: \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD55C \uC694\uC18C\uB97C \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {2}
+contains.max = {0}: \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD558\uB294 \uCD5C\uB300 {1}\uAC1C\uC758 \uC694\uC18C\uB97C \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4: {2}
+contains.min = {0}: \uB2E4\uC74C \uC720\uD6A8\uC131 \uAC80\uC0AC\uB97C \uD1B5\uACFC\uD558\uB294 \uC694\uC18C\uAC00 \uCD5C\uC18C\uD55C {1}\uAC1C \uD3EC\uD568\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4: {2}
+dependencies = {0}: \uC885\uC18D\uC131 {1}\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4.
+dependentRequired = {0}: ''{2}''\uC774(\uAC00) \uC874\uC7AC\uD558\uAE30 \uB54C\uBB38\uC5D0 \uC885\uC18D \uD544\uC218\uC778 ''{1}'' \uC18D\uC131\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
+dependentSchemas = {0}:dependentSchemas {1}\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4.
+enum = {0}: \uC5F4\uAC70\uD615 {1}\uC5D0 \uAC12\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
+exclusiveMaximum = {0}: {1}\uC758 \uBC30\uD0C0\uC801 \uCD5C\uB300\uAC12\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4.
+exclusiveMinimum = {0}: {1}\uC758 \uBC30\uD0C0\uC801 \uCD5C\uC18C\uAC12\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4.
+false = {0}: ''{1}''\uC5D0 \uB300\uD55C \uC2A4\uD0A4\uB9C8\uAC00 false\uC785\uB2C8\uB2E4.
+format = {0}: {1} \uD328\uD134 {2}\uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
+format.date = {0}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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}: {1} \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 = {0}: \uC54C \uC218 \uC5C6\uB294 \uD615\uC2DD ''{1}''\uC774(\uAC00) \uC788\uC2B5\uB2C8\uB2E4.
+id = {0}: ''{1}''\uC740(\uB294) \uC720\uD6A8\uD55C {2}\uC774 \uC544\uB2D9\uB2C8\uB2E4.
+items = {0}: \uC0C9\uC778 ''{1}''\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}: {1}\uC5D0\uC11C \uC74C\uC218\uAC00 \uC544\uB2CC \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.
+maxItems = {0}: \uCD5C\uB300 {1}\uAC1C\uC758 \uD56D\uBAA9\uC774 \uC788\uC5B4\uC57C \uD558\uC9C0\uB9CC {2}\uAC1C\uB97C \uCC3E\uC558\uC2B5\uB2C8\uB2E4.
+maxLength = {0}: \uAE38\uC774\uB294 \uCD5C\uB300 {1}\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.
+maxProperties = {0}: \uCD5C\uB300 {1}\uAC1C\uC758 \uC18D\uC131\uC744 \uAC00\uC838\uC57C \uD569\uB2C8\uB2E4.
+maximum = {0}: \uCD5C\uB300\uAC12\uC740 {1}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+minContains = {0}: {1}\uC5D0\uC11C \uC74C\uC218\uAC00 \uC544\uB2CC \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.
+minContainsVsMaxContains = {0}: minContains\uB294 {1}\uC758 maxContains\uBCF4\uB2E4 \uC791\uAC70\uB098 \uAC19\uC544\uC57C \uD569\uB2C8\uB2E4.
+minItems = {0}: \uCD5C\uC18C {1}\uAC1C\uC758 \uD56D\uBAA9\uC774 \uC788\uC5B4\uC57C \uD558\uC9C0\uB9CC {2}\uAC1C\uB97C \uCC3E\uC558\uC2B5\uB2C8\uB2E4.
+minLength = {0}: \uAE38\uC774\uB294 {1}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+minProperties = {0}: \uCD5C\uC18C\uD55C {1}\uAC1C\uC758 \uC18D\uC131\uC774 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+minimum = {0}: \uCD5C\uC18C\uAC12\uC740 {1}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+multipleOf = {0}: {1}\uC758 \uBC30\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.
+not = {0}: \uC2A4\uD0A4\uB9C8 {1}\uC5D0 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544\uC57C \uD569\uB2C8\uB2E4.
+notAllowed = {0}: ''{1}'' \uC18D\uC131\uC740 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC9C0\uB9CC \uB370\uC774\uD130\uC5D0 \uC788\uC2B5\uB2C8\uB2E4.
+oneOf = {0}: \uD558\uB098\uC758 \uC2A4\uD0A4\uB9C8\uC5D0\uB9CC \uC720\uD6A8\uD574\uC57C \uD558\uC9C0\uB9CC {1}\uC740(\uB294) \uC720\uD6A8\uD569\uB2C8\uB2E4.
+oneOf.indexes = {0}: \uD558\uB098\uC758 \uC2A4\uD0A4\uB9C8\uC5D0\uB9CC \uC720\uD6A8\uD574\uC57C \uD558\uC9C0\uB9CC {1}\uC740(\uB294) \uC0C9\uC778 ''{2}''\uC5D0 \uC720\uD6A8\uD569\uB2C8\uB2E4.
+pattern = {0}: \uC815\uADDC\uC2DD \uD328\uD134 {1}\uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
+patternProperties = {0}: ''\uD328\uD134 \uC18D\uC131''\uC5D0 \uC77C\uBD80 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4.
+prefixItems = {0}: \uC774 \uC0C9\uC778\uC5D0\uC11C \uC720\uD6A8\uC131 \uAC80\uC0AC\uAE30\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+properties = {0}: ''\uC18D\uC131''\uC5D0 \uC624\uB958\uAC00 \uC788\uC2B5\uB2C8\uB2E4.
+propertyNames = {0}: ''{1}'' \uC18D\uC131 \uC774\uB984\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {2}
+readOnly = {0}: \uC77D\uAE30 \uC804\uC6A9 \uD544\uB4DC\uC774\uBBC0\uB85C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+required = {0}: \uD544\uC218 \uC18D\uC131 ''{1}''\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+type = {0}: {1} \uBC1C\uACAC, {2} \uC608\uC0C1
+unevaluatedItems = {0}: ''{1}'' \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}: ''{1}'' \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}: {1} \uBC1C\uACAC, {2} \uC608\uC0C1
+uniqueItems = {0}: \uBC30\uC5F4\uC5D0 \uACE0\uC720\uD55C \uD56D\uBAA9\uB9CC \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.
+writeOnly = {0}: \uC4F0\uAE30 \uC804\uC6A9 \uD544\uB4DC\uC774\uBBC0\uB85C \uB370\uC774\uD130\uC5D0 \uB098\uD0C0\uB0A0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+contentEncoding = {0}: \uCF58\uD150\uCE20 \uC778\uCF54\uB529 {1}\uACFC(\uC640) \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
+contentMediaType = {0}: \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 0000000..2a94370
--- /dev/null
+++ b/src/main/resources/jsv-messages_nb.properties
@@ -0,0 +1,70 @@
+$ref = {0}: har en feil med ''refs''
+additionalItems = {0}: indeks ''{1}'' er ikke definert i skjemaet og skjemaet tillater ikke flere elementer
+additionalProperties = {0}: egenskapen ''{1}'' er ikke definert i skjemaet og skjemaet tillater ikke ytterligere egenskaper
+allOf = {0}: må være gyldig for alle skjemaene {1}
+anyOf = {0}: må være gyldig for alle skjemaene {1}
+const = {0}: må være konstantverdien ''{1}''
+contains = {0}: inneholder ikke et element som består disse valideringene: {2}
+contains.max = {0}: må inneholde maksimalt {1} element(er) som består disse valideringene: {2}
+contains.min = {0}: må inneholde minst {1} element(er) som består disse valideringene: {2}
+dependencies = {0}: har en feil med avhengigheter {1}
+dependentRequired = {0}: har en manglende egenskap ''{1}'' som er avhengig nødvendig fordi ''{2}'' er tilstede
+dependentSchemas = {0}: har en feil med dependentSchemas {1}
+enum = {0}: har ikke en verdi i oppregningen {1}
+exclusiveMaximum = {0}: må ha en eksklusiv maksimumsverdi på {1}
+exclusiveMinimum = {0}: må ha en eksklusiv minimumsverdi på {1}
+false = {0}: skjemaet for ''{1}'' er usant
+format = {0}: samsvarer ikke med {1}-mønsteret {2}
+format.date = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3339 full-dato
+format.date-time = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3339 dato-klokkeslett
+format.duration = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig ISO 8601-varighet
+format.email = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 5321-postboks
+format.ipv4 = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 2673 IP-adresse
+format.ipv6 = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 4291 IP-adresse
+format.idn-email = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 6531-postboks
+format.idn-hostname = {0}: samsvarer ikke med {1}-mønsteret må være et gyldig RFC 5890 internasjonalisert vertsnavn
+format.iri = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3987 IRI
+format.iri-reference = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3987 IRI-referanse
+format.uri = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3986 URI
+format.uri-reference = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3986 URI-referanse
+format.uri-template = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 6570 URI-mal
+format.uuid = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 4122 UUID
+format.regex = {0}: samsvarer ikke med {1}-mønsteret må være et gyldig ECMA-262 regulært uttrykk
+format.time = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 3339-tid
+format.hostname = {0}: samsvarer ikke med {1}-mønsteret må være et gyldig RFC 1123-vertsnavn
+format.json-pointer = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig RFC 6901 JSON-peker
+format.relative-json-pointer = {0}: samsvarer ikke med {1}-mønsteret må være en gyldig IETF relativ JSON-peker
+format.unknown = {0}: har et ukjent format ''{1}''
+id = {0}: ''{1}'' er ikke en gyldig {2}
+items = {0}: indeks ''{1}'' er ikke definert i skjemaet, og skjemaet tillater ikke flere elementer
+maxContains = {0}: må være et ikke-negativt heltall i {1}
+maxItems = {0}: må ha maksimalt {1} varer, men fant {2}
+maxLength = {0}: må bestå av maksimalt {1} tegn
+maxProperties = {0}: må ha maksimalt {1} egenskaper
+maximum = {0}: må ha en maksimal verdi på {1}
+minContains = {0}: må være et ikke-negativt heltall i {1}
+minContainsVsMaxContains = {0}: minContains må være mindre enn eller lik maxContains i {1}
+minItems = {0}: må ha minst {1} elementer, men fant {2}
+minLength = {0}: må bestå av minst {1} tegn
+minProperties = {0}: må ha minst {1} egenskaper
+minimum = {0}: må ha en minimumsverdi på {1}
+multipleOf = {0}: må være multiplum av {1}
+not = {0}: må ikke være gyldig for skjemaet {1}
+notAllowed = {0}: egenskapen ''{1}'' er ikke tillatt, men den er i dataene
+oneOf = {0}: må være gyldig for ett og bare ett skjema, men {1} er gyldige
+oneOf.indexes = {0}: må være gyldig for ett og bare ett skjema, men {1} er gyldige med indeksene ''{2}''
+pattern = {0}: samsvarer ikke med regex-mønsteret {1}
+patternProperties = {0}: har en feil med ''mønsteregenskaper''
+prefixItems = {0}: ingen validator funnet i denne indeksen
+properties = {0}: har en feil med ''egenskaper''
+propertyNames = {0}: egenskapen ''{1}'' navn er ikke gyldig: {2}
+readOnly = {0}: er et skrivebeskyttet felt, det kan ikke endres
+required = {0}: påkrevd egenskap ''{1}'' ikke funnet
+type = {0}: {1} funnet, {2} forventet
+unevaluatedItems = {0}: indeks ''{1}'' er ikke evaluert og skjemaet tillater ikke uevaluerte elementer
+unevaluatedProperties = {0}: egenskapen ''{1}'' er ikke evaluert og skjemaet tillater ikke uevaluerte egenskaper
+unionType = {0}: {1} funnet, {2} forventet
+uniqueItems = {0}: må bare ha unike elementer i matrisen
+writeOnly = {0}: er et skrivebeskyttet felt, det kan ikke vises i dataene
+contentEncoding = {0}: samsvarer ikke med innholdskoding {1}
+contentMediaType = {0}: 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 0000000..bf77c37
--- /dev/null
+++ b/src/main/resources/jsv-messages_nl.properties
@@ -0,0 +1,70 @@
+$ref = {0}: bevat een fout met ''refs''
+additionalItems = {0}: index ''{1}'' is niet gedefinieerd in het schema en het schema staat geen extra items toe
+additionalProperties = {0}: eigenschap ''{1}'' is niet gedefinieerd in het schema en het schema staat geen aanvullende eigenschappen toe
+allOf = {0}: moet geldig zijn voor alle schema''s {1}
+anyOf = {0}: moet geldig zijn voor elk van de schema''s {1}
+const = {0}: moet de constante waarde ''{1}'' zijn
+contains = {0}: bevat geen element dat deze validaties doorstaat: {2}
+contains.max = {0}: moet maximaal {1} element(en) bevatten die aan deze validaties voldoen: {2}
+contains.min = {0}: moet minimaal {1} element(en) bevatten die aan deze validaties voldoen: {2}
+dependencies = {0}: bevat een fout met afhankelijkheden {1}
+dependentRequired = {0}: heeft een ontbrekende eigenschap ''{1}'' die afhankelijk is vereist omdat ''{2}'' aanwezig is
+dependentSchemas = {0}: bevat een fout met dependSchemas {1}
+enum = {0}: heeft geen waarde in de opsomming {1}
+exclusiveMaximum = {0}: moet een exclusieve maximumwaarde hebben van {1}
+exclusiveMinimum = {0}: moet een exclusieve minimumwaarde hebben van {1}
+false = {0}: schema voor ''{1}'' is false
+format = {0}: komt niet overeen met het {1} patroon {2}
+format.date = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3339-volledige datum zijn
+format.date-time = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3339-datum-tijd zijn
+format.duration = {0}: komt niet overeen met het patroon {1} moet een geldige ISO 8601-duur hebben
+format.email = {0}: komt niet overeen met het patroon {1} moet een geldige RFC 5321-mailbox zijn
+format.ipv4 = {0}: komt niet overeen met het patroon {1} moet een geldig RFC 2673 IP-adres zijn
+format.ipv6 = {0}: komt niet overeen met het patroon {1} moet een geldig RFC 4291 IP-adres zijn
+format.idn-email = {0}: komt niet overeen met het patroon {1} moet een geldige RFC 6531-mailbox zijn
+format.idn-hostname = {0}: komt niet overeen met het patroon {1} moet een geldige geïnternationaliseerde RFC 5890-hostnaam zijn
+format.iri = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3987 IRI zijn
+format.iri-reference = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3987 IRI-referentie zijn
+format.uri = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3986-URI zijn
+format.uri-reference = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3986 URI-referentie zijn
+format.uri-template = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 6570 URI-sjabloon zijn
+format.uuid = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 4122 UUID zijn
+format.regex = {0}: komt niet overeen met het patroon {1} moet een geldige reguliere ECMA-262-expressie zijn
+format.time = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 3339-tijd zijn
+format.hostname = {0}: komt niet overeen met het patroon {1} moet een geldige RFC 1123-hostnaam zijn
+format.json-pointer = {0}: komt niet overeen met het {1}-patroon moet een geldige RFC 6901 JSON-aanwijzer zijn
+format.relative-json-pointer = {0}: komt niet overeen met het {1}-patroon moet een geldige IETF relatieve JSON-aanwijzer zijn
+format.unknown = {0}: heeft een onbekend formaat ''{1}''
+id = {0}: ''{1}'' is geen geldige {2}
+items = {0}: index ''{1}'' is niet gedefinieerd in het schema en het schema staat geen extra items toe
+maxContains = {0}: moet een niet-negatief geheel getal zijn in {1}
+maxItems = {0}: moet maximaal {1} items bevatten, maar heeft {2} gevonden
+maxLength = {0}: mag maximaal {1} tekens lang zijn
+maxProperties = {0}: moet maximaal {1} eigenschappen hebben
+maximum = {0}: moet een maximale waarde van {1} hebben
+minContains = {0}: moet een niet-negatief geheel getal zijn in {1}
+minContainsVsMaxContains = {0}: minContains moet kleiner zijn dan of gelijk zijn aan maxContains in {1}
+minItems = {0}: moet minstens {1} items hebben, maar {2} gevonden
+minLength = {0}: moet minimaal {1} tekens lang zijn
+minProperties = {0}: moet minimaal {1} eigenschappen hebben
+minimum = {0}: moet een minimumwaarde van {1} hebben
+multipleOf = {0}: moet een veelvoud zijn van {1}
+not = {0}: mag niet geldig zijn voor het schema {1}
+notAllowed = {0}: eigenschap ''{1}'' is niet toegestaan, maar staat wel in de gegevens
+oneOf = {0}: moet geldig zijn voor slechts één schema, maar {1} zijn geldig
+oneOf.indexes = {0}: moet geldig zijn voor één en slechts één schema, maar {1} zijn geldig met indexen ''{2}''
+pattern = {0}: komt niet overeen met het regex-patroon {1}
+patternProperties = {0}: bevat een fout met ''patrooneigenschappen''
+prefixItems = {0}: geen validator gevonden bij deze index
+properties = {0}: bevat een fout met ''properties''
+propertyNames = {0}: eigenschap ''{1}'' naam is niet geldig: {2}
+readOnly = {0}: is een alleen-lezen veld, dit kan niet worden gewijzigd
+required = {0}: vereiste eigenschap ''{1}'' niet gevonden
+type = {0}: {1} gevonden, {2} verwacht
+unevaluatedItems = {0}: index ''{1}'' wordt niet geëvalueerd en het schema staat geen niet-geëvalueerde items toe
+unevaluatedProperties = {0}: eigenschap ''{1}'' wordt niet geëvalueerd en het schema staat geen niet-geëvalueerde eigenschappen toe
+unionType = {0}: {1} gevonden, {2} verwacht
+uniqueItems = {0}: mag alleen unieke items in de array bevatten
+writeOnly = {0}: is een alleen-schrijven-veld, het kan niet voorkomen in de gegevens
+contentEncoding = {0}: komt niet overeen met inhoudscodering {1}
+contentMediaType = {0}: 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 0000000..254477b
--- /dev/null
+++ b/src/main/resources/jsv-messages_pl.properties
@@ -0,0 +1,70 @@
+$ref = {0}: zawiera b\u0142\u0105d z \u201Erefami\u201D
+additionalItems = {0}: indeks \u201E{1}\u201D nie jest zdefiniowany w schemacie i schemat nie pozwala na dodatkowe elementy
+additionalProperties = {0}: w\u0142a\u015Bciwo\u015B\u0107 \u201E{1}\u201D nie jest zdefiniowana w schemacie i schemat nie pozwala na dodatkowe w\u0142a\u015Bciwo\u015Bci
+allOf = {0}: musi by\u0107 poprawny dla wszystkich schematów {1}
+anyOf = {0}: musi by\u0107 poprawny dla dowolnego schematu {1}
+const = {0}: musi by\u0107 sta\u0142\u0105 warto\u015Bci\u0105 \u201E{1}\u201D
+contains = {0}: nie zawiera elementu, który przechodzi te weryfikacje: {2}
+contains.max = {0}: musi zawiera\u0107 maksymalnie {1} elementów, które przechodz\u0105 t\u0119 weryfikacj\u0119: {2}
+contains.min = {0}: musi zawiera\u0107 co najmniej {1} elementów, które przechodz\u0105 t\u0119 weryfikacj\u0119: {2}
+dependencies = {0}: zawiera b\u0142\u0105d z zale\u017Cno\u015Bciami {1}
+dependentRequired = {0}: ma brakuj\u0105c\u0105 w\u0142a\u015Bciwo\u015B\u0107 \u201E{1}\u201D, która jest zale\u017Cna i wymagana, poniewa\u017C wyst\u0119puje \u201E{2}\u201D
+dependentSchemas = {0}: zawiera b\u0142\u0105d w schematach zale\u017Cnych {1}
+enum = {0}: nie ma warto\u015Bci w wyliczeniu {1}
+exclusiveMaximum = {0}: musi mie\u0107 wy\u0142\u0105czn\u0105 warto\u015B\u0107 maksymaln\u0105 wynosz\u0105c\u0105 {1}
+exclusiveMinimum = {0}: musi mie\u0107 wy\u0142\u0105czn\u0105 minimaln\u0105 warto\u015B\u0107 {1}
+false = {0}: schemat dla \u201E{1}\u201D jest fa\u0142szywy
+format = {0}: nie pasuje do wzorca {1} {2}
+format.date = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 pe\u0142n\u0105 dat\u0105 RFC 3339
+format.date-time = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 dat\u0105 i godzin\u0105 RFC 3339
+format.duration = {0}: nie pasuje do wzorca {1}, musi mie\u0107 prawid\u0142owy czas trwania ISO 8601
+format.email = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 skrzynk\u0105 pocztow\u0105 RFC 5321
+format.ipv4 = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym adresem IP RFC 2673
+format.ipv6 = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym adresem IP RFC 4291
+format.idn-email = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 skrzynk\u0105 pocztow\u0105 RFC 6531
+format.idn-hostname = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 mi\u0119dzynarodow\u0105 nazw\u0105 hosta zgodn\u0105 z RFC 5890
+format.iri = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym IRI RFC 3987
+format.iri-reference = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym odwo\u0142aniem IRI RFC 3987
+format.uri = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym identyfikatorem URI RFC 3986
+format.uri-reference = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym odwo\u0142aniem URI RFC 3986
+format.uri-template = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym szablonem URI RFC 6570
+format.uuid = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym identyfikatorem UUID RFC 4122
+format.regex = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym wyra\u017Ceniem regularnym ECMA-262
+format.time = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym czasem RFC 3339
+format.hostname = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142ow\u0105 nazw\u0105 hosta zgodn\u0105 z RFC 1123
+format.json-pointer = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym wska\u017Anikiem JSON RFC 6901
+format.relative-json-pointer = {0}: nie pasuje do wzorca {1} musi by\u0107 prawid\u0142owym wzgl\u0119dnym wska\u017Anikiem JSON IETF
+format.unknown = {0}: ma nieznany format \u201E{1}\u201D
+id = {0}: \u201E{1}\u201D nie jest prawid\u0142owym {2}
+items = {0}: indeks ''{1}'' nie jest zdefiniowany w schemacie i schemat nie pozwala na dodatkowe elementy
+maxContains = {0}: musi by\u0107 nieujemn\u0105 liczb\u0105 ca\u0142kowit\u0105 w {1}
+maxItems = {0}: musi mie\u0107 co najwy\u017Cej {1} elementów, ale znaleziono {2}
+maxLength = {0}: musi mie\u0107 maksymalnie {1} znaków
+maxProperties = {0}: musi mie\u0107 co najwy\u017Cej {1} w\u0142a\u015Bciwo\u015Bci
+maximum = {0}: musi mie\u0107 maksymaln\u0105 warto\u015B\u0107 {1}
+minContains = {0}: musi by\u0107 nieujemn\u0105 liczb\u0105 ca\u0142kowit\u0105 w {1}
+minContainsVsMaxContains = {0}: minContains musi by\u0107 mniejsze lub równe maxContains w {1}
+minItems = {0}: musi mie\u0107 co najmniej {1} elementów, ale znaleziono {2}
+minLength = {0}: musi mie\u0107 co najmniej {1} znaków
+minProperties = {0}: musi mie\u0107 co najmniej {1} w\u0142a\u015Bciwo\u015Bci
+minimum = {0}: musi mie\u0107 minimaln\u0105 warto\u015B\u0107 {1}
+multipleOf = {0}: musi by\u0107 wielokrotno\u015Bci\u0105 {1}
+not = {0}: nie mo\u017Ce by\u0107 poprawny dla schematu {1}
+notAllowed = {0}: w\u0142a\u015Bciwo\u015B\u0107 \u201E{1}\u201D jest niedozwolona, ale znajduje si\u0119 w danych
+oneOf = {0}: musi by\u0107 poprawny dla jednego i tylko jednego schematu, ale {1} s\u0105 prawid\u0142owe
+oneOf.indexes = {0}: musi by\u0107 poprawny dla jednego i tylko jednego schematu, ale {1} jest prawid\u0142owe z indeksami \u201E{2}\u201D
+pattern = {0}: nie pasuje do wzorca wyra\u017Cenia regularnego {1}
+patternProperties = {0}: zawiera b\u0142\u0105d dotycz\u0105cy \u201Ew\u0142a\u015Bciwo\u015Bci wzorca\u201D
+prefixItems = {0}: w tym indeksie nie znaleziono walidatora
+properties = {0}: zawiera b\u0142\u0105d dotycz\u0105cy \u201Ew\u0142a\u015Bciwo\u015Bci\u201D
+propertyNames = {0}: nazwa w\u0142a\u015Bciwo\u015Bci \u201E{1}\u201D jest nieprawid\u0142owa: {2}
+readOnly = {0}: jest polem tylko do odczytu, nie mo\u017Cna go zmieni\u0107
+required = {0}: nie znaleziono wymaganej w\u0142a\u015Bciwo\u015Bci \u201E{1}\u201D.
+type = {0}: Znaleziono {1}, oczekiwano {2}
+unevaluatedItems = {0}: indeks \u201E{1}\u201D nie jest oceniany i schemat nie pozwala na nieocenione elementy
+unevaluatedProperties = {0}: w\u0142a\u015Bciwo\u015B\u0107 \u201E{1}\u201D nie jest oceniana i schemat nie pozwala na nieocenione w\u0142a\u015Bciwo\u015Bci
+unionType = {0}: Znaleziono {1}, oczekiwano {2}
+uniqueItems = {0}: tablica musi zawiera\u0107 tylko unikalne elementy
+writeOnly = {0}: jest polem tylko do zapisu, nie mo\u017Ce pojawia\u0107 si\u0119 w danych
+contentEncoding = {0}: nie pasuje do kodowania tre\u015Bci {1}
+contentMediaType = {0}: 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 0000000..ae3ebc1
--- /dev/null
+++ b/src/main/resources/jsv-messages_pt.properties
@@ -0,0 +1,70 @@
+$ref = {0}: tem um erro com ''refs''
+additionalItems = {0}: o índice ''{1}'' não está definido no esquema e o esquema não permite itens adicionais
+additionalProperties = {0}: a propriedade ''{1}'' não está definida no esquema e o esquema não permite propriedades adicionais
+allOf = {0}: deve ser válido para todos os esquemas {1}
+anyOf = {0}: deve ser válido para qualquer um dos esquemas {1}
+const = {0}: deve ser o valor constante ''{1}''
+contains = {0}: não contém um elemento que passe nestas validações: {2}
+contains.max = {0}: deve conter no máximo {1} elemento(s) que passe(m) nestas validações: {2}
+contains.min = {0}: deve conter pelo menos {1} elemento(s) que passe(m) nestas validações: {2}
+dependencies = {0}: há um erro com dependências {1}
+dependentRequired = {0}: tem uma propriedade ausente ''{1}'' que é dependente necessária porque ''{2}'' está presente
+dependentSchemas = {0}: há um erro com dependenteSchemas {1}
+enum = {0}: não possui valor na enumeração {1}
+exclusiveMaximum = {0}: deve ter um valor máximo exclusivo de {1}
+exclusiveMinimum = {0}: deve ter um valor mínimo exclusivo de {1}
+false = {0}: o esquema para ''{1}'' é falso
+format = {0}: não corresponde ao padrão {1} {2}
+format.date = {0}: não corresponde ao padrão {1} deve ser uma data completa RFC 3339 válida
+format.date-time = {0}: não corresponde ao padrão {1} deve ser uma data e hora RFC 3339 válida
+format.duration = {0}: não corresponde ao padrão {1} deve ter uma duração ISO 8601 válida
+format.email = {0}: não corresponde ao padrão {1} deve ser uma caixa de correio RFC 5321 válida
+format.ipv4 = {0}: não corresponde ao padrão {1} deve ser um endereço IP RFC 2673 válido
+format.ipv6 = {0}: não corresponde ao padrão {1} deve ser um endereço IP RFC 4291 válido
+format.idn-email = {0}: não corresponde ao padrão {1} deve ser uma caixa de correio RFC 6531 válida
+format.idn-hostname = {0}: não corresponde ao padrão {1} deve ser um nome de host internacionalizado RFC 5890 válido
+format.iri = {0}: não corresponde ao padrão {1} deve ser um RFC 3987 IRI válido
+format.iri-reference = {0}: não corresponde ao padrão {1} deve ser uma referência IRI RFC 3987 válida
+format.uri = {0}: não corresponde ao padrão {1} deve ser um URI RFC 3986 válido
+format.uri-reference = {0}: não corresponde ao padrão {1} deve ser uma referência de URI RFC 3986 válida
+format.uri-template = {0}: não corresponde ao padrão {1} deve ser um modelo de URI RFC 6570 válido
+format.uuid = {0}: não corresponde ao padrão {1} deve ser um UUID RFC 4122 válido
+format.regex = {0}: não corresponde ao padrão {1} deve ser uma expressão regular ECMA-262 válida
+format.time = {0}: não corresponde ao padrão {1} deve ser um horário RFC 3339 válido
+format.hostname = {0}: não corresponde ao padrão {1} deve ser um nome de host RFC 1123 válido
+format.json-pointer = {0}: não corresponde ao padrão {1} deve ser um ponteiro JSON RFC 6901 válido
+format.relative-json-pointer = {0}: não corresponde ao padrão {1} deve ser um ponteiro JSON relativo IETF válido
+format.unknown = {0}: tem um formato desconhecido ''{1}''
+id = {0}: ''{1}'' não é um {2} válido
+items = {0}: o índice ''{1}'' não está definido no esquema e o esquema não permite itens adicionais
+maxContains = {0}: deve ser um número inteiro não negativo em {1}
+maxItems = {0}: deve ter no máximo {1} itens, mas foram encontrados {2}
+maxLength = {0}: deve ter no máximo {1} caracteres
+maxProperties = {0}: deve ter no máximo {1} propriedades
+maximum = {0}: deve ter um valor máximo de {1}
+minContains = {0}: deve ser um número inteiro não negativo em {1}
+minContainsVsMaxContains = {0}: minContains deve ser menor ou igual a maxContains em {1}
+minItems = {0}: deve ter pelo menos {1} itens, mas foi encontrado {2}
+minLength = {0}: deve ter pelo menos {1} caracteres
+minProperties = {0}: deve ter pelo menos {1} propriedades
+minimum = {0}: deve ter um valor mínimo de {1}
+multipleOf = {0}: deve ser múltiplo de {1}
+not = {0}: não deve ser válido para o esquema {1}
+notAllowed = {0}: propriedade ''{1}'' não é permitida, mas está nos dados
+oneOf = {0}: deve ser válido para um e apenas um esquema, mas {1} são válidos
+oneOf.indexes = {0}: deve ser válido para um e somente um esquema, mas {1} são válidos com índices ''{2}''
+pattern = {0}: não corresponde ao padrão regex {1}
+patternProperties = {0}: há algum erro com ''propriedades do padrão''
+prefixItems = {0}: nenhum validador encontrado neste índice
+properties = {0}: há um erro com ''propriedades''
+propertyNames = {0}: o nome da propriedade ''{1}'' não é válido: {2}
+readOnly = {0}: é um campo somente leitura, não pode ser alterado
+required = {0}: propriedade obrigatória ''{1}'' não encontrada
+type = {0}: {1} encontrado, {2} esperado
+unevaluatedItems = {0}: o índice ''{1}'' não é avaliado e o esquema não permite itens não avaliados
+unevaluatedProperties = {0}: a propriedade ''{1}'' não foi avaliada e o esquema não permite propriedades não avaliadas
+unionType = {0}: {1} encontrado, {2} esperado
+uniqueItems = {0}: deve ter apenas itens únicos no array
+writeOnly = {0}: é um campo somente gravação, não pode aparecer nos dados
+contentEncoding = {0}: não corresponde à codificação de conteúdo {1}
+contentMediaType = {0}: não é um conteúdo 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 0000000..3ade90c
--- /dev/null
+++ b/src/main/resources/jsv-messages_ro.properties
@@ -0,0 +1,70 @@
+$ref = {0}: are o eroare cu \u201Erefs\u201D
+additionalItems = {0}: indexul \u201E{1}\u201D nu este definit în schem\u0103, iar schema nu permite elemente suplimentare
+additionalProperties = {0}: proprietatea \u201E{1}\u201D nu este definit\u0103 în schem\u0103, iar schema nu permite propriet\u0103\u021Bi suplimentare
+allOf = {0}: trebuie s\u0103 fie valid pentru toate schemele {1}
+anyOf = {0}: trebuie s\u0103 fie valid pentru oricare dintre schemele {1}
+const = {0}: trebuie s\u0103 fie valoarea constant\u0103 \u201E{1}\u201D
+contains = {0}: nu con\u0163ine un element care trece aceste valid\u0103ri: {2}
+contains.max = {0}: trebuie s\u0103 con\u021Bin\u0103 cel mult {1} element(e) care trece aceste valid\u0103ri: {2}
+contains.min = {0}: trebuie s\u0103 con\u021Bin\u0103 cel pu\u021Bin {1} element(e) care trece aceste valid\u0103ri: {2}
+dependencies = {0}: are o eroare cu dependen\u021Bele {1}
+dependentRequired = {0}: are o proprietate lips\u0103 ''{1}'' care este dependent\u0103 necesar\u0103 deoarece ''{2}'' este prezent
+dependentSchemas = {0}: are o eroare cu dependentSchemas {1}
+enum = {0}: nu are o valoare în enumerarea {1}
+exclusiveMaximum = {0}: trebuie s\u0103 aib\u0103 o valoare maxim\u0103 exclusiv\u0103 de {1}
+exclusiveMinimum = {0}: trebuie s\u0103 aib\u0103 o valoare minim\u0103 exclusiv\u0103 de {1}
+false = {0}: schema pentru \u201E{1}\u201D este fals\u0103
+format = {0}: nu se potrive\u0219te cu modelul {1} {2}
+format.date = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o dat\u0103 complet\u0103 RFC 3339 valid\u0103
+format.date-time = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un RFC 3339 data-ora valid
+format.duration = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o durat\u0103 ISO 8601 valid\u0103
+format.email = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o cutie po\u0219tal\u0103 RFC 5321 valid\u0103
+format.ipv4 = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o adres\u0103 IP RFC 2673 valid\u0103
+format.ipv6 = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o adres\u0103 IP RFC 4291 valid\u0103
+format.idn-email = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o cutie po\u0219tal\u0103 RFC 6531 valid\u0103
+format.idn-hostname = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un nume de gazd\u0103 interna\u021Bionalizat RFC 5890 valid
+format.iri = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un RFC 3987 IRI valid
+format.iri-reference = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o referin\u021B\u0103 IRI RFC 3987 valid\u0103
+format.uri = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un URI RFC 3986 valid
+format.uri-reference = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o referin\u021B\u0103 URI RFC 3986 valid\u0103
+format.uri-template = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un \u0219ablon URI RFC 6570 valid
+format.uuid = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un UUID RFC 4122 valid
+format.regex = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie o expresie regulat\u0103 ECMA-262 valid\u0103
+format.time = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un timp RFC 3339 valid
+format.hostname = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un nume de gazd\u0103 RFC 1123 valid
+format.json-pointer = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un pointer JSON RFC 6901 valid
+format.relative-json-pointer = {0}: nu se potrive\u0219te cu modelul {1} trebuie s\u0103 fie un pointer JSON relativ IETF valid
+format.unknown = {0}: are un format necunoscut ''{1}''
+id = {0}: \u201E{1}\u201D nu este un {2} valid
+items = {0}: indexul \u201E{1}\u201D nu este definit în schem\u0103, iar schema nu permite articole suplimentare
+maxContains = {0}: trebuie s\u0103 fie un num\u0103r întreg nenegativ în {1}
+maxItems = {0}: trebuie s\u0103 aib\u0103 cel mult {1} articole, dar g\u0103site {2}
+maxLength = {0}: trebuie s\u0103 aib\u0103 cel mult {1} caractere
+maxProperties = {0}: trebuie s\u0103 aib\u0103 cel mult {1} propriet\u0103\u021Bi
+maximum = {0}: trebuie s\u0103 aib\u0103 o valoare maxim\u0103 de {1}
+minContains = {0}: trebuie s\u0103 fie un num\u0103r întreg nenegativ în {1}
+minContainsVsMaxContains = {0}: minContains trebuie s\u0103 fie mai mic sau egal cu maxContains în {1}
+minItems = {0}: trebuie s\u0103 aib\u0103 cel pu\u021Bin {1} articole, dar g\u0103site {2}
+minLength = {0}: trebuie s\u0103 aib\u0103 cel pu\u021Bin {1} caractere
+minProperties = {0}: trebuie s\u0103 aib\u0103 cel pu\u021Bin {1} propriet\u0103\u021Bi
+minimum = {0}: trebuie s\u0103 aib\u0103 o valoare minim\u0103 de {1}
+multipleOf = {0}: trebuie s\u0103 fie multiplu de {1}
+not = {0}: nu trebuie s\u0103 fie valid pentru schema {1}
+notAllowed = {0}: proprietatea \u201E{1}\u201D nu este permis\u0103, dar este în date
+oneOf = {0}: trebuie s\u0103 fie valid pentru una \u0219i doar o schem\u0103, dar {1} sunt valide
+oneOf.indexes = {0}: trebuie s\u0103 fie valid pentru una \u0219i doar o schem\u0103, dar {1} sunt valide cu indec\u0219ii \u201E{2}\u201D
+pattern = {0}: nu se potrive\u0219te cu modelul regex {1}
+patternProperties = {0}: are o eroare cu \u201Epropriet\u0103\u021Bile modelului\u201D
+prefixItems = {0}: nu a fost g\u0103sit niciun validator la acest index
+properties = {0}: are o eroare cu \u201Epropriet\u0103\u021Bi\u201D
+propertyNames = {0}: numele propriet\u0103\u021Bii \u201E{1}\u201D nu este valid: {2}
+readOnly = {0}: este un câmp numai în citire, nu poate fi modificat
+required = {0}: proprietatea obligatorie \u201E{1}\u201D nu a fost g\u0103sit\u0103
+type = {0}: {1} g\u0103sit, {2} a\u0219teptat
+unevaluatedItems = {0}: indexul \u201E{1}\u201D nu este evaluat \u0219i schema nu permite elemente neevaluate
+unevaluatedProperties = {0}: proprietatea \u201E{1}\u201D nu este evaluat\u0103 \u0219i schema nu permite propriet\u0103\u021Bi neevaluate
+unionType = {0}: {1} g\u0103sit, {2} a\u0219teptat
+uniqueItems = {0}: trebuie s\u0103 aib\u0103 numai elemente unice în matrice
+writeOnly = {0}: este un câmp numai pentru scriere, nu poate ap\u0103rea în date
+contentEncoding = {0}: nu se potrive\u0219te cu codificarea con\u021Binutului {1}
+contentMediaType = {0}: 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 0000000..2ea8b4e
--- /dev/null
+++ b/src/main/resources/jsv-messages_ru.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441 «refs».
+additionalItems = {0}: \u0438\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{1}'' \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 = {0}: \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 {1}.
+anyOf = {0}: \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 {1}.
+const = {0}: \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 ''{1}''
+contains = {0}: \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: {2}
+contains.max = {0}: \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {1} \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: {2}
+contains.min = {0}: \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 {1} \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: {2}
+dependencies = {0}: \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 {1}.
+dependentRequired = {0}: \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 ''{1}'', \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 ''{2}''.
+dependentSchemas = {0}: \u0438\u043C\u0435\u0435\u0442\u0441\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441dependentSchemas {1}.
+enum = {0}: \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 {1}
+exclusiveMaximum = {0}: \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 {1}.
+exclusiveMinimum = {0}: \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 {1}.
+false = {0}: \u0441\u0445\u0435\u043C\u0430 \u0434\u043B\u044F ''{1}'' \u043D\u0435\u0432\u0435\u0440\u043D\u0430
+format = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} {2}
+format.date = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ''{1}''
+id = {0}: ''{1}'' \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u043C {2}
+items = {0}: \u0438\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \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 {1}.
+maxItems = {0}: \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {1} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043E {2}
+maxLength = {0}: \u0434\u043B\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {1} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432.
+maxProperties = {0}: \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u043C\u0435\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {1} \u0441\u0432\u043E\u0439\u0441\u0442\u0432.
+maximum = {0}: \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 {1}.
+minContains = {0}: \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 {1}.
+minContainsVsMaxContains = {0}: 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 {1}.
+minItems = {0}: \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {1} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043E {2}
+minLength = {0}: \u0434\u043B\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {1} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432.
+minProperties = {0}: \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {1} \u0441\u0432\u043E\u0439\u0441\u0442\u0432.
+minimum = {0}: \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 {1}.
+multipleOf = {0}: \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043A\u0440\u0430\u0442\u043D\u043E {1}
+not = {0}: \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 {1}
+notAllowed = {0}: \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{1}'' \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 = {0}: \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 {1} \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E.
+oneOf.indexes = {0}: \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 {1} \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u0441 \u0438\u043D\u0434\u0435\u043A\u0441\u0430\u043C\u0438 ''{2}''
+pattern = {0}: \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 {1}
+patternProperties = {0}: \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 = {0}: \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 = {0}: \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 = {0}: \u0438\u043C\u044F \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 ''{1}'' \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E: {2}
+readOnly = {0}: \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 = {0}: \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0435 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{1}'' \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E
+type = {0}: \u043D\u0430\u0439\u0434\u0435\u043D\u043E {1}, \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F {2}
+unevaluatedItems = {0}: \u0438\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E ''{1}'' \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 = {0}: \u043D\u0430\u0439\u0434\u0435\u043D\u043E {1}, \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F {2}
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \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 {1}
+contentMediaType = {0}: \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 0000000..63e3329
--- /dev/null
+++ b/src/main/resources/jsv-messages_sk.properties
@@ -0,0 +1,70 @@
+$ref = {0}: obsahuje chybu s ''refs''
+additionalItems = {0}: index ''{1}'' nie je definovaný v schéme a schéma nepovo\u013Euje \u010Fal\u0161ie polo\u017Eky
+additionalProperties = {0}: vlastnos\u0165 ''{1}'' nie je definovaná v schéme a schéma neumo\u017E\u0148uje \u010Fal\u0161ie vlastnosti
+allOf = {0}: musí by\u0165 platné pre v\u0161etky schémy {1}
+anyOf = {0}: musí by\u0165 platné pre ktorúko\u013Evek schému {1}
+const = {0}: musí by\u0165 kon\u0161tantná hodnota ''{1}''
+contains = {0}: neobsahuje prvok, ktorý vyhovuje týmto overeniam: {2}
+contains.max = {0}: musí obsahova\u0165 najviac {1} prvkov, ktoré prejdú týmito overeniami: {2}
+contains.min = {0}: musí obsahova\u0165 aspo\u0148 {1} prvkov, ktoré prejdú týmito overeniami: {2}
+dependencies = {0}: obsahuje chybu so závislos\u0165ami {1}
+dependentRequired = {0}: má chýbajúcu vlastnos\u0165 ''{1}'', ktorá je závislá vy\u017Eadovaná, preto\u017Ee ''{2}'' je prítomná
+dependentSchemas = {0}: obsahuje chybu s dependentSchemas {1}
+enum = {0}: nemá hodnotu v enumerácii {1}
+exclusiveMaximum = {0}: musí ma\u0165 výlu\u010Dnú maximálnu hodnotu {1}
+exclusiveMinimum = {0}: musí ma\u0165 výlu\u010Dnú minimálnu hodnotu {1}
+false = {0}: schéma pre ''{1}'' je nepravda
+format = {0}: nezhoduje sa so vzorom {1} {2}
+format.date = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný úplný dátum RFC 3339
+format.date-time = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný dátum a \u010Das RFC 3339
+format.duration = {0}: nezhoduje sa so vzorom {1}, musí ma\u0165 platné trvanie ISO 8601
+format.email = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platná po\u0161tová schránka RFC 5321
+format.ipv4 = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platná adresa IP RFC 2673
+format.ipv6 = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platná adresa IP RFC 4291
+format.idn-email = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platná po\u0161tová schránka RFC 6531
+format.idn-hostname = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný medzinárodný názov hostite\u013Ea pod\u013Ea RFC 5890
+format.iri = {0}: nezhoduje sa so vzorom {1} musí by\u0165 platný RFC 3987 IRI
+format.iri-reference = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný RFC 3987 IRI-reference
+format.uri = {0}: nezhoduje sa so vzorom {1}, musí by\u0165 platný RFC 3986 URI
+format.uri-reference = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný odkaz URI RFC 3986
+format.uri-template = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platná \u0161ablóna URI RFC 6570
+format.uuid = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný UUID RFC 4122
+format.regex = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný regulárny výraz ECMA-262
+format.time = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný \u010Das RFC 3339
+format.hostname = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný názov hostite\u013Ea RFC 1123
+format.json-pointer = {0}: nezhoduje sa so vzorom {1}, musí to by\u0165 platný ukazovate\u013E RFC 6901 JSON
+format.relative-json-pointer = {0}: nezhoduje sa so vzorom {1}, musí by\u0165 platným IETF relatívnym ukazovate\u013Eom JSON
+format.unknown = {0}: má neznámy formát ''{1}''
+id = {0}: ''{1}'' nie je platný {2}
+items = {0}: index ''{1}'' nie je definovaný v schéme a schéma nepovo\u013Euje \u010Fal\u0161ie polo\u017Eky
+maxContains = {0}: musí by\u0165 nezáporné celé \u010Díslo v {1}
+maxItems = {0}: musí ma\u0165 najviac {1} polo\u017Eiek, ale nájdených {2}
+maxLength = {0}: musí ma\u0165 maximálne {1} znakov
+maxProperties = {0}: musí ma\u0165 najviac {1} vlastností
+maximum = {0}: musí ma\u0165 maximálnu hodnotu {1}
+minContains = {0}: musí by\u0165 nezáporné celé \u010Díslo v {1}
+minContainsVsMaxContains = {0}: minContains musí by\u0165 men\u0161í alebo rovný maxContains v {1}
+minItems = {0}: musí ma\u0165 aspo\u0148 {1} polo\u017Eiek, ale nájdených {2}
+minLength = {0}: musí ma\u0165 aspo\u0148 {1} znakov
+minProperties = {0}: musí ma\u0165 aspo\u0148 {1} vlastností
+minimum = {0}: musí ma\u0165 minimálnu hodnotu {1}
+multipleOf = {0}: musí by\u0165 násobkom {1}
+not = {0}: nesmie by\u0165 platné pre schému {1}
+notAllowed = {0}: vlastnos\u0165 ''{1}'' nie je povolená, ale je v údajoch
+oneOf = {0}: musí by\u0165 platné pre jednu a iba jednu schému, ale {1} sú platné
+oneOf.indexes = {0}: musí by\u0165 platné pre jednu a iba jednu schému, ale {1} sú platné s indexmi ''{2}''
+pattern = {0}: nezodpovedá vzoru regulárneho výrazu {1}
+patternProperties = {0}: obsahuje nejakú chybu s ''vlastnos\u0165ami vzoru''
+prefixItems = {0}: v tomto indexe sa nena\u0161iel \u017Eiadny validátor
+properties = {0}: obsahuje chybu s ''vlastnosti''
+propertyNames = {0}: názov vlastnosti ''{1}'' nie je platný: {2}
+readOnly = {0}: je pole len na \u010Dítanie, nemo\u017Eno ho zmeni\u0165
+required = {0}: po\u017Eadovaná vlastnos\u0165 ''{1}'' sa nena\u0161la
+type = {0}: nájdených {1}, o\u010Dakávaných {2}
+unevaluatedItems = {0}: index ''{1}'' nie je vyhodnotený a schéma nepovo\u013Euje nehodnotené polo\u017Eky
+unevaluatedProperties = {0}: vlastnos\u0165 ''{1}'' nie je vyhodnotená a schéma nepovo\u013Euje nehodnotené vlastnosti
+unionType = {0}: nájdených {1}, o\u010Dakávaných {2}
+uniqueItems = {0}: v poli musia by\u0165 iba jedine\u010Dné polo\u017Eky
+writeOnly = {0}: je pole ur\u010Dené len na zápis, nemô\u017Ee sa objavi\u0165 v údajoch
+contentEncoding = {0}: nezhoduje sa s kódovaním obsahu {1}
+contentMediaType = {0}: 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 0000000..b9dc6ff
--- /dev/null
+++ b/src/main/resources/jsv-messages_sv.properties
@@ -0,0 +1,70 @@
+$ref = {0}: har ett fel med ''refs''
+additionalItems = {0}: index ''{1}'' är inte definierat i schemat och schemat tillåter inte ytterligare objekt
+additionalProperties = {0}: egenskapen ''{1}'' är inte definierad i schemat och schemat tillåter inte ytterligare egenskaper
+allOf = {0}: måste vara giltig för alla scheman {1}
+anyOf = {0}: måste vara giltigt för något av schemana {1}
+const = {0}: måste vara det konstanta värdet ''{1}''
+contains = {0}: innehåller inte ett element som klarar dessa valideringar: {2}
+contains.max = {0}: måste innehålla högst {1} element som klarar dessa valideringar: {2}
+contains.min = {0}: måste innehålla minst {1} element som klarar dessa valideringar: {2}
+dependencies = {0}: har ett fel med beroenden {1}
+dependentRequired = {0}: har en saknad egenskap ''{1}'' som är beroende krävs eftersom ''{2}'' är närvarande
+dependentSchemas = {0}: har ett fel med dependentSchemas {1}
+enum = {0}: har inget värde i uppräkningen {1}
+exclusiveMaximum = {0}: måste ha ett exklusivt maxvärde på {1}
+exclusiveMinimum = {0}: måste ha ett exklusivt lägsta värde på {1}
+false = {0}: schemat för ''{1}'' är falskt
+format = {0}: matchar inte {1}-mönstret {2}
+format.date = {0}: matchar inte {1}-mönstret måste vara ett giltigt RFC 3339 fulldatum
+format.date-time = {0}: matchar inte {1}-mönstret måste vara ett giltigt RFC 3339 datum-tid
+format.duration = {0}: matchar inte {1}-mönstret måste vara en giltig ISO 8601-varaktighet
+format.email = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 5321-postlåda
+format.ipv4 = {0}: matchar inte mönstret {1} måste vara en giltig RFC 2673 IP-adress
+format.ipv6 = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 4291 IP-adress
+format.idn-email = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 6531-postlåda
+format.idn-hostname = {0}: matchar inte mönstret {1} måste vara ett giltigt RFC 5890 internationaliserat värdnamn
+format.iri = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 3987 IRI
+format.iri-reference = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 3987 IRI-referens
+format.uri = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 3986 URI
+format.uri-reference = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 3986 URI-referens
+format.uri-template = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 6570 URI-mall
+format.uuid = {0}: matchar inte {1}-mönstret måste vara ett giltigt RFC 4122 UUID
+format.regex = {0}: matchar inte {1}-mönstret måste vara ett giltigt ECMA-262 reguljärt uttryck
+format.time = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 3339-tid
+format.hostname = {0}: matchar inte {1}-mönstret måste vara ett giltigt RFC 1123-värdnamn
+format.json-pointer = {0}: matchar inte {1}-mönstret måste vara en giltig RFC 6901 JSON-pekare
+format.relative-json-pointer = {0}: matchar inte {1}-mönstret måste vara en giltig IETF Relativ JSON-pekare
+format.unknown = {0}: har ett okänt format ''{1}''
+id = {0}: ''{1}'' är inte en giltig {2}
+items = {0}: index ''{1}'' är inte definierat i schemat och schemat tillåter inte ytterligare objekt
+maxContains = {0}: måste vara ett icke-negativt heltal i {1}
+maxItems = {0}: måste ha högst {1} objekt men hittade {2}
+maxLength = {0}: får vara högst {1} tecken lång
+maxProperties = {0}: måste ha högst {1} egenskaper
+maximum = {0}: måste ha ett maximalt värde på {1}
+minContains = {0}: måste vara ett icke-negativt heltal i {1}
+minContainsVsMaxContains = {0}: minContains måste vara mindre än eller lika med maxContains i {1}
+minItems = {0}: måste ha minst {1} objekt men hittade {2}
+minLength = {0}: måste vara minst {1} tecken lång
+minProperties = {0}: måste ha minst {1} egenskaper
+minimum = {0}: måste ha ett lägsta värde på {1}
+multipleOf = {0}: måste vara multipel av {1}
+not = {0}: får inte vara giltigt för schemat {1}
+notAllowed = {0}: egenskapen ''{1}'' är inte tillåten men den finns i data
+oneOf = {0}: måste vara giltigt för ett och endast ett schema, men {1} är giltiga
+oneOf.indexes = {0}: måste vara giltigt för ett och endast ett schema, men {1} är giltiga med index ''{2}''
+pattern = {0}: matchar inte regexmönstret {1}
+patternProperties = {0}: har något fel med ''mönsteregenskaper''
+prefixItems = {0}: ingen validator hittades i detta index
+properties = {0}: har ett fel med ''egenskaper''
+propertyNames = {0}: egenskapen ''{1}'' namn är inte giltigt: {2}
+readOnly = {0}: är ett skrivskyddat fält, det kan inte ändras
+required = {0}: den obligatoriska egenskapen ''{1}'' hittades inte
+type = {0}: {1} hittades, {2} förväntas
+unevaluatedItems = {0}: index ''{1}'' utvärderas inte och schemat tillåter inte oevaluerade objekt
+unevaluatedProperties = {0}: egenskapen ''{1}'' utvärderas inte och schemat tillåter inte oevaluerade egenskaper
+unionType = {0}: {1} hittades, {2} förväntas
+uniqueItems = {0}: får endast ha unika objekt i arrayen
+writeOnly = {0}: är ett skrivskyddat fält, det kan inte visas i data
+contentEncoding = {0}: matchar inte innehållskodning {1}
+contentMediaType = {0}: är inte ett innehåll jag
diff --git a/src/main/resources/jsv-messages_th.properties b/src/main/resources/jsv-messages_th.properties
new file mode 100644
index 0000000..d13a67b
--- /dev/null
+++ b/src/main/resources/jsv-messages_th.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A ''refs''
+additionalItems = {0}: \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{1}'' \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 = {0}: \u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{1}'' \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 = {0}: \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 {1}
+anyOf = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32\u0E43\u0E14\u0E46 {1}
+const = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E04\u0E48\u0E32\u0E04\u0E07\u0E17\u0E35\u0E48 ''{1}''
+contains = {0}: \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: {2}
+contains.max = {0}: \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 {1} \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: {2}
+contains.min = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {1} \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: {2}
+dependencies = {0}: \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 {1}
+dependentRequired = {0}: \u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E02\u0E32\u0E14\u0E2B\u0E32\u0E22\u0E44\u0E1B ''{1}'' \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 ''{2}'' \u0E2D\u0E22\u0E39\u0E48
+dependentSchemas = {0}: \u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E01\u0E31\u0E1A dependentSchemas {1}
+enum = {0}: \u0E44\u0E21\u0E48\u0E21\u0E35\u0E04\u0E48\u0E32\u0E43\u0E19\u0E01\u0E32\u0E23\u0E41\u0E08\u0E07\u0E19\u0E31\u0E1A {1}
+exclusiveMaximum = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14\u0E1E\u0E34\u0E40\u0E28\u0E29\u0E40\u0E1B\u0E47\u0E19 {1}
+exclusiveMinimum = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E15\u0E48\u0E33\u0E2A\u0E38\u0E14\u0E1E\u0E34\u0E40\u0E28\u0E29\u0E40\u0E1B\u0E47\u0E19 {1}
+false = {0}: \u0E2A\u0E04\u0E35\u0E21\u0E32\u0E2A\u0E33\u0E2B\u0E23\u0E31\u0E1A ''{1}'' \u0E40\u0E1B\u0E47\u0E19\u0E40\u0E17\u0E47\u0E08
+format = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} {2}
+format.date = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3987 IRI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+format.iri-reference = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3987 IRI-reference \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+format.uri = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 3986 URI \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+format.uri-reference = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 RFC 4122 UUID \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+format.regex = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A {1} \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 = {0}: \u0E21\u0E35\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A\u0E17\u0E35\u0E48\u0E44\u0E21\u0E48\u0E23\u0E39\u0E49\u0E08\u0E31\u0E01 ''{1}''
+id = {0}: ''{1}'' \u0E44\u0E21\u0E48\u0E43\u0E0A\u0E48 {2} \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+items = {0}: \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{1}'' \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 = {0}: \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 {1}
+maxItems = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E21\u0E32\u0E01\u0E17\u0E35\u0E48\u0E2A\u0E38\u0E14 {1} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23 \u0E41\u0E15\u0E48\u0E1E\u0E1A {2}
+maxLength = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E27\u0E32\u0E21\u0E22\u0E32\u0E27\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 {1} \u0E2D\u0E31\u0E01\u0E02\u0E23\u0E30
+maxProperties = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 {1}
+maximum = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14\u0E40\u0E1B\u0E47\u0E19 {1}
+minContains = {0}: \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 {1}
+minContainsVsMaxContains = {0}: 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 {1}
+minItems = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {1} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23 \u0E41\u0E15\u0E48\u0E1E\u0E1A {2}
+minLength = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E27\u0E32\u0E21\u0E22\u0E32\u0E27\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {1} \u0E2D\u0E31\u0E01\u0E02\u0E23\u0E30
+minProperties = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 {1}
+minimum = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E04\u0E48\u0E32\u0E15\u0E48\u0E33\u0E2A\u0E38\u0E14\u0E40\u0E1B\u0E47\u0E19 {1}
+multipleOf = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19\u0E1C\u0E25\u0E04\u0E39\u0E13\u0E02\u0E2D\u0E07 {1}
+not = {0}: \u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E31\u0E1A\u0E2A\u0E04\u0E35\u0E21\u0E32 {1}
+notAllowed = {0}: \u0E44\u0E21\u0E48\u0E2D\u0E19\u0E38\u0E0D\u0E32\u0E15\u0E43\u0E2B\u0E49\u0E43\u0E0A\u0E49\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{1}'' \u0E41\u0E15\u0E48\u0E2D\u0E22\u0E39\u0E48\u0E43\u0E19\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25
+oneOf = {0}: \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 {1} \u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07
+oneOf.indexes = {0}: \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 {1} \u0E43\u0E0A\u0E49\u0E44\u0E14\u0E49\u0E01\u0E31\u0E1A\u0E14\u0E31\u0E0A\u0E19\u0E35 ''{2}''
+pattern = {0}: \u0E44\u0E21\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E1A\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A regex {1}
+patternProperties = {0}: \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 = {0}: \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 = {0}: \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 = {0}: \u0E0A\u0E37\u0E48\u0E2D\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{1}'' \u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07: {2}
+readOnly = {0}: \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 = {0}: \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34\u0E17\u0E35\u0E48\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23 ''{1}''
+type = {0}: \u0E1E\u0E1A {1}, \u0E04\u0E32\u0E14\u0E2B\u0E27\u0E31\u0E07 {2}
+unevaluatedItems = {0}: \u0E14\u0E31\u0E0A\u0E19\u0E35 ''{1}'' \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 = {0}: \u0E04\u0E38\u0E13\u0E2A\u0E21\u0E1A\u0E31\u0E15\u0E34 ''{1}'' \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 = {0}: \u0E1E\u0E1A {1}, \u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23 {2}
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \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 {1}
+contentMediaType = {0}: \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 0000000..20e9b3a
--- /dev/null
+++ b/src/main/resources/jsv-messages_tr.properties
@@ -0,0 +1,70 @@
+$ref = {0}: ''refs'' ile ilgili bir hata var
+additionalItems = {0}: ''{1}'' dizini \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek ö\u011Felere izin vermiyor
+additionalProperties = {0}: ''{1}'' özelli\u011Fi \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek özelliklere izin vermiyor
+allOf = {0}: tüm {1} \u015Femalar\u0131 için geçerli olmal\u0131d\u0131r
+anyOf = {0}: {1} \u015Femalar\u0131ndan herhangi biri için geçerli olmal\u0131d\u0131r
+const = {0}: ''{1}'' sabit de\u011Feri olmal\u0131d\u0131r
+contains = {0}: bu do\u011Frulamalar\u0131 geçen bir ö\u011Fe içermiyor: {2}
+contains.max = {0}: \u015Fu do\u011Frulamalar\u0131 geçen en fazla {1} ö\u011Fe içermelidir: {2}
+contains.min = {0}: \u015Fu do\u011Frulamalar\u0131 geçen en az {1} ö\u011Fe içermelidir: {2}
+dependencies = {0}: ba\u011F\u0131ml\u0131l\u0131klarda hata var {1}
+dependentRequired = {0}: ''{1}'' eksik bir özelli\u011Fi var ve ''{2}'' mevcut oldu\u011Fundan ba\u011F\u0131ml\u0131 gerekli
+dependentSchemas = {0}: DependedSchemas {1} ile ilgili bir hata var
+enum = {0}: {1} numaraland\u0131rmas\u0131nda bir de\u011Fer yok
+exclusiveMaximum = {0}: özel maksimum de\u011Feri {1} olmal\u0131d\u0131r
+exclusiveMinimum = {0}: özel minimum de\u011Feri {1} olmal\u0131d\u0131r
+false = {0}: ''{1}'' \u015Femas\u0131 yanl\u0131\u015F
+format = {0}: {1} modeliyle {2} e\u015Fle\u015Fmiyor
+format.date = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3339 tam tarihi olmal\u0131d\u0131r
+format.date-time = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3339 tarih-saat olmal\u0131d\u0131r
+format.duration = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir ISO 8601 süresi olmal\u0131d\u0131r
+format.email = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 5321 Posta Kutusu olmal\u0131d\u0131r
+format.ipv4 = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 2673 IP adresi olmal\u0131d\u0131r
+format.ipv6 = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 4291 IP adresi olmal\u0131d\u0131r
+format.idn-email = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 6531 Posta Kutusu olmal\u0131d\u0131r
+format.idn-hostname = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 5890 uluslararas\u0131la\u015Ft\u0131r\u0131lm\u0131\u015F ana bilgisayar ad\u0131 olmal\u0131d\u0131r
+format.iri = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3987 IRI olmal\u0131d\u0131r
+format.iri-reference = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3987 IRI referans\u0131 olmal\u0131d\u0131r
+format.uri = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3986 URI olmal\u0131d\u0131r
+format.uri-reference = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3986 URI referans\u0131 olmal\u0131d\u0131r
+format.uri-template = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 6570 URI \u015Eablonu olmal\u0131d\u0131r
+format.uuid = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 4122 UUID olmal\u0131d\u0131r
+format.regex = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir ECMA-262 normal ifadesi olmal\u0131d\u0131r
+format.time = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 3339 saati olmal\u0131d\u0131r
+format.hostname = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 1123 ana bilgisayar ad\u0131 olmal\u0131d\u0131r
+format.json-pointer = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir RFC 6901 JSON \u0130\u015Faretçisi olmal\u0131d\u0131r
+format.relative-json-pointer = {0}: {1} modeliyle e\u015Fle\u015Fmiyor, geçerli bir IETF Göreli JSON \u0130\u015Faretçisi olmal\u0131d\u0131r
+format.unknown = {0}: bilinmeyen bir formata sahip ''{1}''
+id = {0}: ''{1}'' geçerli bir {2} de\u011Fil
+items = {0}: ''{1}'' dizini \u015Femada tan\u0131ml\u0131 de\u011Fil ve \u015Fema ek ö\u011Felere izin vermiyor
+maxContains = {0}: {1}''de negatif olmayan bir tamsay\u0131 olmal\u0131d\u0131r
+maxItems = {0}: en fazla {1} ö\u011Feye sahip olmal\u0131 ancak {2} bulundu
+maxLength = {0}: en fazla {1} karakter uzunlu\u011Funda olmal\u0131d\u0131r
+maxProperties = {0}: en fazla {1} özelli\u011Fe sahip olmal\u0131d\u0131r
+maximum = {0}: maksimum de\u011Feri {1} olmal\u0131d\u0131r
+minContains = {0}: {1}''de negatif olmayan bir tam say\u0131 olmal\u0131d\u0131r
+minContainsVsMaxContains = {0}: minContains, {1} içindeki maxContains de\u011Ferinden küçük veya ona e\u015Fit olmal\u0131d\u0131r
+minItems = {0}: en az {1} ö\u011Feye sahip olmal\u0131 ancak {2} bulundu
+minLength = {0}: en az {1} karakter uzunlu\u011Funda olmal\u0131d\u0131r
+minProperties = {0}: en az {1} özelli\u011Fe sahip olmal\u0131d\u0131r
+minimum = {0}: minimum de\u011Feri {1} olmal\u0131d\u0131r
+multipleOf = {0}: {1}''\u0131n kat\u0131 olmal\u0131d\u0131r
+not = {0}: {1} \u015Femas\u0131 için geçerli olmamal\u0131d\u0131r
+notAllowed = {0}: ''{1}'' özelli\u011Fine izin verilmiyor ancak verilerde mevcut
+oneOf = {0}: yaln\u0131zca bir \u015Fema için geçerli olmal\u0131d\u0131r, ancak {1} geçerlidir
+oneOf.indexes = {0}: yaln\u0131zca bir \u015Fema için geçerli olmal\u0131d\u0131r, ancak {1} ''{2}'' dizinleriyle geçerlidir
+pattern = {0}: normal ifade modeli {1} ile e\u015Fle\u015Fmiyor
+patternProperties = {0}: ''desen özelliklerinde'' baz\u0131 hatalar var
+prefixItems = {0}: bu dizinde do\u011Frulay\u0131c\u0131 bulunamad\u0131
+properties = {0}: ''özellikler'' ile ilgili bir hata var
+propertyNames = {0}: ''{1}'' özelli\u011Finin ad\u0131 geçerli de\u011Fil: {2}
+readOnly = {0}: salt okunur bir aland\u0131r, de\u011Fi\u015Ftirilemez
+required = {0}: gerekli ''{1}'' özelli\u011Fi bulunamad\u0131
+type = {0}: {1} bulundu, {2} bekleniyor
+unevaluatedItems = {0}: ''{1}'' dizini de\u011Ferlendirilmez ve \u015Fema, de\u011Ferlendirilmemi\u015F ö\u011Felere izin vermez
+unevaluatedProperties = {0}: ''{1}'' özelli\u011Fi de\u011Ferlendirilmez ve \u015Fema, de\u011Ferlendirilmemi\u015F özelliklere izin vermez
+unionType = {0}: {1} bulundu, {2} bekleniyor
+uniqueItems = {0}: dizide yaln\u0131zca benzersiz ö\u011Feler bulunmal\u0131d\u0131r
+writeOnly = {0}: salt yaz\u0131l\u0131r bir aland\u0131r, verilerde görünemez
+contentEncoding = {0}: içerik kodlamas\u0131 {1} ile e\u015Fle\u015Fmiyor
+contentMediaType = {0}: bir içerik 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 0000000..ee62782
--- /dev/null
+++ b/src/main/resources/jsv-messages_uk.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043C\u0438\u043B\u043A\u0443 \u0437 ''refs''
+additionalItems = {0}: \u0456\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{1}'' \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 = {0}: \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 {1}
+anyOf = {0}: \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 {1}
+const = {0}: \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 ''{1}''
+contains = {0}: \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: {2}
+contains.max = {0}: \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {1} \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: {2}
+contains.min = {0}: \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {1} \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: {2}
+dependencies = {0}: \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 {1}
+dependentRequired = {0}: \u043C\u0430\u0454 \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u044E \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{1}'', \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 ''{2}''
+dependentSchemas = {0}: \u0454 \u043F\u043E\u043C\u0438\u043B\u043A\u0430 \u0437 dependentSchemas {1}
+enum = {0}: \u043D\u0435 \u043C\u0430\u0454 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F \u0432 \u043F\u0435\u0440\u0435\u043B\u0456\u043A\u0443 {1}
+exclusiveMaximum = {0}: \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 {1}
+exclusiveMinimum = {0}: \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 {1}
+false = {0}: \u0441\u0445\u0435\u043C\u0430 \u0434\u043B\u044F ''{1}'' \u043D\u0435\u0432\u0456\u0440\u043D\u0430
+format = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} {2}
+format.date = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C RFC 3987 IRI
+format.iri-reference = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C URI RFC 3986
+format.uri-reference = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C UUID RFC 4122
+format.regex = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0447\u0430\u0441\u043E\u043C RFC 3339
+format.hostname = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1} \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 = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C RFC 6901 JSON Pointer
+format.relative-json-pointer = {0}: \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0454 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 {1}, \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 = {0}: \u043C\u0430\u0454 \u043D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ''{1}''
+id = {0}: ''{1}'' \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0438\u0439 {2}
+items = {0}: \u0456\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \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 {1}
+maxItems = {0}: \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {1} \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0456\u0432, \u0430\u043B\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {2}
+maxLength = {0}: \u043D\u0435 \u0431\u0456\u043B\u044C\u0448\u0435 \u043D\u0456\u0436 {1} \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432
+maxProperties = {0}: \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u0449\u043E\u043D\u0430\u0439\u0431\u0456\u043B\u044C\u0448\u0435 {1} \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0435\u0439
+maximum = {0}: \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 {1}
+minContains = {0}: \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 {1}
+minContainsVsMaxContains = {0}: 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 {1}
+minItems = {0}: \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {1} \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0456\u0432, \u0430\u043B\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {2}
+minLength = {0}: \u043C\u0430\u0454 \u043C\u0456\u0441\u0442\u0438\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {1} \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432
+minProperties = {0}: \u043C\u0430\u0454 \u043C\u0430\u0442\u0438 \u043F\u0440\u0438\u043D\u0430\u0439\u043C\u043D\u0456 {1} \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0435\u0439
+minimum = {0}: \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 {1}
+multipleOf = {0}: \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u043A\u0440\u0430\u0442\u043D\u0438\u043C {1}
+not = {0}: \u043D\u0435 \u043C\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043D\u0438\u043C \u0434\u043B\u044F \u0441\u0445\u0435\u043C\u0438 {1}
+notAllowed = {0}: \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{1}'' \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 = {0}: \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 {1} \u0454 \u0434\u0456\u0439\u0441\u043D\u0438\u043C\u0438
+oneOf.indexes = {0}: \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 {1} \u0454 \u0434\u0456\u0439\u0441\u043D\u0438\u043C\u0438 \u0437 \u0456\u043D\u0434\u0435\u043A\u0441\u0430\u043C\u0438 ''{2}''
+pattern = {0}: \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 {1}
+patternProperties = {0}: \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 = {0}: \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 = {0}: \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 = {0}: \u043D\u0430\u0437\u0432\u0430 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u043E\u0441\u0442\u0456 ''{1}'' \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0430: {2}
+readOnly = {0}: \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 = {0}: \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u0430 \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{1}'' \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u0430
+type = {0}: \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {1}, \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F {2}
+unevaluatedItems = {0}: \u0456\u043D\u0434\u0435\u043A\u0441 ''{1}'' \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 = {0}: \u0432\u043B\u0430\u0441\u0442\u0438\u0432\u0456\u0441\u0442\u044C ''{1}'' \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 = {0}: \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E {1}, \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F {2}
+uniqueItems = {0}: \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 = {0}: \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 = {0}: \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 {1}
+contentMediaType = {0}: \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 0000000..94797c3
--- /dev/null
+++ b/src/main/resources/jsv-messages_vi.properties
@@ -0,0 +1,70 @@
+$ref = {0}: có l\u1ED7i ''refs''
+additionalItems = {0}: ch\u1EC9 m\u1EE5c ''{1}'' không \u0111\u01B0\u1EE3c xác \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 và l\u01B0\u1EE3c \u0111\u1ED3 không cho phép các m\u1EE5c b\u1ED5 sung
+additionalProperties = {0}: thu\u1ED9c tính ''{1}'' không \u0111\u01B0\u1EE3c xác \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 và l\u01B0\u1EE3c \u0111\u1ED3 không cho phép các thu\u1ED9c tính b\u1ED5 sung
+allOf = {0}: ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi t\u1EA5t c\u1EA3 l\u01B0\u1EE3c \u0111\u1ED3 {1}
+anyOf = {0}: ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi b\u1EA5t k\u1EF3 l\u01B0\u1EE3c \u0111\u1ED3 nào {1}
+const = {0}: ph\u1EA3i là giá tr\u1ECB không \u0111\u1ED5i ''{1}''
+contains = {0}: không ch\u1EE9a ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua các xác nh\u1EADn này: {2}
+contains.max = {0}: ph\u1EA3i ch\u1EE9a t\u1ED1i \u0111a {1} ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua các xác th\u1EF1c này: {2}
+contains.min = {0}: ph\u1EA3i ch\u1EE9a ít nh\u1EA5t {1} ph\u1EA7n t\u1EED v\u01B0\u1EE3t qua các xác nh\u1EADn này: {2}
+dependencies = {0}: có l\u1ED7i v\u1EDBi ph\u1EE5 thu\u1ED9c {1}
+dependentRequired = {0}: thi\u1EBFu thu\u1ED9c tính ''{1}'' thu\u1ED9c tính ph\u1EE5 thu\u1ED9c b\u1EAFt bu\u1ED9c vì ''{2}'' hi\u1EC7n di\u1EC7n
+dependentSchemas = {0}: có l\u1ED7i v\u1EDBi dependencySchemas {1}
+enum = {0}: không có giá tr\u1ECB trong b\u1EA3ng li\u1EC7t kê {1}
+exclusiveMaximum = {0}: ph\u1EA3i có giá tr\u1ECB t\u1ED1i \u0111a \u0111\u1ED9c quy\u1EC1n là {1}
+exclusiveMinimum = {0}: ph\u1EA3i có giá tr\u1ECB t\u1ED1i thi\u1EC3u duy nh\u1EA5t là {1}
+false = {0}: l\u01B0\u1EE3c \u0111\u1ED3 cho ''{1}'' là sai
+format = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} {2}
+format.date = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là ngày \u0111\u1EA7y \u0111\u1EE7 RFC 3339 h\u1EE3p l\u1EC7
+format.date-time = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là ngày gi\u1EDD h\u1EE3p l\u1EC7 RFC 3339
+format.duration = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i có th\u1EDDi l\u01B0\u1EE3ng ISO 8601 h\u1EE3p l\u1EC7
+format.email = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là H\u1ED9p th\u01B0 RFC 5321 h\u1EE3p l\u1EC7
+format.ipv4 = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là \u0111\u1ECBa ch\u1EC9 IP RFC 2673 h\u1EE3p l\u1EC7
+format.ipv6 = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là \u0111\u1ECBa ch\u1EC9 IP RFC 4291 h\u1EE3p l\u1EC7
+format.idn-email = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là H\u1ED9p th\u01B0 RFC 6531 h\u1EE3p l\u1EC7
+format.idn-hostname = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là tên máy ch\u1EE7 \u0111\u01B0\u1EE3c qu\u1ED1c t\u1EBF hóa RFC 5890 h\u1EE3p l\u1EC7
+format.iri = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là RFC 3987 IRI h\u1EE3p l\u1EC7
+format.iri-reference = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là tham chi\u1EBFu IRI RFC 3987 h\u1EE3p l\u1EC7
+format.uri = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là URI RFC 3986 h\u1EE3p l\u1EC7
+format.uri-reference = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là tham chi\u1EBFu URI RFC 3986 h\u1EE3p l\u1EC7
+format.uri-template = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là M\u1EABu URI RFC 6570 h\u1EE3p l\u1EC7
+format.uuid = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là UUID RFC 4122 h\u1EE3p l\u1EC7
+format.regex = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là bi\u1EC3u th\u1EE9c chính quy ECMA-262 h\u1EE3p l\u1EC7
+format.time = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là th\u1EDDi gian RFC 3339 h\u1EE3p l\u1EC7
+format.hostname = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là tên máy ch\u1EE7 RFC 1123 h\u1EE3p l\u1EC7
+format.json-pointer = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là Con tr\u1ECF JSON RFC 6901 h\u1EE3p l\u1EC7
+format.relative-json-pointer = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu {1} ph\u1EA3i là Con tr\u1ECF JSON t\u01B0\u01A1ng \u0111\u1ED1i c\u1EE7a IETF h\u1EE3p l\u1EC7
+format.unknown = {0}: có \u0111\u1ECBnh d\u1EA1ng không xác \u0111\u1ECBnh ''{1}''
+id = {0}: ''{1}'' không ph\u1EA3i là {2} h\u1EE3p l\u1EC7
+items = {0}: ch\u1EC9 m\u1EE5c ''{1}'' không \u0111\u01B0\u1EE3c xác \u0111\u1ECBnh trong l\u01B0\u1EE3c \u0111\u1ED3 và l\u01B0\u1EE3c \u0111\u1ED3 không cho phép các m\u1EE5c b\u1ED5 sung
+maxContains = {0}: ph\u1EA3i là s\u1ED1 nguyên không âm trong {1}
+maxItems = {0}: ph\u1EA3i có t\u1ED1i \u0111a {1} m\u1EE5c nh\u01B0ng \u0111ã tìm th\u1EA5y {2}
+maxLength = {0}: ph\u1EA3i dài t\u1ED1i \u0111a {1} ký t\u1EF1
+maxProperties = {0}: ph\u1EA3i có t\u1ED1i \u0111a {1} thu\u1ED9c tính
+maximum = {0}: ph\u1EA3i có giá tr\u1ECB t\u1ED1i \u0111a là {1}
+minContains = {0}: ph\u1EA3i là s\u1ED1 nguyên không âm trong {1}
+minContainsVsMaxContains = {0}: minContains ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng maxContains trong {1}
+minItems = {0}: ph\u1EA3i có ít nh\u1EA5t {1} m\u1EE5c nh\u01B0ng \u0111ã tìm th\u1EA5y {2}
+minLength = {0}: ph\u1EA3i dài ít nh\u1EA5t {1} ký t\u1EF1
+minProperties = {0}: ph\u1EA3i có ít nh\u1EA5t {1} thu\u1ED9c tính
+minimum = {0}: ph\u1EA3i có giá tr\u1ECB t\u1ED1i thi\u1EC3u là {1}
+multipleOf = {0}: ph\u1EA3i là b\u1ED9i s\u1ED1 c\u1EE7a {1}
+not = {0}: không \u0111\u01B0\u1EE3c h\u1EE3p l\u1EC7 \u0111\u1ED1i v\u1EDBi l\u01B0\u1EE3c \u0111\u1ED3 {1}
+notAllowed = {0}: thu\u1ED9c tính ''{1}'' không \u0111\u01B0\u1EE3c phép nh\u01B0ng nó có trong d\u1EEF li\u1EC7u
+oneOf = {0}: ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi m\u1ED9t và ch\u1EC9 m\u1ED9t l\u01B0\u1EE3c \u0111\u1ED3, nh\u01B0ng {1} h\u1EE3p l\u1EC7
+oneOf.indexes = {0}: ph\u1EA3i h\u1EE3p l\u1EC7 v\u1EDBi m\u1ED9t và ch\u1EC9 m\u1ED9t l\u01B0\u1EE3c \u0111\u1ED3, nh\u01B0ng {1} h\u1EE3p l\u1EC7 v\u1EDBi các ch\u1EC9 m\u1EE5c ''{2}''
+pattern = {0}: không kh\u1EDBp v\u1EDBi m\u1EABu bi\u1EC3u th\u1EE9c chính quy {1}
+patternProperties = {0}: có m\u1ED9t s\u1ED1 l\u1ED7i v\u1EDBi ''thu\u1ED9c tính m\u1EABu''
+prefixItems = {0}: không tìm th\u1EA5y trình xác th\u1EF1c nào t\u1EA1i ch\u1EC9 m\u1EE5c này
+properties = {0}: có l\u1ED7i ''thu\u1ED9c tính''
+propertyNames = {0}: tên thu\u1ED9c tính ''{1}'' không h\u1EE3p l\u1EC7: {2}
+readOnly = {0}: là tr\u01B0\u1EDDng ch\u1EC9 \u0111\u1ECDc, không th\u1EC3 thay \u0111\u1ED5i
+required = {0}: không tìm th\u1EA5y thu\u1ED9c tính b\u1EAFt bu\u1ED9c ''{1}''
+type = {0}: \u0111ã tìm th\u1EA5y {1}, mong \u0111\u1EE3i {2}
+unevaluatedItems = {0}: ch\u1EC9 m\u1EE5c ''{1}'' không \u0111\u01B0\u1EE3c \u0111ánh giá và l\u01B0\u1EE3c \u0111\u1ED3 không cho phép các m\u1EE5c không \u0111\u01B0\u1EE3c \u0111ánh giá
+unevaluatedProperties = {0}: thu\u1ED9c tính ''{1}'' không \u0111\u01B0\u1EE3c \u0111ánh giá và l\u01B0\u1EE3c \u0111\u1ED3 không cho phép các thu\u1ED9c tính không \u0111\u01B0\u1EE3c \u0111ánh giá
+unionType = {0}: \u0111ã tìm th\u1EA5y {1}, mong \u0111\u1EE3i {2}
+uniqueItems = {0}: ch\u1EC9 \u0111\u01B0\u1EE3c có các m\u1EE5c duy nh\u1EA5t trong m\u1EA3ng
+writeOnly = {0}: là tr\u01B0\u1EDDng ch\u1EC9 ghi, không xu\u1EA5t hi\u1EC7n trong d\u1EEF li\u1EC7u
+contentEncoding = {0}: không kh\u1EDBp v\u1EDBi mã hóa n\u1ED9i dung {1}
+contentMediaType = {0}: không ph\u1EA3i là n\u1ED9i dung c\u1EE7a tôi
diff --git a/src/main/resources/jsv-messages_zh_CN.properties b/src/main/resources/jsv-messages_zh_CN.properties
new file mode 100644
index 0000000..d5d7d45
--- /dev/null
+++ b/src/main/resources/jsv-messages_zh_CN.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u201Crefs\u201D\u6709\u9519\u8BEF
+additionalItems = {0}: \u7D22\u5F15\u201C{1}\u201D\u672A\u5728\u67B6\u6784\u4E2D\u5B9A\u4E49\uFF0C\u5E76\u4E14\u8BE5\u67B6\u6784\u4E0D\u5141\u8BB8\u9644\u52A0\u9879\u76EE
+additionalProperties = {0}: \u67B6\u6784\u4E2D\u672A\u5B9A\u4E49\u5C5E\u6027\u201C{1}\u201D\uFF0C\u5E76\u4E14\u67B6\u6784\u4E0D\u5141\u8BB8\u9644\u52A0\u5C5E\u6027
+allOf = {0}: \u5FC5\u987B\u5BF9\u6240\u6709\u67B6\u6784 {1} \u6709\u6548
+anyOf = {0}: \u5FC5\u987B\u5BF9\u4EFB\u4F55\u67B6\u6784 {1} \u6709\u6548
+const = {0}: \u5FC5\u987B\u662F\u5E38\u91CF\u503C\u201C{1}\u201D
+contains = {0}: \u4E0D\u5305\u542B\u901A\u8FC7\u8FD9\u4E9B\u9A8C\u8BC1\u7684\u5143\u7D20: {2}
+contains.max = {0}: \u5FC5\u987B\u5305\u542B\u6700\u591A {1} \u4E2A\u901A\u8FC7\u4EE5\u4E0B\u9A8C\u8BC1\u7684\u5143\u7D20: {2}
+contains.min = {0}: \u5FC5\u987B\u5305\u542B\u81F3\u5C11 {1} \u4E2A\u901A\u8FC7\u8FD9\u4E9B\u9A8C\u8BC1\u7684\u5143\u7D20: {2}
+dependencies = {0}: \u4F9D\u8D56\u9879 {1} \u5B58\u5728\u9519\u8BEF
+dependentRequired = {0}: \u7F3A\u5C11\u5C5E\u6027\u201C{1}\u201D\uFF0C\u8BE5\u5C5E\u6027\u662F\u4F9D\u8D56\u5FC5\u9700\u7684\uFF0C\u56E0\u4E3A\u5B58\u5728\u201C{2}\u201D
+dependentSchemas = {0}: dependentSchemas {1} \u5B58\u5728\u9519\u8BEF
+enum = {0}: \u679A\u4E3E {1} \u4E2D\u6CA1\u6709\u503C
+exclusiveMaximum = {0}: \u5FC5\u987B\u5177\u6709\u72EC\u5360\u6700\u5927\u503C {1}
+exclusiveMinimum = {0}: \u5FC5\u987B\u5177\u6709\u72EC\u5360\u6700\u5C0F\u503C {1}
+false = {0}: \u201C{1}\u201D\u7684\u67B6\u6784\u4E3A false
+format = {0}: \u4E0E {1} \u6A21\u5F0F {2} \u4E0D\u5339\u914D
+format.date = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u5B8C\u6574\u65E5\u671F
+format.date-time = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u65E5\u671F\u65F6\u95F4
+format.duration = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 ISO 8601 \u6301\u7EED\u65F6\u95F4
+format.email = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 5321 \u90AE\u7BB1
+format.ipv4 = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 2673 IP \u5730\u5740
+format.ipv6 = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 4291 IP \u5730\u5740
+format.idn-email = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6531 \u90AE\u7BB1
+format.idn-hostname = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 5890 \u56FD\u9645\u5316\u4E3B\u673A\u540D
+format.iri = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3987 IRI
+format.iri-reference = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3987 IRI-reference
+format.uri = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3986 URI
+format.uri-reference = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3986 URI \u5F15\u7528
+format.uri-template = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6570 URI \u6A21\u677F
+format.uuid = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 4122 UUID
+format.regex = {0}: \u4E0D\u5339\u914D {1} \u6A21\u5F0F\u5FC5\u987B\u662F\u6709\u6548\u7684 ECMA-262 \u6B63\u5219\u8868\u8FBE\u5F0F
+format.time = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 3339 \u65F6\u95F4
+format.hostname = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 1123 \u4E3B\u673A\u540D
+format.json-pointer = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 RFC 6901 JSON \u6307\u9488
+format.relative-json-pointer = {0}: \u4E0E {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u987B\u662F\u6709\u6548\u7684 IETF \u76F8\u5BF9 JSON \u6307\u9488
+format.unknown = {0}: \u683C\u5F0F\u672A\u77E5\u201C{1}\u201D
+id = {0}: \u201C{1}\u201D\u4E0D\u662F\u6709\u6548\u7684 {2}
+items = {0}: \u7D22\u5F15\u201C{1}\u201D\u672A\u5728\u67B6\u6784\u4E2D\u5B9A\u4E49\uFF0C\u5E76\u4E14\u8BE5\u67B6\u6784\u4E0D\u5141\u8BB8\u6DFB\u52A0\u5176\u4ED6\u9879\u76EE
+maxContains = {0}: \u5FC5\u987B\u662F {1} \u4E2D\u7684\u975E\u8D1F\u6574\u6570
+maxItems = {0}: \u6700\u591A\u5FC5\u987B\u6709 {1} \u4E2A\u9879\u76EE\uFF0C\u4F46\u627E\u5230\u4E86 {2} \u4E2A
+maxLength = {0}: \u957F\u5EA6\u4E0D\u5F97\u8D85\u8FC7 {1} \u4E2A\u5B57\u7B26
+maxProperties = {0}: \u6700\u591A\u5FC5\u987B\u6709 {1} \u4E2A\u5C5E\u6027
+maximum = {0}: \u6700\u5927\u503C\u5FC5\u987B\u4E3A {1}
+minContains = {0}: \u5FC5\u987B\u662F {1} \u4E2D\u7684\u975E\u8D1F\u6574\u6570
+minContainsVsMaxContains = {0}: minContains \u5FC5\u987B\u5C0F\u4E8E\u6216\u7B49\u4E8E {1} \u4E2D\u7684 maxContains
+minItems = {0}: \u5FC5\u987B\u81F3\u5C11\u6709 {1} \u4E2A\u9879\u76EE\uFF0C\u4F46\u5DF2\u627E\u5230 {2} \u4E2A
+minLength = {0}: \u957F\u5EA6\u5FC5\u987B\u81F3\u5C11\u4E3A {1} \u4E2A\u5B57\u7B26
+minProperties = {0}: \u5FC5\u987B\u81F3\u5C11\u5177\u6709 {1} \u4E2A\u5C5E\u6027
+minimum = {0}: \u6700\u5C0F\u503C\u5FC5\u987B\u4E3A {1}
+multipleOf = {0}: \u5FC5\u987B\u662F {1} \u7684\u500D\u6570
+not = {0}: \u5BF9\u4E8E\u67B6\u6784 {1} \u5FC5\u987B\u65E0\u6548
+notAllowed = {0}: \u4E0D\u5141\u8BB8\u4F7F\u7528\u5C5E\u6027\u201C{1}\u201D\uFF0C\u4F46\u5B83\u5B58\u5728\u4E8E\u6570\u636E\u4E2D
+oneOf = {0}: \u5FC5\u987B\u5BF9\u4E00\u4E2A\u4E14\u4EC5\u4E00\u4E2A\u67B6\u6784\u6709\u6548\uFF0C\u4F46 {1} \u6709\u6548
+oneOf.indexes = {0}: \u5FC5\u987B\u5BF9\u4E00\u4E2A\u4E14\u4EC5\u4E00\u4E2A\u67B6\u6784\u6709\u6548\uFF0C\u4F46 {1} \u5BF9\u7D22\u5F15\u201C{2}\u201D\u6709\u6548
+pattern = {0}: \u4E0E\u6B63\u5219\u8868\u8FBE\u5F0F\u6A21\u5F0F {1} \u4E0D\u5339\u914D
+patternProperties = {0}: \u201C\u6A21\u5F0F\u5C5E\u6027\u201D\u6709\u4E00\u4E9B\u9519\u8BEF
+prefixItems = {0}: \u5728\u6B64\u7D22\u5F15\u5904\u627E\u4E0D\u5230\u9A8C\u8BC1\u5668
+properties = {0}: \u201C\u5C5E\u6027\u201D\u6709\u9519\u8BEF
+propertyNames = {0}: \u5C5E\u6027\u201C{1}\u201D\u540D\u79F0\u65E0\u6548: {2}
+readOnly = {0}: \u662F\u53EA\u8BFB\u5B57\u6BB5\uFF0C\u65E0\u6CD5\u66F4\u6539
+required = {0}: \u672A\u627E\u5230\u6240\u9700\u5C5E\u6027\u201C{1}\u201D
+type = {0}: \u5DF2\u627E\u5230 {1}\uFF0C\u5FC5\u987B\u662F {2}
+unevaluatedItems = {0}: \u672A\u8BC4\u4F30\u7D22\u5F15\u201C{1}\u201D\uFF0C\u67B6\u6784\u4E0D\u5141\u8BB8\u672A\u8BC4\u4F30\u7684\u9879\u76EE
+unevaluatedProperties = {0}: \u672A\u8BC4\u4F30\u5C5E\u6027\u201C{1}\u201D\uFF0C\u5E76\u4E14\u67B6\u6784\u4E0D\u5141\u8BB8\u672A\u8BC4\u4F30\u7684\u5C5E\u6027
+unionType = {0}: \u5DF2\u627E\u5230 {1}\uFF0C\u5FC5\u987B\u662F {2}
+uniqueItems = {0}: \u6570\u7EC4\u4E2D\u5FC5\u987B\u4EC5\u5305\u542B\u552F\u4E00\u9879
+writeOnly = {0}: \u662F\u53EA\u5199\u5B57\u6BB5\uFF0C\u4E0D\u80FD\u51FA\u73B0\u5728\u6570\u636E\u4E2D
+contentEncoding = {0}: \u4E0E\u5185\u5BB9\u7F16\u7801 {1} \u4E0D\u5339\u914D
+contentMediaType = {0}: \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 0000000..c69f267
--- /dev/null
+++ b/src/main/resources/jsv-messages_zh_TW.properties
@@ -0,0 +1,70 @@
+$ref = {0}: \u300Crefs\u300D\u6709\u932F\u8AA4
+additionalItems = {0}: \u7D22\u5F15\u300C{1}\u300D\u672A\u5728\u67B6\u69CB\u4E2D\u5B9A\u7FA9\uFF0C\u4E14\u8A72\u67B6\u69CB\u4E0D\u5141\u8A31\u9644\u52A0\u9805\u76EE
+additionalProperties = {0}: \u67B6\u69CB\u4E2D\u672A\u5B9A\u7FA9\u5C6C\u6027\u201C{1}\u201D\uFF0C\u4E14\u67B6\u69CB\u4E0D\u5141\u8A31\u9644\u52A0\u5C6C\u6027
+allOf = {0}: \u5FC5\u9808\u5C0D\u6240\u6709\u67B6\u69CB {1} \u6709\u6548
+anyOf = {0}: \u5FC5\u9808\u5C0D\u4EFB\u4F55\u67B6\u69CB {1} \u6709\u6548
+const = {0}: \u5FC5\u9808\u662F\u5E38\u6578\u503C\u201C{1}\u201D
+contains = {0}: \u4E0D\u5305\u542B\u901A\u904E\u9019\u4E9B\u9A57\u8B49\u7684\u5143\u7D20: {2}
+contains.max = {0}: \u5FC5\u9808\u5305\u542B\u6700\u591A {1} \u500B\u901A\u904E\u4EE5\u4E0B\u9A57\u8B49\u7684\u5143\u7D20: {2}
+contains.min = {0}: \u5FC5\u9808\u5305\u542B\u81F3\u5C11 {1} \u500B\u901A\u904E\u9019\u4E9B\u9A57\u8B49\u7684\u5143\u7D20: {2}
+dependencies = {0}: \u4F9D\u8CF4\u9805 {1} \u5B58\u5728\u932F\u8AA4
+dependentRequired = {0}: \u7F3A\u5C11\u5C6C\u6027\u201C{1}\u201D\uFF0C\u8A72\u5C6C\u6027\u662F\u4F9D\u8CF4\u5FC5\u9700\u7684\uFF0C\u56E0\u70BA\u5B58\u5728\u201C{2}\u201D
+dependentSchemas = {0}: dependentSchemas {1} \u5B58\u5728\u932F\u8AA4
+enum = {0}: \u679A\u8209 {1} \u4E2D\u6C92\u6709\u503C
+exclusiveMaximum = {0}: \u5FC5\u9808\u5177\u6709\u7368\u4F54\u6700\u5927\u503C {1}
+exclusiveMinimum = {0}: \u5FC5\u9808\u5177\u6709\u7368\u4F54\u6700\u5C0F\u503C {1}
+false = {0}: \u300C{1}\u300D\u7684\u67B6\u69CB\u70BA false
+format = {0}: \u8207 {1} \u6A21\u5F0F {2} \u4E0D\u5339\u914D
+format.date = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u5B8C\u6574\u65E5\u671F
+format.date-time = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u65E5\u671F\u6642\u9593
+format.duration = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 ISO 8601 \u6301\u7E8C\u6642\u9593
+format.email = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 5321 \u90F5\u7BB1
+format.ipv4 = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 2673 IP \u4F4D\u5740
+format.ipv6 = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 4291 IP \u4F4D\u5740
+format.idn-email = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6531 \u90F5\u7BB1
+format.idn-hostname = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 5890 \u570B\u969B\u5316\u4E3B\u6A5F\u540D
+format.iri = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3987 IRI
+format.iri-reference = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3987 IRI-reference
+format.uri = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3986 URI
+format.uri-reference = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3986 URI \u5F15\u7528
+format.uri-template = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6570 URI \u6A21\u677F
+format.uuid = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 4122 UUID
+format.regex = {0}: \u4E0D\u7B26\u5408 {1} \u6A21\u5F0F\u5FC5\u9808\u662F\u6709\u6548\u7684 ECMA-262 \u6B63\u898F\u8868\u793A\u5F0F
+format.time = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 3339 \u6642\u9593
+format.hostname = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u5339\u914D\uFF0C\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 1123 \u4E3B\u6A5F\u540D
+format.json-pointer = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u7B26\u5FC5\u9808\u662F\u6709\u6548\u7684 RFC 6901 JSON \u6307\u91DD
+format.relative-json-pointer = {0}: \u8207 {1} \u6A21\u5F0F\u4E0D\u5339\u914D\u5FC5\u9808\u662F\u6709\u6548\u7684 IETF \u76F8\u5C0D JSON \u6307\u91DD
+format.unknown = {0}: \u683C\u5F0F\u672A\u77E5\u201C{1}\u201D
+id = {0}: \u300C{1}\u300D\u4E0D\u662F\u6709\u6548\u7684 {2}
+items = {0}: \u7D22\u5F15\u300C{1}\u300D\u672A\u5728\u67B6\u69CB\u4E2D\u5B9A\u7FA9\uFF0C\u4E14\u8A72\u67B6\u69CB\u4E0D\u5141\u8A31\u65B0\u589E\u5176\u4ED6\u9805\u76EE
+maxContains = {0}: \u5FC5\u9808\u662F {1} \u4E2D\u7684\u975E\u8CA0\u6574\u6578
+maxItems = {0}: \u6700\u591A\u5FC5\u9808\u6709 {1} \u500B\u9805\u76EE\uFF0C\u4F46\u627E\u5230\u4E86 {2} \u500B
+maxLength = {0}: \u9577\u5EA6\u4E0D\u5F97\u8D85\u904E {1} \u500B\u5B57\u5143
+maxProperties = {0}: \u6700\u591A\u5FC5\u9808\u6709 {1} \u500B\u5C6C\u6027
+maximum = {0}: \u6700\u5927\u503C\u5FC5\u9808\u70BA {1}
+minContains = {0}: \u5FC5\u9808\u662F {1} \u4E2D\u7684\u975E\u8CA0\u6574\u6578
+minContainsVsMaxContains = {0}: minContains \u5FC5\u9808\u5C0F\u65BC\u6216\u7B49\u65BC {1} \u4E2D\u7684 maxContains
+minItems = {0}: \u5FC5\u9808\u81F3\u5C11\u6709 {1} \u500B\u9805\u76EE\uFF0C\u4F46\u5DF2\u627E\u5230 {2} \u500B
+minLength = {0}: \u9577\u5EA6\u5FC5\u9808\u81F3\u5C11\u70BA {1} \u500B\u5B57\u5143
+minProperties = {0}: \u5FC5\u9808\u81F3\u5C11\u5177\u6709 {1} \u500B\u5C6C\u6027
+minimum = {0}: \u6700\u5C0F\u503C\u5FC5\u9808\u70BA {1}
+multipleOf = {0}: \u5FC5\u9808\u662F {1} \u7684\u500D\u6578
+not = {0}: \u5C0D\u65BC\u67B6\u69CB {1} \u5FC5\u9808\u7121\u6548
+notAllowed = {0}: \u4E0D\u5141\u8A31\u4F7F\u7528\u5C6C\u6027\u201C{1}\u201D\uFF0C\u4F46\u5B83\u5B58\u5728\u65BC\u8CC7\u6599\u4E2D
+oneOf = {0}: \u5FC5\u9808\u5C0D\u4E00\u500B\u4E14\u50C5\u4E00\u500B\u67B6\u69CB\u6709\u6548\uFF0C\u4F46 {1} \u6709\u6548
+oneOf.indexes = {0}: \u5FC5\u9808\u5C0D\u4E00\u500B\u4E14\u50C5\u4E00\u500B\u67B6\u69CB\u6709\u6548\uFF0C\u4F46 {1} \u5C0D\u7D22\u5F15\u300C{2}\u300D\u6709\u6548
+pattern = {0}: \u8207\u6B63\u898F\u8868\u793A\u5F0F\u6A21\u5F0F {1} \u4E0D\u5339\u914D
+patternProperties = {0}: \u300C\u6A21\u5F0F\u5C6C\u6027\u300D\u6709\u4E00\u4E9B\u932F\u8AA4
+prefixItems = {0}: \u5728\u6B64\u7D22\u5F15\u8655\u627E\u4E0D\u5230\u9A57\u8B49\u5668
+properties = {0}: \u300C\u5C6C\u6027\u300D\u6709\u932F\u8AA4
+propertyNames = {0}: \u5C6C\u6027\u300C{1}\u300D\u540D\u7A31\u7121\u6548: {2}
+readOnly = {0}: \u662F\u552F\u8B80\u5B57\u6BB5\uFF0C\u7121\u6CD5\u66F4\u6539
+required = {0}: \u672A\u627E\u5230\u6240\u9700\u5C6C\u6027\u201C{1}\u201D
+type = {0}: \u5DF2\u627E\u5230 {1}\uFF0C\u5FC5\u9808\u662F {2}
+unevaluatedItems = {0}: \u672A\u8A55\u4F30\u7D22\u5F15\u201C{1}\u201D\uFF0C\u67B6\u69CB\u4E0D\u5141\u8A31\u672A\u8A55\u4F30\u7684\u9805\u76EE
+unevaluatedProperties = {0}: \u672A\u8A55\u4F30\u5C6C\u6027\u201C{1}\u201D\uFF0C\u4E14\u67B6\u69CB\u4E0D\u5141\u8A31\u672A\u8A55\u4F30\u7684\u5C6C\u6027
+unionType = {0}: \u5DF2\u627E\u5230 {1}\uFF0C\u5FC5\u9808\u662F {2}
+uniqueItems = {0}: \u6578\u7D44\u4E2D\u5FC5\u9808\u53EA\u5305\u542B\u552F\u4E00\u9805
+writeOnly = {0}: \u662F\u53EA\u5BEB\u5B57\u6BB5\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8CC7\u6599\u4E2D
+contentEncoding = {0}: \u8207\u5167\u5BB9\u7DE8\u78BC {1} \u4E0D\u7B26
+contentMediaType = {0}: \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 0000000..2ac7f8a
--- /dev/null
+++ b/src/main/resources/ucd/RFC5892-appendix-B.txt
@@ -0,0 +1,2321 @@
+0000..002C ; DISALLOWED # <control>..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 # <reserved>..<reserved>
+037A ; DISALLOWED # GREEK YPOGEGRAMMENI
+037B..037D ; PVALID # GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GR
+037E ; DISALLOWED # GREEK QUESTION MARK
+037F..0383 ; UNASSIGNED # <reserved>..<reserved>
+0384..038A ; DISALLOWED # GREEK TONOS..GREEK CAPITAL LETTER IOTA WITH
+038B ; UNASSIGNED # <reserved>
+038C ; DISALLOWED # GREEK CAPITAL LETTER OMICRON WITH TONOS
+038D ; UNASSIGNED # <reserved>
+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 # <reserved>
+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 # <reserved>..<reserved>
+0531..0556 ; DISALLOWED # ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITA
+0557..0558 ; UNASSIGNED # <reserved>..<reserved>
+0559 ; PVALID # ARMENIAN MODIFIER LETTER LEFT HALF RING
+055A..055F ; DISALLOWED # ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION M
+0560 ; UNASSIGNED # <reserved>
+0561..0586 ; PVALID # ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LE
+0587 ; DISALLOWED # ARMENIAN SMALL LIGATURE ECH YIWN
+0588 ; UNASSIGNED # <reserved>
+0589..058A ; DISALLOWED # ARMENIAN FULL STOP..ARMENIAN HYPHEN
+058B..0590 ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+05D0..05EA ; PVALID # HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EB..05EF ; UNASSIGNED # <reserved>..<reserved>
+05F0..05F2 ; PVALID # HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW L
+05F3..05F4 ; CONTEXTO # HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATIO
+05F5..05FF ; UNASSIGNED # <reserved>..<reserved>
+0600..0603 ; DISALLOWED # ARABIC NUMBER SIGN..ARABIC SIGN SAFHA
+0604..0605 ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+061E..061F ; DISALLOWED # ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC Q
+0620 ; UNASSIGNED # <reserved>
+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 # <reserved>
+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 # <reserved>
+070F ; DISALLOWED # SYRIAC ABBREVIATION MARK
+0710..074A ; PVALID # SYRIAC LETTER ALAPH..SYRIAC BARREKH
+074B..074C ; UNASSIGNED # <reserved>..<reserved>
+074D..07B1 ; PVALID # SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER N
+07B2..07BF ; UNASSIGNED # <reserved>..<reserved>
+07C0..07F5 ; PVALID # NKO DIGIT ZERO..NKO LOW TONE APOSTROPHE
+07F6..07FA ; DISALLOWED # NKO SYMBOL OO DENNEN..NKO LAJANYALAN
+07FB..07FF ; UNASSIGNED # <reserved>..<reserved>
+0800..082D ; PVALID # SAMARITAN LETTER ALAF..SAMARITAN MARK NEQUDA
+082E..082F ; UNASSIGNED # <reserved>..<reserved>
+0830..083E ; DISALLOWED # SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUN
+083F..08FF ; UNASSIGNED # <reserved>..<reserved>
+0900..0939 ; PVALID # DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANA
+093A..093B ; UNASSIGNED # <reserved>..<reserved>
+093C..094E ; PVALID # DEVANAGARI SIGN NUKTA..DEVANAGARI VOWEL SIGN
+094F ; UNASSIGNED # <reserved>
+0950..0955 ; PVALID # DEVANAGARI OM..DEVANAGARI VOWEL SIGN CANDRA
+0956..0957 ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+0979..097F ; PVALID # DEVANAGARI LETTER ZHA..DEVANAGARI LETTER BBA
+0980 ; UNASSIGNED # <reserved>
+0981..0983 ; PVALID # BENGALI SIGN CANDRABINDU..BENGALI SIGN VISAR
+0984 ; UNASSIGNED # <reserved>
+0985..098C ; PVALID # BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098D..098E ; UNASSIGNED # <reserved>..<reserved>
+098F..0990 ; PVALID # BENGALI LETTER E..BENGALI LETTER AI
+0991..0992 ; UNASSIGNED # <reserved>..<reserved>
+0993..09A8 ; PVALID # BENGALI LETTER O..BENGALI LETTER NA
+09A9 ; UNASSIGNED # <reserved>
+09AA..09B0 ; PVALID # BENGALI LETTER PA..BENGALI LETTER RA
+09B1 ; UNASSIGNED # <reserved>
+09B2 ; PVALID # BENGALI LETTER LA
+09B3..09B5 ; UNASSIGNED # <reserved>..<reserved>
+09B6..09B9 ; PVALID # BENGALI LETTER SHA..BENGALI LETTER HA
+09BA..09BB ; UNASSIGNED # <reserved>..<reserved>
+09BC..09C4 ; PVALID # BENGALI SIGN NUKTA..BENGALI VOWEL SIGN VOCAL
+09C5..09C6 ; UNASSIGNED # <reserved>..<reserved>
+09C7..09C8 ; PVALID # BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09C9..09CA ; UNASSIGNED # <reserved>..<reserved>
+09CB..09CE ; PVALID # BENGALI VOWEL SIGN O..BENGALI LETTER KHANDA
+09CF..09D6 ; UNASSIGNED # <reserved>..<reserved>
+09D7 ; PVALID # BENGALI AU LENGTH MARK
+09D8..09DB ; UNASSIGNED # <reserved>..<reserved>
+09DC..09DD ; DISALLOWED # BENGALI LETTER RRA..BENGALI LETTER RHA
+09DE ; UNASSIGNED # <reserved>
+09DF ; DISALLOWED # BENGALI LETTER YYA
+09E0..09E3 ; PVALID # BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIG
+09E4..09E5 ; UNASSIGNED # <reserved>..<reserved>
+09E6..09F1 ; PVALID # BENGALI DIGIT ZERO..BENGALI LETTER RA WITH L
+09F2..09FB ; DISALLOWED # BENGALI RUPEE MARK..BENGALI GANDA MARK
+09FC..0A00 ; UNASSIGNED # <reserved>..<reserved>
+0A01..0A03 ; PVALID # GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN VISA
+0A04 ; UNASSIGNED # <reserved>
+0A05..0A0A ; PVALID # GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0B..0A0E ; UNASSIGNED # <reserved>..<reserved>
+0A0F..0A10 ; PVALID # GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A11..0A12 ; UNASSIGNED # <reserved>..<reserved>
+0A13..0A28 ; PVALID # GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A29 ; UNASSIGNED # <reserved>
+0A2A..0A30 ; PVALID # GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A31 ; UNASSIGNED # <reserved>
+0A32 ; PVALID # GURMUKHI LETTER LA
+0A33 ; DISALLOWED # GURMUKHI LETTER LLA
+0A34 ; UNASSIGNED # <reserved>
+0A35 ; PVALID # GURMUKHI LETTER VA
+0A36 ; DISALLOWED # GURMUKHI LETTER SHA
+0A37 ; UNASSIGNED # <reserved>
+0A38..0A39 ; PVALID # GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3A..0A3B ; UNASSIGNED # <reserved>..<reserved>
+0A3C ; PVALID # GURMUKHI SIGN NUKTA
+0A3D ; UNASSIGNED # <reserved>
+0A3E..0A42 ; PVALID # GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN
+0A43..0A46 ; UNASSIGNED # <reserved>..<reserved>
+0A47..0A48 ; PVALID # GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN
+0A49..0A4A ; UNASSIGNED # <reserved>..<reserved>
+0A4B..0A4D ; PVALID # GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A4E..0A50 ; UNASSIGNED # <reserved>..<reserved>
+0A51 ; PVALID # GURMUKHI SIGN UDAAT
+0A52..0A58 ; UNASSIGNED # <reserved>..<reserved>
+0A59..0A5B ; DISALLOWED # GURMUKHI LETTER KHHA..GURMUKHI LETTER ZA
+0A5C ; PVALID # GURMUKHI LETTER RRA
+0A5D ; UNASSIGNED # <reserved>
+0A5E ; DISALLOWED # GURMUKHI LETTER FA
+0A5F..0A65 ; UNASSIGNED # <reserved>..<reserved>
+0A66..0A75 ; PVALID # GURMUKHI DIGIT ZERO..GURMUKHI SIGN YAKASH
+0A76..0A80 ; UNASSIGNED # <reserved>..<reserved>
+0A81..0A83 ; PVALID # GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VIS
+0A84 ; UNASSIGNED # <reserved>
+0A85..0A8D ; PVALID # GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8E ; UNASSIGNED # <reserved>
+0A8F..0A91 ; PVALID # GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A92 ; UNASSIGNED # <reserved>
+0A93..0AA8 ; PVALID # GUJARATI LETTER O..GUJARATI LETTER NA
+0AA9 ; UNASSIGNED # <reserved>
+0AAA..0AB0 ; PVALID # GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB1 ; UNASSIGNED # <reserved>
+0AB2..0AB3 ; PVALID # GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB4 ; UNASSIGNED # <reserved>
+0AB5..0AB9 ; PVALID # GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABA..0ABB ; UNASSIGNED # <reserved>..<reserved>
+0ABC..0AC5 ; PVALID # GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CAN
+0AC6 ; UNASSIGNED # <reserved>
+0AC7..0AC9 ; PVALID # GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN C
+0ACA ; UNASSIGNED # <reserved>
+0ACB..0ACD ; PVALID # GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA
+0ACE..0ACF ; UNASSIGNED # <reserved>..<reserved>
+0AD0 ; PVALID # GUJARATI OM
+0AD1..0ADF ; UNASSIGNED # <reserved>..<reserved>
+0AE0..0AE3 ; PVALID # GUJARATI LETTER VOCALIC RR..GUJARATI VOWEL S
+0AE4..0AE5 ; UNASSIGNED # <reserved>..<reserved>
+0AE6..0AEF ; PVALID # GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+0AF0 ; UNASSIGNED # <reserved>
+0AF1 ; DISALLOWED # GUJARATI RUPEE SIGN
+0AF2..0B00 ; UNASSIGNED # <reserved>..<reserved>
+0B01..0B03 ; PVALID # ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA
+0B04 ; UNASSIGNED # <reserved>
+0B05..0B0C ; PVALID # ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0D..0B0E ; UNASSIGNED # <reserved>..<reserved>
+0B0F..0B10 ; PVALID # ORIYA LETTER E..ORIYA LETTER AI
+0B11..0B12 ; UNASSIGNED # <reserved>..<reserved>
+0B13..0B28 ; PVALID # ORIYA LETTER O..ORIYA LETTER NA
+0B29 ; UNASSIGNED # <reserved>
+0B2A..0B30 ; PVALID # ORIYA LETTER PA..ORIYA LETTER RA
+0B31 ; UNASSIGNED # <reserved>
+0B32..0B33 ; PVALID # ORIYA LETTER LA..ORIYA LETTER LLA
+0B34 ; UNASSIGNED # <reserved>
+0B35..0B39 ; PVALID # ORIYA LETTER VA..ORIYA LETTER HA
+0B3A..0B3B ; UNASSIGNED # <reserved>..<reserved>
+0B3C..0B44 ; PVALID # ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC R
+0B45..0B46 ; UNASSIGNED # <reserved>..<reserved>
+0B47..0B48 ; PVALID # ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B49..0B4A ; UNASSIGNED # <reserved>..<reserved>
+0B4B..0B4D ; PVALID # ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA
+0B4E..0B55 ; UNASSIGNED # <reserved>..<reserved>
+0B56..0B57 ; PVALID # ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK
+0B58..0B5B ; UNASSIGNED # <reserved>..<reserved>
+0B5C..0B5D ; DISALLOWED # ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5E ; UNASSIGNED # <reserved>
+0B5F..0B63 ; PVALID # ORIYA LETTER YYA..ORIYA VOWEL SIGN VOCALIC L
+0B64..0B65 ; UNASSIGNED # <reserved>..<reserved>
+0B66..0B6F ; PVALID # ORIYA DIGIT ZERO..ORIYA DIGIT NINE
+0B70 ; DISALLOWED # ORIYA ISSHAR
+0B71 ; PVALID # ORIYA LETTER WA
+0B72..0B81 ; UNASSIGNED # <reserved>..<reserved>
+0B82..0B83 ; PVALID # TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA
+0B84 ; UNASSIGNED # <reserved>
+0B85..0B8A ; PVALID # TAMIL LETTER A..TAMIL LETTER UU
+0B8B..0B8D ; UNASSIGNED # <reserved>..<reserved>
+0B8E..0B90 ; PVALID # TAMIL LETTER E..TAMIL LETTER AI
+0B91 ; UNASSIGNED # <reserved>
+0B92..0B95 ; PVALID # TAMIL LETTER O..TAMIL LETTER KA
+0B96..0B98 ; UNASSIGNED # <reserved>..<reserved>
+0B99..0B9A ; PVALID # TAMIL LETTER NGA..TAMIL LETTER CA
+0B9B ; UNASSIGNED # <reserved>
+0B9C ; PVALID # TAMIL LETTER JA
+0B9D ; UNASSIGNED # <reserved>
+0B9E..0B9F ; PVALID # TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA0..0BA2 ; UNASSIGNED # <reserved>..<reserved>
+0BA3..0BA4 ; PVALID # TAMIL LETTER NNA..TAMIL LETTER TA
+0BA5..0BA7 ; UNASSIGNED # <reserved>..<reserved>
+0BA8..0BAA ; PVALID # TAMIL LETTER NA..TAMIL LETTER PA
+0BAB..0BAD ; UNASSIGNED # <reserved>..<reserved>
+0BAE..0BB9 ; PVALID # TAMIL LETTER MA..TAMIL LETTER HA
+0BBA..0BBD ; UNASSIGNED # <reserved>..<reserved>
+0BBE..0BC2 ; PVALID # TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU
+0BC3..0BC5 ; UNASSIGNED # <reserved>..<reserved>
+0BC6..0BC8 ; PVALID # TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BC9 ; UNASSIGNED # <reserved>
+0BCA..0BCD ; PVALID # TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA
+0BCE..0BCF ; UNASSIGNED # <reserved>..<reserved>
+0BD0 ; PVALID # TAMIL OM
+0BD1..0BD6 ; UNASSIGNED # <reserved>..<reserved>
+0BD7 ; PVALID # TAMIL AU LENGTH MARK
+0BD8..0BE5 ; UNASSIGNED # <reserved>..<reserved>
+0BE6..0BEF ; PVALID # TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+0BF0..0BFA ; DISALLOWED # TAMIL NUMBER TEN..TAMIL NUMBER SIGN
+0BFB..0C00 ; UNASSIGNED # <reserved>..<reserved>
+0C01..0C03 ; PVALID # TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04 ; UNASSIGNED # <reserved>
+0C05..0C0C ; PVALID # TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0D ; UNASSIGNED # <reserved>
+0C0E..0C10 ; PVALID # TELUGU LETTER E..TELUGU LETTER AI
+0C11 ; UNASSIGNED # <reserved>
+0C12..0C28 ; PVALID # TELUGU LETTER O..TELUGU LETTER NA
+0C29 ; UNASSIGNED # <reserved>
+0C2A..0C33 ; PVALID # TELUGU LETTER PA..TELUGU LETTER LLA
+0C34 ; UNASSIGNED # <reserved>
+0C35..0C39 ; PVALID # TELUGU LETTER VA..TELUGU LETTER HA
+0C3A..0C3C ; UNASSIGNED # <reserved>..<reserved>
+0C3D..0C44 ; PVALID # TELUGU SIGN AVAGRAHA..TELUGU VOWEL SIGN VOCA
+0C45 ; UNASSIGNED # <reserved>
+0C46..0C48 ; PVALID # TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C49 ; UNASSIGNED # <reserved>
+0C4A..0C4D ; PVALID # TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C4E..0C54 ; UNASSIGNED # <reserved>..<reserved>
+0C55..0C56 ; PVALID # TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C57 ; UNASSIGNED # <reserved>
+0C58..0C59 ; PVALID # TELUGU LETTER TSA..TELUGU LETTER DZA
+0C5A..0C5F ; UNASSIGNED # <reserved>..<reserved>
+0C60..0C63 ; PVALID # TELUGU LETTER VOCALIC RR..TELUGU VOWEL SIGN
+0C64..0C65 ; UNASSIGNED # <reserved>..<reserved>
+0C66..0C6F ; PVALID # TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+0C70..0C77 ; UNASSIGNED # <reserved>..<reserved>
+0C78..0C7F ; DISALLOWED # TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF
+0C80..0C81 ; UNASSIGNED # <reserved>..<reserved>
+0C82..0C83 ; PVALID # KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C84 ; UNASSIGNED # <reserved>
+0C85..0C8C ; PVALID # KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8D ; UNASSIGNED # <reserved>
+0C8E..0C90 ; PVALID # KANNADA LETTER E..KANNADA LETTER AI
+0C91 ; UNASSIGNED # <reserved>
+0C92..0CA8 ; PVALID # KANNADA LETTER O..KANNADA LETTER NA
+0CA9 ; UNASSIGNED # <reserved>
+0CAA..0CB3 ; PVALID # KANNADA LETTER PA..KANNADA LETTER LLA
+0CB4 ; UNASSIGNED # <reserved>
+0CB5..0CB9 ; PVALID # KANNADA LETTER VA..KANNADA LETTER HA
+0CBA..0CBB ; UNASSIGNED # <reserved>..<reserved>
+0CBC..0CC4 ; PVALID # KANNADA SIGN NUKTA..KANNADA VOWEL SIGN VOCAL
+0CC5 ; UNASSIGNED # <reserved>
+0CC6..0CC8 ; PVALID # KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI
+0CC9 ; UNASSIGNED # <reserved>
+0CCA..0CCD ; PVALID # KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA
+0CCE..0CD4 ; UNASSIGNED # <reserved>..<reserved>
+0CD5..0CD6 ; PVALID # KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CD7..0CDD ; UNASSIGNED # <reserved>..<reserved>
+0CDE ; PVALID # KANNADA LETTER FA
+0CDF ; UNASSIGNED # <reserved>
+0CE0..0CE3 ; PVALID # KANNADA LETTER VOCALIC RR..KANNADA VOWEL SIG
+0CE4..0CE5 ; UNASSIGNED # <reserved>..<reserved>
+0CE6..0CEF ; PVALID # KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+0CF0 ; UNASSIGNED # <reserved>
+0CF1..0CF2 ; DISALLOWED # KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADH
+0CF3..0D01 ; UNASSIGNED # <reserved>..<reserved>
+0D02..0D03 ; PVALID # MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISA
+0D04 ; UNASSIGNED # <reserved>
+0D05..0D0C ; PVALID # MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC
+0D0D ; UNASSIGNED # <reserved>
+0D0E..0D10 ; PVALID # MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D11 ; UNASSIGNED # <reserved>
+0D12..0D28 ; PVALID # MALAYALAM LETTER O..MALAYALAM LETTER NA
+0D29 ; UNASSIGNED # <reserved>
+0D2A..0D39 ; PVALID # MALAYALAM LETTER PA..MALAYALAM LETTER HA
+0D3A..0D3C ; UNASSIGNED # <reserved>..<reserved>
+0D3D..0D44 ; PVALID # MALAYALAM SIGN AVAGRAHA..MALAYALAM VOWEL SIG
+0D45 ; UNASSIGNED # <reserved>
+0D46..0D48 ; PVALID # MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN
+0D49 ; UNASSIGNED # <reserved>
+0D4A..0D4D ; PVALID # MALAYALAM VOWEL SIGN O..MALAYALAM SIGN VIRAM
+0D4E..0D56 ; UNASSIGNED # <reserved>..<reserved>
+0D57 ; PVALID # MALAYALAM AU LENGTH MARK
+0D58..0D5F ; UNASSIGNED # <reserved>..<reserved>
+0D60..0D63 ; PVALID # MALAYALAM LETTER VOCALIC RR..MALAYALAM VOWEL
+0D64..0D65 ; UNASSIGNED # <reserved>..<reserved>
+0D66..0D6F ; PVALID # MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+0D70..0D75 ; DISALLOWED # MALAYALAM NUMBER TEN..MALAYALAM FRACTION THR
+0D76..0D78 ; UNASSIGNED # <reserved>..<reserved>
+0D79 ; DISALLOWED # MALAYALAM DATE MARK
+0D7A..0D7F ; PVALID # MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER
+0D80..0D81 ; UNASSIGNED # <reserved>..<reserved>
+0D82..0D83 ; PVALID # SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARG
+0D84 ; UNASSIGNED # <reserved>
+0D85..0D96 ; PVALID # SINHALA LETTER AYANNA..SINHALA LETTER AUYANN
+0D97..0D99 ; UNASSIGNED # <reserved>..<reserved>
+0D9A..0DB1 ; PVALID # SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA L
+0DB2 ; UNASSIGNED # <reserved>
+0DB3..0DBB ; PVALID # SINHALA LETTER SANYAKA DAYANNA..SINHALA LETT
+0DBC ; UNASSIGNED # <reserved>
+0DBD ; PVALID # SINHALA LETTER DANTAJA LAYANNA
+0DBE..0DBF ; UNASSIGNED # <reserved>..<reserved>
+0DC0..0DC6 ; PVALID # SINHALA LETTER VAYANNA..SINHALA LETTER FAYAN
+0DC7..0DC9 ; UNASSIGNED # <reserved>..<reserved>
+0DCA ; PVALID # SINHALA SIGN AL-LAKUNA
+0DCB..0DCE ; UNASSIGNED # <reserved>..<reserved>
+0DCF..0DD4 ; PVALID # SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL
+0DD5 ; UNASSIGNED # <reserved>
+0DD6 ; PVALID # SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DD7 ; UNASSIGNED # <reserved>
+0DD8..0DDF ; PVALID # SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOW
+0DE0..0DF1 ; UNASSIGNED # <reserved>..<reserved>
+0DF2..0DF3 ; PVALID # SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHAL
+0DF4 ; DISALLOWED # SINHALA PUNCTUATION KUNDDALIYA
+0DF5..0E00 ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+0E81..0E82 ; PVALID # LAO LETTER KO..LAO LETTER KHO SUNG
+0E83 ; UNASSIGNED # <reserved>
+0E84 ; PVALID # LAO LETTER KHO TAM
+0E85..0E86 ; UNASSIGNED # <reserved>..<reserved>
+0E87..0E88 ; PVALID # LAO LETTER NGO..LAO LETTER CO
+0E89 ; UNASSIGNED # <reserved>
+0E8A ; PVALID # LAO LETTER SO TAM
+0E8B..0E8C ; UNASSIGNED # <reserved>..<reserved>
+0E8D ; PVALID # LAO LETTER NYO
+0E8E..0E93 ; UNASSIGNED # <reserved>..<reserved>
+0E94..0E97 ; PVALID # LAO LETTER DO..LAO LETTER THO TAM
+0E98 ; UNASSIGNED # <reserved>
+0E99..0E9F ; PVALID # LAO LETTER NO..LAO LETTER FO SUNG
+0EA0 ; UNASSIGNED # <reserved>
+0EA1..0EA3 ; PVALID # LAO LETTER MO..LAO LETTER LO LING
+0EA4 ; UNASSIGNED # <reserved>
+0EA5 ; PVALID # LAO LETTER LO LOOT
+0EA6 ; UNASSIGNED # <reserved>
+0EA7 ; PVALID # LAO LETTER WO
+0EA8..0EA9 ; UNASSIGNED # <reserved>..<reserved>
+0EAA..0EAB ; PVALID # LAO LETTER SO SUNG..LAO LETTER HO SUNG
+0EAC ; UNASSIGNED # <reserved>
+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 # <reserved>
+0EBB..0EBD ; PVALID # LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN N
+0EBE..0EBF ; UNASSIGNED # <reserved>..<reserved>
+0EC0..0EC4 ; PVALID # LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC5 ; UNASSIGNED # <reserved>
+0EC6 ; PVALID # LAO KO LA
+0EC7 ; UNASSIGNED # <reserved>
+0EC8..0ECD ; PVALID # LAO TONE MAI EK..LAO NIGGAHITA
+0ECE..0ECF ; UNASSIGNED # <reserved>..<reserved>
+0ED0..0ED9 ; PVALID # LAO DIGIT ZERO..LAO DIGIT NINE
+0EDA..0EDB ; UNASSIGNED # <reserved>..<reserved>
+0EDC..0EDD ; DISALLOWED # LAO HO NO..LAO HO MO
+0EDE..0EFF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>
+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 # <reserved>
+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 # <reserved>
+0FCE..0FD8 ; DISALLOWED # TIBETAN SIGN RDEL NAG RDEL DKAR..LEFT-FACING
+0FD9..0FFF ; UNASSIGNED # <reserved>..<reserved>
+1000..1049 ; PVALID # MYANMAR LETTER KA..MYANMAR DIGIT NINE
+10000..1000B; PVALID # LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE
+1000C ; UNASSIGNED # <reserved>
+1000D..10026; PVALID # LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE
+10027 ; UNASSIGNED # <reserved>
+10028..1003A; PVALID # LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE
+1003B ; UNASSIGNED # <reserved>
+1003C..1003D; PVALID # LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE
+1003E ; UNASSIGNED # <reserved>
+1003F..1004D; PVALID # LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE
+1004E..1004F; UNASSIGNED # <reserved>..<reserved>
+10050..1005D; PVALID # LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+1005E..1007F; UNASSIGNED # <reserved>..<reserved>
+10080..100FA; PVALID # LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRA
+100FB..100FF; UNASSIGNED # <reserved>..<reserved>
+10100..10102; DISALLOWED # AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MAR
+10103..10106; UNASSIGNED # <reserved>..<reserved>
+10107..10133; DISALLOWED # AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOU
+10134..10136; UNASSIGNED # <reserved>..<reserved>
+10137..1018A; DISALLOWED # AEGEAN WEIGHT BASE UNIT..GREEK ZERO SIGN
+1018B..1018F; UNASSIGNED # <reserved>..<reserved>
+10190..1019B; DISALLOWED # ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN
+1019C..101CF; UNASSIGNED # <reserved>..<reserved>
+101D0..101FC; DISALLOWED # PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC
+101FD ; PVALID # PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+101FE..1027F; UNASSIGNED # <reserved>..<reserved>
+10280..1029C; PVALID # LYCIAN LETTER A..LYCIAN LETTER X
+1029D..1029F; UNASSIGNED # <reserved>..<reserved>
+102A0..102D0; PVALID # CARIAN LETTER A..CARIAN LETTER UUU3
+102D1..102FF; UNASSIGNED # <reserved>..<reserved>
+10300..1031E; PVALID # OLD ITALIC LETTER A..OLD ITALIC LETTER UU
+1031F ; UNASSIGNED # <reserved>
+10320..10323; DISALLOWED # OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL F
+10324..1032F; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+10380..1039D; PVALID # UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+1039E ; UNASSIGNED # <reserved>
+1039F ; DISALLOWED # UGARITIC WORD DIVIDER
+103A0..103C3; PVALID # OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C4..103C7; UNASSIGNED # <reserved>..<reserved>
+103C8..103CF; PVALID # OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIG
+103D0..103D5; DISALLOWED # OLD PERSIAN WORD DIVIDER..OLD PERSIAN NUMBER
+103D6..103FF; UNASSIGNED # <reserved>..<reserved>
+10400..10427; DISALLOWED # DESERET CAPITAL LETTER LONG I..DESERET CAPIT
+10428..1049D; PVALID # DESERET SMALL LETTER LONG I..OSMANYA LETTER
+1049E..1049F; UNASSIGNED # <reserved>..<reserved>
+104A..104F ; DISALLOWED # MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL
+104A0..104A9; PVALID # OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+104AA..107FF; UNASSIGNED # <reserved>..<reserved>
+1050..109D ; PVALID # MYANMAR LETTER SHA..MYANMAR VOWEL SIGN AITON
+10800..10805; PVALID # CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10806..10807; UNASSIGNED # <reserved>..<reserved>
+10808 ; PVALID # CYPRIOT SYLLABLE JO
+10809 ; UNASSIGNED # <reserved>
+1080A..10835; PVALID # CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10836 ; UNASSIGNED # <reserved>
+10837..10838; PVALID # CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+10839..1083B; UNASSIGNED # <reserved>..<reserved>
+1083C ; PVALID # CYPRIOT SYLLABLE ZA
+1083D..1083E; UNASSIGNED # <reserved>..<reserved>
+1083F..10855; PVALID # CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER
+10856 ; UNASSIGNED # <reserved>
+10857..1085F; DISALLOWED # IMPERIAL ARAMAIC SECTION SIGN..IMPERIAL ARAM
+10860..108FF; UNASSIGNED # <reserved>..<reserved>
+10900..10915; PVALID # PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10916..1091B; DISALLOWED # PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THR
+1091C..1091E; UNASSIGNED # <reserved>..<reserved>
+1091F ; DISALLOWED # PHOENICIAN WORD SEPARATOR
+10920..10939; PVALID # LYDIAN LETTER A..LYDIAN LETTER C
+1093A..1093E; UNASSIGNED # <reserved>..<reserved>
+1093F ; DISALLOWED # LYDIAN TRIANGULAR MARK
+10940..109FF; UNASSIGNED # <reserved>..<reserved>
+109E..10C5 ; DISALLOWED # MYANMAR SYMBOL SHAN ONE..GEORGIAN CAPITAL LE
+10A00..10A03; PVALID # KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN V
+10A04 ; UNASSIGNED # <reserved>
+10A05..10A06; PVALID # KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SI
+10A07..10A0B; UNASSIGNED # <reserved>..<reserved>
+10A0C..10A13; PVALID # KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI LET
+10A14 ; UNASSIGNED # <reserved>
+10A15..10A17; PVALID # KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A18 ; UNASSIGNED # <reserved>
+10A19..10A33; PVALID # KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER TTT
+10A34..10A37; UNASSIGNED # <reserved>..<reserved>
+10A38..10A3A; PVALID # KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN D
+10A3B..10A3E; UNASSIGNED # <reserved>..<reserved>
+10A3F ; PVALID # KHAROSHTHI VIRAMA
+10A40..10A47; DISALLOWED # KHAROSHTHI DIGIT ONE..KHAROSHTHI NUMBER ONE
+10A48..10A4F; UNASSIGNED # <reserved>..<reserved>
+10A50..10A58; DISALLOWED # KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCT
+10A59..10A5F; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+10B00..10B35; PVALID # AVESTAN LETTER A..AVESTAN LETTER HE
+10B36..10B38; UNASSIGNED # <reserved>..<reserved>
+10B39..10B3F; DISALLOWED # AVESTAN ABBREVIATION MARK..LARGE ONE RING OV
+10B40..10B55; PVALID # INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIP
+10B56..10B57; UNASSIGNED # <reserved>..<reserved>
+10B58..10B5F; DISALLOWED # INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTI
+10B60..10B72; PVALID # INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPT
+10B73..10B77; UNASSIGNED # <reserved>..<reserved>
+10B78..10B7F; DISALLOWED # INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIO
+10B80..10BFF; UNASSIGNED # <reserved>..<reserved>
+10C00..10C48; PVALID # OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTE
+10C49..10E5F; UNASSIGNED # <reserved>..<reserved>
+10C6..10CF ; UNASSIGNED # <reserved>..<reserved>
+10D0..10FA ; PVALID # GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10E60..10E7E; DISALLOWED # RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS
+10E7F..1107F; UNASSIGNED # <reserved>..<reserved>
+10FB..10FC ; DISALLOWED # GEORGIAN PARAGRAPH SEPARATOR..MODIFIER LETTE
+10FD..10FF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1200..1248 ; PVALID # ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA
+12000..1236E; PVALID # CUNEIFORM SIGN A..CUNEIFORM SIGN ZUM
+1236F..123FF; UNASSIGNED # <reserved>..<reserved>
+12400..12462; DISALLOWED # CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NU
+12463..1246F; UNASSIGNED # <reserved>..<reserved>
+12470..12473; DISALLOWED # CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD
+12474..12FFF; UNASSIGNED # <reserved>..<reserved>
+1249 ; UNASSIGNED # <reserved>
+124A..124D ; PVALID # ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+124E..124F ; UNASSIGNED # <reserved>..<reserved>
+1250..1256 ; PVALID # ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1257 ; UNASSIGNED # <reserved>
+1258 ; PVALID # ETHIOPIC SYLLABLE QHWA
+1259 ; UNASSIGNED # <reserved>
+125A..125D ; PVALID # ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QH
+125E..125F ; UNASSIGNED # <reserved>..<reserved>
+1260..1288 ; PVALID # ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+1289 ; UNASSIGNED # <reserved>
+128A..128D ; PVALID # ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+128E..128F ; UNASSIGNED # <reserved>..<reserved>
+1290..12B0 ; PVALID # ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B1 ; UNASSIGNED # <reserved>
+12B2..12B5 ; PVALID # ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B6..12B7 ; UNASSIGNED # <reserved>..<reserved>
+12B8..12BE ; PVALID # ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12BF ; UNASSIGNED # <reserved>
+12C0 ; PVALID # ETHIOPIC SYLLABLE KXWA
+12C1 ; UNASSIGNED # <reserved>
+12C2..12C5 ; PVALID # ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KX
+12C6..12C7 ; UNASSIGNED # <reserved>..<reserved>
+12C8..12D6 ; PVALID # ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHAR
+12D7 ; UNASSIGNED # <reserved>
+12D8..1310 ; PVALID # ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+13000..1342E; PVALID # EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYP
+1311 ; UNASSIGNED # <reserved>
+1312..1315 ; PVALID # ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1316..1317 ; UNASSIGNED # <reserved>..<reserved>
+1318..135A ; PVALID # ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+1342F..1CFFF; UNASSIGNED # <reserved>..<reserved>
+135B..135E ; UNASSIGNED # <reserved>..<reserved>
+135F ; PVALID # ETHIOPIC COMBINING GEMINATION MARK
+1360..137C ; DISALLOWED # ETHIOPIC SECTION MARK..ETHIOPIC NUMBER TEN T
+137D..137F ; UNASSIGNED # <reserved>..<reserved>
+1380..138F ; PVALID # ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SY
+1390..1399 ; DISALLOWED # ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MA
+139A..139F ; UNASSIGNED # <reserved>..<reserved>
+13A0..13F4 ; PVALID # CHEROKEE LETTER A..CHEROKEE LETTER YV
+13F5..13FF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+16A0..16EA ; PVALID # RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EB..16F0 ; DISALLOWED # RUNIC SINGLE PUNCTUATION..RUNIC BELGTHOR SYM
+16F1..16FF ; UNASSIGNED # <reserved>..<reserved>
+1700..170C ; PVALID # TAGALOG LETTER A..TAGALOG LETTER YA
+170D ; UNASSIGNED # <reserved>
+170E..1714 ; PVALID # TAGALOG LETTER LA..TAGALOG SIGN VIRAMA
+1715..171F ; UNASSIGNED # <reserved>..<reserved>
+1720..1734 ; PVALID # HANUNOO LETTER A..HANUNOO SIGN PAMUDPOD
+1735..1736 ; DISALLOWED # PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DO
+1737..173F ; UNASSIGNED # <reserved>..<reserved>
+1740..1753 ; PVALID # BUHID LETTER A..BUHID VOWEL SIGN U
+1754..175F ; UNASSIGNED # <reserved>..<reserved>
+1760..176C ; PVALID # TAGBANWA LETTER A..TAGBANWA LETTER YA
+176D ; UNASSIGNED # <reserved>
+176E..1770 ; PVALID # TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1771 ; UNASSIGNED # <reserved>
+1772..1773 ; PVALID # TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+1774..177F ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+17E0..17E9 ; PVALID # KHMER DIGIT ZERO..KHMER DIGIT NINE
+17EA..17EF ; UNASSIGNED # <reserved>..<reserved>
+17F0..17F9 ; DISALLOWED # KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK
+17FA..17FF ; UNASSIGNED # <reserved>..<reserved>
+1800..180E ; DISALLOWED # MONGOLIAN BIRGA..MONGOLIAN VOWEL SEPARATOR
+180F ; UNASSIGNED # <reserved>
+1810..1819 ; PVALID # MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+181A..181F ; UNASSIGNED # <reserved>..<reserved>
+1820..1877 ; PVALID # MONGOLIAN LETTER A..MONGOLIAN LETTER MANCHU
+1878..187F ; UNASSIGNED # <reserved>..<reserved>
+1880..18AA ; PVALID # MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONG
+18AB..18AF ; UNASSIGNED # <reserved>..<reserved>
+18B0..18F5 ; PVALID # CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CA
+18F6..18FF ; UNASSIGNED # <reserved>..<reserved>
+1900..191C ; PVALID # LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER HA
+191D..191F ; UNASSIGNED # <reserved>..<reserved>
+1920..192B ; PVALID # LIMBU VOWEL SIGN A..LIMBU SUBJOINED LETTER W
+192C..192F ; UNASSIGNED # <reserved>..<reserved>
+1930..193B ; PVALID # LIMBU SMALL LETTER KA..LIMBU SIGN SA-I
+193C..193F ; UNASSIGNED # <reserved>..<reserved>
+1940 ; DISALLOWED # LIMBU SIGN LOO
+1941..1943 ; UNASSIGNED # <reserved>..<reserved>
+1944..1945 ; DISALLOWED # LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK
+1946..196D ; PVALID # LIMBU DIGIT ZERO..TAI LE LETTER AI
+196E..196F ; UNASSIGNED # <reserved>..<reserved>
+1970..1974 ; PVALID # TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1975..197F ; UNASSIGNED # <reserved>..<reserved>
+1980..19AB ; PVALID # NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETT
+19AC..19AF ; UNASSIGNED # <reserved>..<reserved>
+19B0..19C9 ; PVALID # NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW
+19CA..19CF ; UNASSIGNED # <reserved>..<reserved>
+19D0..19DA ; PVALID # NEW TAI LUE DIGIT ZERO..NEW TAI LUE THAM DIG
+19DB..19DD ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1A1E..1A1F ; DISALLOWED # BUGINESE PALLAWA..BUGINESE END OF SECTION
+1A20..1A5E ; PVALID # TAI THAM LETTER HIGH KA..TAI THAM CONSONANT
+1A5F ; UNASSIGNED # <reserved>
+1A60..1A7C ; PVALID # TAI THAM SIGN SAKOT..TAI THAM SIGN KHUEN-LUE
+1A7D..1A7E ; UNASSIGNED # <reserved>..<reserved>
+1A7F..1A89 ; PVALID # TAI THAM COMBINING CRYPTOGRAMMIC DOT..TAI TH
+1A8A..1A8F ; UNASSIGNED # <reserved>..<reserved>
+1A90..1A99 ; PVALID # TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGI
+1A9A..1A9F ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1B00..1B4B ; PVALID # BALINESE SIGN ULU RICEM..BALINESE LETTER ASY
+1B4C..1B4F ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1B80..1BAA ; PVALID # SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PAMA
+1BAB..1BAD ; UNASSIGNED # <reserved>..<reserved>
+1BAE..1BB9 ; PVALID # SUNDANESE LETTER KHA..SUNDANESE DIGIT NINE
+1BBA..1BFF ; UNASSIGNED # <reserved>..<reserved>
+1C00..1C37 ; PVALID # LEPCHA LETTER KA..LEPCHA SIGN NUKTA
+1C38..1C3A ; UNASSIGNED # <reserved>..<reserved>
+1C3B..1C3F ; DISALLOWED # LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATIO
+1C40..1C49 ; PVALID # LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+1C4A..1C4C ; UNASSIGNED # <reserved>..<reserved>
+1C4D..1C7D ; PVALID # LEPCHA LETTER TTA..OL CHIKI AHAD
+1C7E..1C7F ; DISALLOWED # OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTU
+1C80..1CCF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1D00..1D2B ; PVALID # LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTE
+1D000..1D0F5; DISALLOWED # BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MU
+1D0F6..1D0FF; UNASSIGNED # <reserved>..<reserved>
+1D100..1D126; DISALLOWED # MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBO
+1D127..1D128; UNASSIGNED # <reserved>..<reserved>
+1D129..1D1DD; DISALLOWED # MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICA
+1D1DE..1D1FF; UNASSIGNED # <reserved>..<reserved>
+1D200..1D245; DISALLOWED # GREEK VOCAL NOTATION SYMBOL-1..GREEK MUSICAL
+1D246..1D2FF; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1D360..1D371; DISALLOWED # COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TE
+1D372..1D3FF; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>
+1D456..1D49C; DISALLOWED # MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SC
+1D49D ; UNASSIGNED # <reserved>
+1D49E..1D49F; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL
+1D4A0..1D4A1; UNASSIGNED # <reserved>..<reserved>
+1D4A2 ; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL G
+1D4A3..1D4A4; UNASSIGNED # <reserved>..<reserved>
+1D4A5..1D4A6; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL
+1D4A7..1D4A8; UNASSIGNED # <reserved>..<reserved>
+1D4A9..1D4AC; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL
+1D4AD ; UNASSIGNED # <reserved>
+1D4AE..1D4B9; DISALLOWED # MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL
+1D4BA ; UNASSIGNED # <reserved>
+1D4BB ; DISALLOWED # MATHEMATICAL SCRIPT SMALL F
+1D4BC ; UNASSIGNED # <reserved>
+1D4BD..1D4C3; DISALLOWED # MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SC
+1D4C4 ; UNASSIGNED # <reserved>
+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 # <reserved>
+1D507..1D50A; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL
+1D50B..1D50C; UNASSIGNED # <reserved>..<reserved>
+1D50D..1D514; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL
+1D515 ; UNASSIGNED # <reserved>
+1D516..1D51C; DISALLOWED # MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL
+1D51D ; UNASSIGNED # <reserved>
+1D51E..1D539; DISALLOWED # MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL D
+1D53A ; UNASSIGNED # <reserved>
+1D53B..1D53E; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEM
+1D53F ; UNASSIGNED # <reserved>
+1D540..1D544; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEM
+1D545 ; UNASSIGNED # <reserved>
+1D546 ; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D547..1D549; UNASSIGNED # <reserved>..<reserved>
+1D54A..1D550; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEM
+1D551 ; UNASSIGNED # <reserved>
+1D552..1D6A5; DISALLOWED # MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMAT
+1D6A6..1D6A7; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1D7CE..1D7FF; DISALLOWED # MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL M
+1D800..1EFFF; UNASSIGNED # <reserved>..<reserved>
+1D9B..1DBF ; DISALLOWED # MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER
+1DC0..1DE6 ; PVALID # COMBINING DOTTED GRAVE ACCENT..COMBINING LAT
+1DE7..1DFC ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1F030..1F093; DISALLOWED # DOMINO TILE HORIZONTAL BACK..DOMINO TILE VER
+1F08..1F0F ; DISALLOWED # GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK
+1F094..1F0FF; UNASSIGNED # <reserved>..<reserved>
+1F10..1F15 ; PVALID # GREEK SMALL LETTER EPSILON WITH PSILI..GREEK
+1F100..1F10A; DISALLOWED # DIGIT ZERO FULL STOP..DIGIT NINE COMMA
+1F10B..1F10F; UNASSIGNED # <reserved>..<reserved>
+1F110..1F12E; DISALLOWED # PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLE
+1F12F..1F130; UNASSIGNED # <reserved>..<reserved>
+1F131 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER B
+1F132..1F13C; UNASSIGNED # <reserved>..<reserved>
+1F13D ; DISALLOWED # SQUARED LATIN CAPITAL LETTER N
+1F13E ; UNASSIGNED # <reserved>
+1F13F ; DISALLOWED # SQUARED LATIN CAPITAL LETTER P
+1F140..1F141; UNASSIGNED # <reserved>..<reserved>
+1F142 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER S
+1F143..1F145; UNASSIGNED # <reserved>..<reserved>
+1F146 ; DISALLOWED # SQUARED LATIN CAPITAL LETTER W
+1F147..1F149; UNASSIGNED # <reserved>..<reserved>
+1F14A..1F14E; DISALLOWED # SQUARED HV..SQUARED PPV
+1F14F..1F156; UNASSIGNED # <reserved>..<reserved>
+1F157 ; DISALLOWED # NEGATIVE CIRCLED LATIN CAPITAL LETTER H
+1F158..1F15E; UNASSIGNED # <reserved>..<reserved>
+1F15F ; DISALLOWED # NEGATIVE CIRCLED LATIN CAPITAL LETTER P
+1F16..1F17 ; UNASSIGNED # <reserved>..<reserved>
+1F160..1F178; UNASSIGNED # <reserved>..<reserved>
+1F179 ; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER J
+1F17A ; UNASSIGNED # <reserved>
+1F17B..1F17C; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER L..NEG
+1F17D..1F17E; UNASSIGNED # <reserved>..<reserved>
+1F17F ; DISALLOWED # NEGATIVE SQUARED LATIN CAPITAL LETTER P
+1F18..1F1D ; DISALLOWED # GREEK CAPITAL LETTER EPSILON WITH PSILI..GRE
+1F180..1F189; UNASSIGNED # <reserved>..<reserved>
+1F18A..1F18D; DISALLOWED # CROSSED NEGATIVE SQUARED LATIN CAPITAL LETTE
+1F18E..1F18F; UNASSIGNED # <reserved>..<reserved>
+1F190 ; DISALLOWED # SQUARE DJ
+1F191..1F1FF; UNASSIGNED # <reserved>..<reserved>
+1F1E..1F1F ; UNASSIGNED # <reserved>..<reserved>
+1F20..1F27 ; PVALID # GREEK SMALL LETTER ETA WITH PSILI..GREEK SMA
+1F200 ; DISALLOWED # SQUARE HIRAGANA HOKA
+1F201..1F20F; UNASSIGNED # <reserved>..<reserved>
+1F210..1F231; DISALLOWED # SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED
+1F232..1F23F; UNASSIGNED # <reserved>..<reserved>
+1F240..1F248; DISALLOWED # TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRA
+1F249..1FFFD; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+1F48..1F4D ; DISALLOWED # GREEK CAPITAL LETTER OMICRON WITH PSILI..GRE
+1F4E..1F4F ; UNASSIGNED # <reserved>..<reserved>
+1F50..1F57 ; PVALID # GREEK SMALL LETTER UPSILON WITH PSILI..GREEK
+1F58 ; UNASSIGNED # <reserved>
+1F59 ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5A ; UNASSIGNED # <reserved>
+1F5B ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA AND
+1F5C ; UNASSIGNED # <reserved>
+1F5D ; DISALLOWED # GREEK CAPITAL LETTER UPSILON WITH DASIA AND
+1F5E ; UNASSIGNED # <reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>
+1FB6 ; PVALID # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FB7..1FC4 ; DISALLOWED # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AN
+1FC5 ; UNASSIGNED # <reserved>
+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 # <reserved>..<reserved>
+1FD6..1FD7 ; PVALID # GREEK SMALL LETTER IOTA WITH PERISPOMENI..GR
+1FD8..1FDB ; DISALLOWED # GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK
+1FDC ; UNASSIGNED # <reserved>
+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 # <reserved>..<reserved>
+1FF2..1FF4 ; DISALLOWED # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOG
+1FF5 ; UNASSIGNED # <reserved>
+1FF6 ; PVALID # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+1FF7..1FFE ; DISALLOWED # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AN
+1FFF ; UNASSIGNED # <reserved>
+1FFFE..1FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+2000..200B ; DISALLOWED # EN QUAD..ZERO WIDTH SPACE
+20000..2A6D6; PVALID # <CJK Ideograph Extension B>..<CJK Ideograph
+200C..200D ; CONTEXTJ # ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER
+200E..2064 ; DISALLOWED # LEFT-TO-RIGHT MARK..INVISIBLE PLUS
+2065..2069 ; UNASSIGNED # <reserved>..<reserved>
+206A..2071 ; DISALLOWED # INHIBIT SYMMETRIC SWAPPING..SUPERSCRIPT LATI
+2072..2073 ; UNASSIGNED # <reserved>..<reserved>
+2074..208E ; DISALLOWED # SUPERSCRIPT FOUR..SUBSCRIPT RIGHT PARENTHESI
+208F ; UNASSIGNED # <reserved>
+2090..2094 ; DISALLOWED # LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCR
+2095..209F ; UNASSIGNED # <reserved>..<reserved>
+20A0..20B8 ; DISALLOWED # EURO-CURRENCY SIGN..TENGE SIGN
+20B9..20CF ; UNASSIGNED # <reserved>..<reserved>
+20D0..20F0 ; DISALLOWED # COMBINING LEFT HARPOON ABOVE..COMBINING ASTE
+20F1..20FF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+2190..23E8 ; DISALLOWED # LEFTWARDS ARROW..DECIMAL EXPONENT SYMBOL
+23E9..23FF ; UNASSIGNED # <reserved>..<reserved>
+2400..2426 ; DISALLOWED # SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM
+2427..243F ; UNASSIGNED # <reserved>..<reserved>
+2440..244A ; DISALLOWED # OCR HOOK..OCR DOUBLE BACKSLASH
+244B..245F ; UNASSIGNED # <reserved>..<reserved>
+2460..26CD ; DISALLOWED # CIRCLED DIGIT ONE..DISABLED CAR
+26CE ; UNASSIGNED # <reserved>
+26CF..26E1 ; DISALLOWED # PICK..RESTRICTED LEFT ENTRY-2
+26E2 ; UNASSIGNED # <reserved>
+26E3 ; DISALLOWED # HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
+26E4..26E7 ; UNASSIGNED # <reserved>..<reserved>
+26E8..26FF ; DISALLOWED # BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZ
+2700 ; UNASSIGNED # <reserved>
+2701..2704 ; DISALLOWED # UPPER BLADE SCISSORS..WHITE SCISSORS
+2705 ; UNASSIGNED # <reserved>
+2706..2709 ; DISALLOWED # TELEPHONE LOCATION SIGN..ENVELOPE
+270A..270B ; UNASSIGNED # <reserved>..<reserved>
+270C..2727 ; DISALLOWED # VICTORY HAND..WHITE FOUR POINTED STAR
+2728 ; UNASSIGNED # <reserved>
+2729..274B ; DISALLOWED # STRESS OUTLINED WHITE STAR..HEAVY EIGHT TEAR
+274C ; UNASSIGNED # <reserved>
+274D ; DISALLOWED # SHADOWED WHITE CIRCLE
+274E ; UNASSIGNED # <reserved>
+274F..2752 ; DISALLOWED # LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPE
+2753..2755 ; UNASSIGNED # <reserved>..<reserved>
+2756..275E ; DISALLOWED # BLACK DIAMOND MINUS WHITE X..HEAVY DOUBLE CO
+275F..2760 ; UNASSIGNED # <reserved>..<reserved>
+2761..2794 ; DISALLOWED # CURVED STEM PARAGRAPH SIGN ORNAMENT..HEAVY W
+2795..2797 ; UNASSIGNED # <reserved>..<reserved>
+2798..27AF ; DISALLOWED # HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT-
+27B0 ; UNASSIGNED # <reserved>
+27B1..27BE ; DISALLOWED # NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARD
+27BF ; UNASSIGNED # <reserved>
+27C0..27CA ; DISALLOWED # THREE DIMENSIONAL ANGLE..VERTICAL BAR WITH H
+27CB ; UNASSIGNED # <reserved>
+27CC ; DISALLOWED # LONG DIVISION
+27CD..27CF ; UNASSIGNED # <reserved>..<reserved>
+27D0..2B4C ; DISALLOWED # WHITE DIAMOND WITH CENTRED DOT..RIGHTWARDS A
+2A6D7..2A6FF; UNASSIGNED # <reserved>..<reserved>
+2A700..2B734; PVALID # <CJK Ideograph Extension C>..<CJK Ideograph
+2B4D..2B4F ; UNASSIGNED # <reserved>..<reserved>
+2B50..2B59 ; DISALLOWED # WHITE MEDIUM STAR..HEAVY CIRCLED SALTIRE
+2B5A..2BFF ; UNASSIGNED # <reserved>..<reserved>
+2B735..2F7FF; UNASSIGNED # <reserved>..<reserved>
+2C00..2C2E ; DISALLOWED # GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CA
+2C2F ; UNASSIGNED # <reserved>
+2C30..2C5E ; PVALID # GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMAL
+2C5F ; UNASSIGNED # <reserved>
+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 # <reserved>..<reserved>
+2CF9..2CFF ; DISALLOWED # COPTIC OLD NUBIAN FULL STOP..COPTIC MORPHOLO
+2D00..2D25 ; PVALID # GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LET
+2D26..2D2F ; UNASSIGNED # <reserved>..<reserved>
+2D30..2D65 ; PVALID # TIFINAGH LETTER YA..TIFINAGH LETTER YAZZ
+2D66..2D6E ; UNASSIGNED # <reserved>..<reserved>
+2D6F ; DISALLOWED # TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D70..2D7F ; UNASSIGNED # <reserved>..<reserved>
+2D80..2D96 ; PVALID # ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGW
+2D97..2D9F ; UNASSIGNED # <reserved>..<reserved>
+2DA0..2DA6 ; PVALID # ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA7 ; UNASSIGNED # <reserved>
+2DA8..2DAE ; PVALID # ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DAF ; UNASSIGNED # <reserved>
+2DB0..2DB6 ; PVALID # ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB7 ; UNASSIGNED # <reserved>
+2DB8..2DBE ; PVALID # ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CC
+2DBF ; UNASSIGNED # <reserved>
+2DC0..2DC6 ; PVALID # ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC7 ; UNASSIGNED # <reserved>
+2DC8..2DCE ; PVALID # ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DCF ; UNASSIGNED # <reserved>
+2DD0..2DD6 ; PVALID # ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD7 ; UNASSIGNED # <reserved>
+2DD8..2DDE ; PVALID # ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2DDF ; UNASSIGNED # <reserved>
+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 # <reserved>..<reserved>
+2E80..2E99 ; DISALLOWED # CJK RADICAL REPEAT..CJK RADICAL RAP
+2E9A ; UNASSIGNED # <reserved>
+2E9B..2EF3 ; DISALLOWED # CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED
+2EF4..2EFF ; UNASSIGNED # <reserved>..<reserved>
+2F00..2FD5 ; DISALLOWED # KANGXI RADICAL ONE..KANGXI RADICAL FLUTE
+2F800..2FA1D; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPA
+2FA1E..2FFFD; UNASSIGNED # <reserved>..<reserved>
+2FD6..2FEF ; UNASSIGNED # <reserved>..<reserved>
+2FF0..2FFB ; DISALLOWED # IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RI
+2FFC..2FFF ; UNASSIGNED # <reserved>..<reserved>
+2FFFE..2FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+3000..3004 ; DISALLOWED # IDEOGRAPHIC SPACE..JAPANESE INDUSTRIAL STAND
+30000..3FFFD; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>
+3041..3096 ; PVALID # HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMA
+3097..3098 ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+3105..312D ; PVALID # BOPOMOFO LETTER B..BOPOMOFO LETTER IH
+312E..3130 ; UNASSIGNED # <reserved>..<reserved>
+3131..318E ; DISALLOWED # HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+318F ; UNASSIGNED # <reserved>
+3190..319F ; DISALLOWED # IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRA
+31A0..31B7 ; PVALID # BOPOMOFO LETTER BU..BOPOMOFO FINAL LETTER H
+31B8..31BF ; UNASSIGNED # <reserved>..<reserved>
+31C0..31E3 ; DISALLOWED # CJK STROKE T..CJK STROKE Q
+31E4..31EF ; UNASSIGNED # <reserved>..<reserved>
+31F0..31FF ; PVALID # KATAKANA LETTER SMALL KU..KATAKANA LETTER SM
+3200..321E ; DISALLOWED # PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED K
+321F ; UNASSIGNED # <reserved>
+3220..32FE ; DISALLOWED # PARENTHESIZED IDEOGRAPH ONE..CIRCLED KATAKAN
+32FF ; UNASSIGNED # <reserved>
+3300..33FF ; DISALLOWED # SQUARE APAATO..SQUARE GAL
+3400..4DB5 ; PVALID # <CJK Ideograph Extension A>..<CJK Ideograph
+3FFFE..3FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+40000..4FFFD; UNASSIGNED # <reserved>..<reserved>
+4DB6..4DBF ; UNASSIGNED # <reserved>..<reserved>
+4DC0..4DFF ; DISALLOWED # HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM F
+4E00..9FCB ; PVALID # <CJK Ideograph>..<CJK Ideograph>
+4FFFE..4FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+50000..5FFFD; UNASSIGNED # <reserved>..<reserved>
+5FFFE..5FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+60000..6FFFD; UNASSIGNED # <reserved>..<reserved>
+6FFFE..6FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+70000..7FFFD; UNASSIGNED # <reserved>..<reserved>
+7FFFE..7FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+80000..8FFFD; UNASSIGNED # <reserved>..<reserved>
+8FFFE..8FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+90000..9FFFD; UNASSIGNED # <reserved>..<reserved>
+9FCC..9FFF ; UNASSIGNED # <reserved>..<reserved>
+9FFFE..9FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+A000..A48C ; PVALID # YI SYLLABLE IT..YI SYLLABLE YYR
+A0000..AFFFD; UNASSIGNED # <reserved>..<reserved>
+A48D..A48F ; UNASSIGNED # <reserved>..<reserved>
+A490..A4C6 ; DISALLOWED # YI RADICAL QOT..YI RADICAL KE
+A4C7..A4CF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+A7FB..A827 ; PVALID # LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI N
+A828..A82B ; DISALLOWED # SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POE
+A82C..A82F ; UNASSIGNED # <reserved>..<reserved>
+A830..A839 ; DISALLOWED # NORTH INDIC FRACTION ONE QUARTER..NORTH INDI
+A83A..A83F ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+A880..A8C4 ; PVALID # SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VI
+A8C5..A8CD ; UNASSIGNED # <reserved>..<reserved>
+A8CE..A8CF ; DISALLOWED # SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA
+A8D0..A8D9 ; PVALID # SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+A8DA..A8DF ; UNASSIGNED # <reserved>..<reserved>
+A8E0..A8F7 ; PVALID # COMBINING DEVANAGARI DIGIT ZERO..DEVANAGARI
+A8F8..A8FA ; DISALLOWED # DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET
+A8FB ; PVALID # DEVANAGARI HEADSTROKE
+A8FC..A8FF ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+A95F..A97C ; DISALLOWED # REJANG SECTION MARK..HANGUL CHOSEONG SSANGYE
+A97D..A97F ; UNASSIGNED # <reserved>..<reserved>
+A980..A9C0 ; PVALID # JAVANESE SIGN PANYANGGA..JAVANESE PANGKON
+A9C1..A9CD ; DISALLOWED # JAVANESE LEFT RERENGGAN..JAVANESE TURNED PAD
+A9CE ; UNASSIGNED # <reserved>
+A9CF..A9D9 ; PVALID # JAVANESE PANGRANGKEP..JAVANESE DIGIT NINE
+A9DA..A9DD ; UNASSIGNED # <reserved>..<reserved>
+A9DE..A9DF ; DISALLOWED # JAVANESE PADA TIRTA TUMETES..JAVANESE PADA I
+A9E0..A9FF ; UNASSIGNED # <reserved>..<reserved>
+AA00..AA36 ; PVALID # CHAM LETTER A..CHAM CONSONANT SIGN WA
+AA37..AA3F ; UNASSIGNED # <reserved>..<reserved>
+AA40..AA4D ; PVALID # CHAM LETTER FINAL K..CHAM CONSONANT SIGN FIN
+AA4E..AA4F ; UNASSIGNED # <reserved>..<reserved>
+AA50..AA59 ; PVALID # CHAM DIGIT ZERO..CHAM DIGIT NINE
+AA5A..AA5B ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+AA80..AAC2 ; PVALID # TAI VIET LETTER LOW KO..TAI VIET TONE MAI SO
+AAC3..AADA ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+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 # <reserved>..<reserved>
+ABF0..ABF9 ; PVALID # MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT
+ABFA..ABFF ; UNASSIGNED # <reserved>..<reserved>
+AC00..D7A3 ; PVALID # <Hangul Syllable>..<Hangul Syllable>
+AFFFE..AFFFF; DISALLOWED # <noncharacter>..<noncharacter>
+B0000..BFFFD; UNASSIGNED # <reserved>..<reserved>
+BFFFE..BFFFF; DISALLOWED # <noncharacter>..<noncharacter>
+C0000..CFFFD; UNASSIGNED # <reserved>..<reserved>
+CFFFE..CFFFF; DISALLOWED # <noncharacter>..<noncharacter>
+D0000..DFFFD; UNASSIGNED # <reserved>..<reserved>
+D7A4..D7AF ; UNASSIGNED # <reserved>..<reserved>
+D7B0..D7C6 ; DISALLOWED # HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARA
+D7C7..D7CA ; UNASSIGNED # <reserved>..<reserved>
+D7CB..D7FB ; DISALLOWED # HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEO
+D7FC..D7FF ; UNASSIGNED # <reserved>..<reserved>
+D800..FA0D ; DISALLOWED # <Non Private Use High Surrogate>..CJK COMPAT
+DFFFE..DFFFF; DISALLOWED # <noncharacter>..<noncharacter>
+E0000 ; UNASSIGNED # <reserved>
+E0001 ; DISALLOWED # LANGUAGE TAG
+E0002..E001F; UNASSIGNED # <reserved>..<reserved>
+E0020..E007F; DISALLOWED # TAG SPACE..CANCEL TAG
+E0080..E00FF; UNASSIGNED # <reserved>..<reserved>
+E0100..E01EF; DISALLOWED # VARIATION SELECTOR-17..VARIATION SELECTOR-25
+E01F0..EFFFD; UNASSIGNED # <reserved>..<reserved>
+EFFFE..10FFFF; DISALLOWED # <noncharacter>..<noncharacter>
+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 # <reserved>..<reserved>
+FA30..FA6D ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA30..CJK COMPAT
+FA6E..FA6F ; UNASSIGNED # <reserved>..<reserved>
+FA70..FAD9 ; DISALLOWED # CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPAT
+FADA..FAFF ; UNASSIGNED # <reserved>..<reserved>
+FB00..FB06 ; DISALLOWED # LATIN SMALL LIGATURE FF..LATIN SMALL LIGATUR
+FB07..FB12 ; UNASSIGNED # <reserved>..<reserved>
+FB13..FB17 ; DISALLOWED # ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SM
+FB18..FB1C ; UNASSIGNED # <reserved>..<reserved>
+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 # <reserved>
+FB38..FB3C ; DISALLOWED # HEBREW LETTER TET WITH DAGESH..HEBREW LETTER
+FB3D ; UNASSIGNED # <reserved>
+FB3E ; DISALLOWED # HEBREW LETTER MEM WITH DAGESH
+FB3F ; UNASSIGNED # <reserved>
+FB40..FB41 ; DISALLOWED # HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER
+FB42 ; UNASSIGNED # <reserved>
+FB43..FB44 ; DISALLOWED # HEBREW LETTER FINAL PE WITH DAGESH..HEBREW L
+FB45 ; UNASSIGNED # <reserved>
+FB46..FBB1 ; DISALLOWED # HEBREW LETTER TSADI WITH DAGESH..ARABIC LETT
+FBB2..FBD2 ; UNASSIGNED # <reserved>..<reserved>
+FBD3..FD3F ; DISALLOWED # ARABIC LETTER NG ISOLATED FORM..ORNATE RIGHT
+FD40..FD4F ; UNASSIGNED # <reserved>..<reserved>
+FD50..FD8F ; DISALLOWED # ARABIC LIGATURE TEH WITH JEEM WITH MEEM INIT
+FD90..FD91 ; UNASSIGNED # <reserved>..<reserved>
+FD92..FDC7 ; DISALLOWED # ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INI
+FDC8..FDCF ; UNASSIGNED # <reserved>..<reserved>
+FDD0..FDFD ; DISALLOWED # <noncharacter>..ARABIC LIGATURE BISMILLAH AR
+FDFE..FDFF ; UNASSIGNED # <reserved>..<reserved>
+FE00..FE19 ; DISALLOWED # VARIATION SELECTOR-1..PRESENTATION FORM FOR
+FE1A..FE1F ; UNASSIGNED # <reserved>..<reserved>
+FE20..FE26 ; PVALID # COMBINING LIGATURE LEFT HALF..COMBINING CONJ
+FE27..FE2F ; UNASSIGNED # <reserved>..<reserved>
+FE30..FE52 ; DISALLOWED # PRESENTATION FORM FOR VERTICAL TWO DOT LEADE
+FE53 ; UNASSIGNED # <reserved>
+FE54..FE66 ; DISALLOWED # SMALL SEMICOLON..SMALL EQUALS SIGN
+FE67 ; UNASSIGNED # <reserved>
+FE68..FE6B ; DISALLOWED # SMALL REVERSE SOLIDUS..SMALL COMMERCIAL AT
+FE6C..FE6F ; UNASSIGNED # <reserved>..<reserved>
+FE70..FE72 ; DISALLOWED # ARABIC FATHATAN ISOLATED FORM..ARABIC DAMMAT
+FE73 ; PVALID # ARABIC TAIL FRAGMENT
+FE74 ; DISALLOWED # ARABIC KASRATAN ISOLATED FORM
+FE75 ; UNASSIGNED # <reserved>
+FE76..FEFC ; DISALLOWED # ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE
+FEFD..FEFE ; UNASSIGNED # <reserved>..<reserved>
+FEFF ; DISALLOWED # ZERO WIDTH NO-BREAK SPACE
+FF00 ; UNASSIGNED # <reserved>
+FF01..FFBE ; DISALLOWED # FULLWIDTH EXCLAMATION MARK..HALFWIDTH HANGUL
+FFBF..FFC1 ; UNASSIGNED # <reserved>..<reserved>
+FFC2..FFC7 ; DISALLOWED # HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL
+FFC8..FFC9 ; UNASSIGNED # <reserved>..<reserved>
+FFCA..FFCF ; DISALLOWED # HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGU
+FFD0..FFD1 ; UNASSIGNED # <reserved>..<reserved>
+FFD2..FFD7 ; DISALLOWED # HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL
+FFD8..FFD9 ; UNASSIGNED # <reserved>..<reserved>
+FFDA..FFDC ; DISALLOWED # HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL
+FFDD..FFDF ; UNASSIGNED # <reserved>..<reserved>
+FFE0..FFE6 ; DISALLOWED # FULLWIDTH CENT SIGN..FULLWIDTH WON SIGN
+FFE7 ; UNASSIGNED # <reserved>
+FFE8..FFEE ; DISALLOWED # HALFWIDTH FORMS LIGHT VERTICAL..HALFWIDTH WH
+FFEF..FFF8 ; UNASSIGNED # <reserved>..<reserved>
+FFF9..FFFF ; DISALLOWED # INTERLINEAR ANNOTATION ANCHOR..<noncharacter \ No newline at end of file
diff --git a/src/main/resources/ucd/extracted/DerivedJoiningType.txt b/src/main/resources/ucd/extracted/DerivedJoiningType.txt
new file mode 100644
index 0000000..62ce8a0
--- /dev/null
+++ b/src/main/resources/ucd/extracted/DerivedJoiningType.txt
@@ -0,0 +1,573 @@
+# DerivedJoiningType-15.0.0.txt
+# Date: 2022-04-26, 23:14:36 GMT
+# © 2022 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see https://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see https://www.unicode.org/reports/tr44/
+
+# ================================================
+
+# Type T is derived, as described in ArabicShaping.txt
+
+# All code points not explicitly listed for Joining_Type
+# have the value Non_Joining (U).
+
+# @missing: 0000..10FFFF; Non_Joining
+
+# ================================================
+
+# Joining_Type=Join_Causing
+
+0640 ; C # Lm ARABIC TATWEEL
+07FA ; C # Lm NKO LAJANYALAN
+0883..0885 ; C # Lo [3] ARABIC TATWEEL WITH OVERSTRUCK HAMZA..ARABIC TATWEEL WITH TWO DOTS BELOW
+180A ; C # Po MONGOLIAN NIRUGU
+200D ; C # Cf ZERO WIDTH JOINER
+
+# Total code points: 7
+
+# ================================================
+
+# Joining_Type=Dual_Joining
+
+0620 ; D # Lo ARABIC LETTER KASHMIRI YEH
+0626 ; D # Lo ARABIC LETTER YEH WITH HAMZA ABOVE
+0628 ; D # Lo ARABIC LETTER BEH
+062A..062E ; D # Lo [5] ARABIC LETTER TEH..ARABIC LETTER KHAH
+0633..063F ; D # Lo [13] ARABIC LETTER SEEN..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0641..0647 ; D # Lo [7] ARABIC LETTER FEH..ARABIC LETTER HEH
+0649..064A ; D # Lo [2] ARABIC LETTER ALEF MAKSURA..ARABIC LETTER YEH
+066E..066F ; D # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0678..0687 ; D # Lo [16] ARABIC LETTER HIGH HAMZA YEH..ARABIC LETTER TCHEHEH
+069A..06BF ; D # Lo [38] ARABIC LETTER SEEN WITH DOT BELOW AND DOT ABOVE..ARABIC LETTER TCHEH WITH DOT ABOVE
+06C1..06C2 ; D # Lo [2] ARABIC LETTER HEH GOAL..ARABIC LETTER HEH GOAL WITH HAMZA ABOVE
+06CC ; D # Lo ARABIC LETTER FARSI YEH
+06CE ; D # Lo ARABIC LETTER YEH WITH SMALL V
+06D0..06D1 ; D # Lo [2] ARABIC LETTER E..ARABIC LETTER YEH WITH THREE DOTS BELOW
+06FA..06FC ; D # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; D # Lo ARABIC LETTER HEH WITH INVERTED V
+0712..0714 ; D # Lo [3] SYRIAC LETTER BETH..SYRIAC LETTER GAMAL GARSHUNI
+071A..071D ; D # Lo [4] SYRIAC LETTER HETH..SYRIAC LETTER YUDH
+071F..0727 ; D # Lo [9] SYRIAC LETTER KAPH..SYRIAC LETTER REVERSED PE
+0729 ; D # Lo SYRIAC LETTER QAPH
+072B ; D # Lo SYRIAC LETTER SHIN
+072D..072E ; D # Lo [2] SYRIAC LETTER PERSIAN BHETH..SYRIAC LETTER PERSIAN GHAMAL
+074E..0758 ; D # Lo [11] SYRIAC LETTER SOGDIAN KHAPH..ARABIC LETTER HAH WITH THREE DOTS POINTING UPWARDS BELOW
+075C..076A ; D # Lo [15] ARABIC LETTER SEEN WITH FOUR DOTS ABOVE..ARABIC LETTER LAM WITH BAR
+076D..0770 ; D # Lo [4] ARABIC LETTER SEEN WITH TWO DOTS VERTICALLY ABOVE..ARABIC LETTER SEEN WITH SMALL ARABIC LETTER TAH AND TWO DOTS
+0772 ; D # Lo ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH ABOVE
+0775..0777 ; D # Lo [3] ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE..ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT FOUR BELOW
+077A..077F ; D # Lo [6] ARABIC LETTER YEH BARREE WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE..ARABIC LETTER KAF WITH TWO DOTS ABOVE
+07CA..07EA ; D # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+0841..0845 ; D # Lo [5] MANDAIC LETTER AB..MANDAIC LETTER USHENNA
+0848 ; D # Lo MANDAIC LETTER ATT
+084A..0853 ; D # Lo [10] MANDAIC LETTER AK..MANDAIC LETTER AR
+0855 ; D # Lo MANDAIC LETTER AT
+0860 ; D # Lo SYRIAC LETTER MALAYALAM NGA
+0862..0865 ; D # Lo [4] SYRIAC LETTER MALAYALAM NYA..SYRIAC LETTER MALAYALAM NNNA
+0868 ; D # Lo SYRIAC LETTER MALAYALAM LLA
+0886 ; D # Lo ARABIC LETTER THIN YEH
+0889..088D ; D # Lo [5] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW
+08A0..08A9 ; D # Lo [10] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER YEH WITH TWO DOTS BELOW AND DOT ABOVE
+08AF..08B0 ; D # Lo [2] ARABIC LETTER SAD WITH THREE DOTS BELOW..ARABIC LETTER GAF WITH INVERTED STROKE
+08B3..08B8 ; D # Lo [6] ARABIC LETTER AIN WITH THREE DOTS BELOW..ARABIC LETTER TEH WITH SMALL TEH ABOVE
+08BA..08C8 ; D # Lo [15] ARABIC LETTER YEH WITH TWO DOTS BELOW AND SMALL NOON ABOVE..ARABIC LETTER GRAF
+1807 ; D # Po MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER
+1820..1842 ; D # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; D # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; D # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1887..18A8 ; D # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18AA ; D # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+A840..A871 ; D # Lo [50] PHAGS-PA LETTER KA..PHAGS-PA SUBJOINED LETTER RA
+10AC0..10AC4 ; D # Lo [5] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER GHIMEL
+10AD3..10AD6 ; D # Lo [4] MANICHAEAN LETTER LAMEDH..MANICHAEAN LETTER MEM
+10AD8..10ADC ; D # Lo [5] MANICHAEAN LETTER SAMEKH..MANICHAEAN LETTER FE
+10ADE..10AE0 ; D # Lo [3] MANICHAEAN LETTER QOPH..MANICHAEAN LETTER QHOPH
+10AEB..10AEE ; D # No [4] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER TWENTY
+10B80 ; D # Lo PSALTER PAHLAVI LETTER ALEPH
+10B82 ; D # Lo PSALTER PAHLAVI LETTER GIMEL
+10B86..10B88 ; D # Lo [3] PSALTER PAHLAVI LETTER ZAYIN..PSALTER PAHLAVI LETTER YODH
+10B8A..10B8B ; D # Lo [2] PSALTER PAHLAVI LETTER LAMEDH..PSALTER PAHLAVI LETTER MEM-QOPH
+10B8D ; D # Lo PSALTER PAHLAVI LETTER SAMEKH
+10B90 ; D # Lo PSALTER PAHLAVI LETTER SHIN
+10BAD..10BAE ; D # No [2] PSALTER PAHLAVI NUMBER TEN..PSALTER PAHLAVI NUMBER TWENTY
+10D01..10D21 ; D # Lo [33] HANIFI ROHINGYA LETTER BA..HANIFI ROHINGYA VOWEL O
+10D23 ; D # Lo HANIFI ROHINGYA MARK NA KHONNA
+10F30..10F32 ; D # Lo [3] SOGDIAN LETTER ALEPH..SOGDIAN LETTER GIMEL
+10F34..10F44 ; D # Lo [17] SOGDIAN LETTER WAW..SOGDIAN LETTER LESH
+10F51..10F53 ; D # No [3] SOGDIAN NUMBER ONE..SOGDIAN NUMBER TWENTY
+10F70..10F73 ; D # Lo [4] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER WAW
+10F76..10F81 ; D # Lo [12] OLD UYGHUR LETTER YODH..OLD UYGHUR LETTER LESH
+10FB0 ; D # Lo CHORASMIAN LETTER ALEPH
+10FB2..10FB3 ; D # Lo [2] CHORASMIAN LETTER BETH..CHORASMIAN LETTER GIMEL
+10FB8 ; D # Lo CHORASMIAN LETTER ZAYIN
+10FBB..10FBC ; D # Lo [2] CHORASMIAN LETTER KAPH..CHORASMIAN LETTER LAMEDH
+10FBE..10FBF ; D # Lo [2] CHORASMIAN LETTER NUN..CHORASMIAN LETTER SAMEKH
+10FC1 ; D # Lo CHORASMIAN LETTER PE
+10FC4 ; D # Lo CHORASMIAN LETTER TAW
+10FCA ; D # No CHORASMIAN NUMBER TWENTY
+1E900..1E943 ; D # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+
+# Total code points: 610
+
+# ================================================
+
+# Joining_Type=Right_Joining
+
+0622..0625 ; R # Lo [4] ARABIC LETTER ALEF WITH MADDA ABOVE..ARABIC LETTER ALEF WITH HAMZA BELOW
+0627 ; R # Lo ARABIC LETTER ALEF
+0629 ; R # Lo ARABIC LETTER TEH MARBUTA
+062F..0632 ; R # Lo [4] ARABIC LETTER DAL..ARABIC LETTER ZAIN
+0648 ; R # Lo ARABIC LETTER WAW
+0671..0673 ; R # Lo [3] ARABIC LETTER ALEF WASLA..ARABIC LETTER ALEF WITH WAVY HAMZA BELOW
+0675..0677 ; R # Lo [3] ARABIC LETTER HIGH HAMZA ALEF..ARABIC LETTER U WITH HAMZA ABOVE
+0688..0699 ; R # Lo [18] ARABIC LETTER DDAL..ARABIC LETTER REH WITH FOUR DOTS ABOVE
+06C0 ; R # Lo ARABIC LETTER HEH WITH YEH ABOVE
+06C3..06CB ; R # Lo [9] ARABIC LETTER TEH MARBUTA GOAL..ARABIC LETTER VE
+06CD ; R # Lo ARABIC LETTER YEH WITH TAIL
+06CF ; R # Lo ARABIC LETTER WAW WITH DOT ABOVE
+06D2..06D3 ; R # Lo [2] ARABIC LETTER YEH BARREE..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; R # Lo ARABIC LETTER AE
+06EE..06EF ; R # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+0710 ; R # Lo SYRIAC LETTER ALAPH
+0715..0719 ; R # Lo [5] SYRIAC LETTER DALATH..SYRIAC LETTER ZAIN
+071E ; R # Lo SYRIAC LETTER YUDH HE
+0728 ; R # Lo SYRIAC LETTER SADHE
+072A ; R # Lo SYRIAC LETTER RISH
+072C ; R # Lo SYRIAC LETTER TAW
+072F ; R # Lo SYRIAC LETTER PERSIAN DHALATH
+074D ; R # Lo SYRIAC LETTER SOGDIAN ZHAIN
+0759..075B ; R # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW AND SMALL TAH..ARABIC LETTER REH WITH STROKE
+076B..076C ; R # Lo [2] ARABIC LETTER REH WITH TWO DOTS VERTICALLY ABOVE..ARABIC LETTER REH WITH HAMZA ABOVE
+0771 ; R # Lo ARABIC LETTER REH WITH SMALL ARABIC LETTER TAH AND TWO DOTS
+0773..0774 ; R # Lo [2] ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE..ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE
+0778..0779 ; R # Lo [2] ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE..ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE
+0840 ; R # Lo MANDAIC LETTER HALQA
+0846..0847 ; R # Lo [2] MANDAIC LETTER AZ..MANDAIC LETTER IT
+0849 ; R # Lo MANDAIC LETTER AKSA
+0854 ; R # Lo MANDAIC LETTER ASH
+0856..0858 ; R # Lo [3] MANDAIC LETTER DUSHENNA..MANDAIC LETTER AIN
+0867 ; R # Lo SYRIAC LETTER MALAYALAM RA
+0869..086A ; R # Lo [2] SYRIAC LETTER MALAYALAM LLLA..SYRIAC LETTER MALAYALAM SSA
+0870..0882 ; R # Lo [19] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC LETTER ALEF WITH ATTACHED LEFT HAMZA
+088E ; R # Lo ARABIC VERTICAL TAIL
+08AA..08AC ; R # Lo [3] ARABIC LETTER REH WITH LOOP..ARABIC LETTER ROHINGYA YEH
+08AE ; R # Lo ARABIC LETTER DAL WITH THREE DOTS BELOW
+08B1..08B2 ; R # Lo [2] ARABIC LETTER STRAIGHT WAW..ARABIC LETTER ZAIN WITH INVERTED V ABOVE
+08B9 ; R # Lo ARABIC LETTER REH WITH SMALL NOON ABOVE
+10AC5 ; R # Lo MANICHAEAN LETTER DALETH
+10AC7 ; R # Lo MANICHAEAN LETTER WAW
+10AC9..10ACA ; R # Lo [2] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER ZHAYIN
+10ACE..10AD2 ; R # Lo [5] MANICHAEAN LETTER TETH..MANICHAEAN LETTER KHAPH
+10ADD ; R # Lo MANICHAEAN LETTER SADHE
+10AE1 ; R # Lo MANICHAEAN LETTER RESH
+10AE4 ; R # Lo MANICHAEAN LETTER TAW
+10AEF ; R # No MANICHAEAN NUMBER ONE HUNDRED
+10B81 ; R # Lo PSALTER PAHLAVI LETTER BETH
+10B83..10B85 ; R # Lo [3] PSALTER PAHLAVI LETTER DALETH..PSALTER PAHLAVI LETTER WAW-AYIN-RESH
+10B89 ; R # Lo PSALTER PAHLAVI LETTER KAPH
+10B8C ; R # Lo PSALTER PAHLAVI LETTER NUN
+10B8E..10B8F ; R # Lo [2] PSALTER PAHLAVI LETTER PE..PSALTER PAHLAVI LETTER SADHE
+10B91 ; R # Lo PSALTER PAHLAVI LETTER TAW
+10BA9..10BAC ; R # No [4] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER FOUR
+10D22 ; R # Lo HANIFI ROHINGYA MARK SAKIN
+10F33 ; R # Lo SOGDIAN LETTER HE
+10F54 ; R # No SOGDIAN NUMBER ONE HUNDRED
+10F74..10F75 ; R # Lo [2] OLD UYGHUR LETTER ZAYIN..OLD UYGHUR LETTER FINAL HETH
+10FB4..10FB6 ; R # Lo [3] CHORASMIAN LETTER DALETH..CHORASMIAN LETTER WAW
+10FB9..10FBA ; R # Lo [2] CHORASMIAN LETTER HETH..CHORASMIAN LETTER YODH
+10FBD ; R # Lo CHORASMIAN LETTER MEM
+10FC2..10FC3 ; R # Lo [2] CHORASMIAN LETTER RESH..CHORASMIAN LETTER SHIN
+10FC9 ; R # No CHORASMIAN NUMBER TEN
+
+# Total code points: 152
+
+# ================================================
+
+# Joining_Type=Left_Joining
+
+A872 ; L # Lo PHAGS-PA SUPERFIXED LETTER RA
+10ACD ; L # Lo MANICHAEAN LETTER HETH
+10AD7 ; L # Lo MANICHAEAN LETTER NUN
+10D00 ; L # Lo HANIFI ROHINGYA LETTER A
+10FCB ; L # No CHORASMIAN NUMBER ONE HUNDRED
+
+# Total code points: 5
+
+# ================================================
+
+# Joining_Type=Transparent
+
+00AD ; T # Cf SOFT HYPHEN
+0300..036F ; T # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0483..0487 ; T # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489 ; T # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+0591..05BD ; T # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; T # Mn HEBREW POINT RAFE
+05C1..05C2 ; T # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; T # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; T # Mn HEBREW POINT QAMATS QATAN
+0610..061A ; T # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+061C ; T # Cf ARABIC LETTER MARK
+064B..065F ; T # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0670 ; T # Mn ARABIC LETTER SUPERSCRIPT ALEF
+06D6..06DC ; T # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; T # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E7..06E8 ; T # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; T # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+070F ; T # Cf SYRIAC ABBREVIATION MARK
+0711 ; T # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0730..074A ; T # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+07A6..07B0 ; T # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07EB..07F3 ; T # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07FD ; T # Mn NKO DANTAYALAN
+0816..0819 ; T # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081B..0823 ; T # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0825..0827 ; T # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0829..082D ; T # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0859..085B ; T # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0898..089F ; T # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA
+08CA..08E1 ; T # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; T # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+093A ; T # Mn DEVANAGARI VOWEL SIGN OE
+093C ; T # Mn DEVANAGARI SIGN NUKTA
+0941..0948 ; T # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+094D ; T # Mn DEVANAGARI SIGN VIRAMA
+0951..0957 ; T # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0962..0963 ; T # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0981 ; T # Mn BENGALI SIGN CANDRABINDU
+09BC ; T # Mn BENGALI SIGN NUKTA
+09C1..09C4 ; T # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09CD ; T # Mn BENGALI SIGN VIRAMA
+09E2..09E3 ; T # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; T # Mn BENGALI SANDHI MARK
+0A01..0A02 ; T # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A3C ; T # Mn GURMUKHI SIGN NUKTA
+0A41..0A42 ; T # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; T # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; T # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; T # Mn GURMUKHI SIGN UDAAT
+0A70..0A71 ; T # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A75 ; T # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; T # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0ABC ; T # Mn GUJARATI SIGN NUKTA
+0AC1..0AC5 ; T # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; T # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0ACD ; T # Mn GUJARATI SIGN VIRAMA
+0AE2..0AE3 ; T # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AFA..0AFF ; T # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; T # Mn ORIYA SIGN CANDRABINDU
+0B3C ; T # Mn ORIYA SIGN NUKTA
+0B3F ; T # Mn ORIYA VOWEL SIGN I
+0B41..0B44 ; T # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B4D ; T # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; T # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B62..0B63 ; T # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B82 ; T # Mn TAMIL SIGN ANUSVARA
+0BC0 ; T # Mn TAMIL VOWEL SIGN II
+0BCD ; T # Mn TAMIL SIGN VIRAMA
+0C00 ; T # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; T # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C3C ; T # Mn TELUGU SIGN NUKTA
+0C3E..0C40 ; T # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C46..0C48 ; T # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; T # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; T # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C62..0C63 ; T # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C81 ; T # Mn KANNADA SIGN CANDRABINDU
+0CBC ; T # Mn KANNADA SIGN NUKTA
+0CBF ; T # Mn KANNADA VOWEL SIGN I
+0CC6 ; T # Mn KANNADA VOWEL SIGN E
+0CCC..0CCD ; T # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CE2..0CE3 ; T # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0D00..0D01 ; T # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D3B..0D3C ; T # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D41..0D44 ; T # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D4D ; T # Mn MALAYALAM SIGN VIRAMA
+0D62..0D63 ; T # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D81 ; T # Mn SINHALA SIGN CANDRABINDU
+0DCA ; T # Mn SINHALA SIGN AL-LAKUNA
+0DD2..0DD4 ; T # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; T # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0E31 ; T # Mn THAI CHARACTER MAI HAN-AKAT
+0E34..0E3A ; T # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E47..0E4E ; T # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0EB1 ; T # Mn LAO VOWEL SIGN MAI KAN
+0EB4..0EBC ; T # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EC8..0ECE ; T # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0F18..0F19 ; T # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F35 ; T # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; T # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; T # Mn TIBETAN MARK TSA -PHRU
+0F71..0F7E ; T # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F80..0F84 ; T # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; T # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F8D..0F97 ; T # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; T # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; T # Mn TIBETAN SYMBOL PADMA GDAN
+102D..1030 ; T # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1032..1037 ; T # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1039..103A ; T # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103D..103E ; T # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+1058..1059 ; T # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105E..1060 ; T # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1071..1074 ; T # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1082 ; T # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1085..1086 ; T # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+108D ; T # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+109D ; T # Mn MYANMAR VOWEL SIGN AITON AI
+135D..135F ; T # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1712..1714 ; T # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1732..1733 ; T # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1752..1753 ; T # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1772..1773 ; T # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+17B4..17B5 ; T # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B7..17BD ; T # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17C6 ; T # Mn KHMER SIGN NIKAHIT
+17C9..17D3 ; T # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17DD ; T # Mn KHMER SIGN ATTHACAN
+180B..180D ; T # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; T # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1885..1886 ; T # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+18A9 ; T # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+1920..1922 ; T # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1927..1928 ; T # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1932 ; T # Mn LIMBU SMALL LETTER ANUSVARA
+1939..193B ; T # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1A17..1A18 ; T # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A1B ; T # Mn BUGINESE VOWEL SIGN AE
+1A56 ; T # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A58..1A5E ; T # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; T # Mn TAI THAM SIGN SAKOT
+1A62 ; T # Mn TAI THAM VOWEL SIGN MAI SAT
+1A65..1A6C ; T # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A73..1A7C ; T # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; T # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1AB0..1ABD ; T # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE ; T # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE ; T # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; T # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B34 ; T # Mn BALINESE SIGN REREKAN
+1B36..1B3A ; T # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3C ; T # Mn BALINESE VOWEL SIGN LA LENGA
+1B42 ; T # Mn BALINESE VOWEL SIGN PEPET
+1B6B..1B73 ; T # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; T # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1BA2..1BA5 ; T # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA8..1BA9 ; T # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAB..1BAD ; T # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BE6 ; T # Mn BATAK SIGN TOMPI
+1BE8..1BE9 ; T # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BED ; T # Mn BATAK VOWEL SIGN KARO O
+1BEF..1BF1 ; T # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1C2C..1C33 ; T # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C36..1C37 ; T # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1CD0..1CD2 ; T # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; T # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE2..1CE8 ; T # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CED ; T # Mn VEDIC SIGN TIRYAK
+1CF4 ; T # Mn VEDIC TONE CANDRA ABOVE
+1CF8..1CF9 ; T # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1DC0..1DFF ; T # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+200B ; T # Cf ZERO WIDTH SPACE
+200E..200F ; T # Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK
+202A..202E ; T # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+2060..2064 ; T # Cf [5] WORD JOINER..INVISIBLE PLUS
+206A..206F ; T # Cf [6] INHIBIT SYMMETRIC SWAPPING..NOMINAL DIGIT SHAPES
+20D0..20DC ; T # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0 ; T # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1 ; T # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4 ; T # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0 ; T # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2CEF..2CF1 ; T # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2D7F ; T # Mn TIFINAGH CONSONANT JOINER
+2DE0..2DFF ; T # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+302A..302D ; T # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+3099..309A ; T # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+A66F ; T # Mn COMBINING CYRILLIC VZMET
+A670..A672 ; T # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A674..A67D ; T # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A69E..A69F ; T # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6F0..A6F1 ; T # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A802 ; T # Mn SYLOTI NAGRI SIGN DVISVARA
+A806 ; T # Mn SYLOTI NAGRI SIGN HASANTA
+A80B ; T # Mn SYLOTI NAGRI SIGN ANUSVARA
+A825..A826 ; T # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A82C ; T # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4..A8C5 ; T # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8E0..A8F1 ; T # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; T # Mn DEVANAGARI VOWEL SIGN AY
+A926..A92D ; T # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A947..A951 ; T # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A980..A982 ; T # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A9B3 ; T # Mn JAVANESE SIGN CECAK TELU
+A9B6..A9B9 ; T # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BC..A9BD ; T # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9E5 ; T # Mn MYANMAR SIGN SHAN SAW
+AA29..AA2E ; T # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA31..AA32 ; T # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA35..AA36 ; T # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA43 ; T # Mn CHAM CONSONANT SIGN FINAL NG
+AA4C ; T # Mn CHAM CONSONANT SIGN FINAL M
+AA7C ; T # Mn MYANMAR SIGN TAI LAING TONE-2
+AAB0 ; T # Mn TAI VIET MAI KANG
+AAB2..AAB4 ; T # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB7..AAB8 ; T # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AABE..AABF ; T # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC1 ; T # Mn TAI VIET TONE MAI THO
+AAEC..AAED ; T # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAF6 ; T # Mn MEETEI MAYEK VIRAMA
+ABE5 ; T # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE8 ; T # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABED ; T # Mn MEETEI MAYEK APUN IYEK
+FB1E ; T # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FE00..FE0F ; T # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; T # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FEFF ; T # Cf ZERO WIDTH NO-BREAK SPACE
+FFF9..FFFB ; T # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+101FD ; T # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+102E0 ; T # Mn COPTIC EPACT THOUSANDS MARK
+10376..1037A ; T # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10A01..10A03 ; T # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; T # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; T # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A38..10A3A ; T # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; T # Mn KHAROSHTHI VIRAMA
+10AE5..10AE6 ; T # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10D24..10D27 ; T # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10EAB..10EAC ; T # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFD..10EFF ; T # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA
+10F46..10F50 ; T # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F82..10F85 ; T # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+11001 ; T # Mn BRAHMI SIGN ANUSVARA
+11038..11046 ; T # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11070 ; T # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11073..11074 ; T # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+1107F..11081 ; T # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+110B3..110B6 ; T # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B9..110BA ; T # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; T # Mn KAITHI VOWEL SIGN VOCALIC R
+11100..11102 ; T # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11127..1112B ; T # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112D..11134 ; T # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11173 ; T # Mn MAHAJANI SIGN NUKTA
+11180..11181 ; T # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+111B6..111BE ; T # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111C9..111CC ; T # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CF ; T # Mn SHARADA SIGN INVERTED CANDRABINDU
+1122F..11231 ; T # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11234 ; T # Mn KHOJKI SIGN ANUSVARA
+11236..11237 ; T # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; T # Mn KHOJKI SIGN SUKUN
+11241 ; T # Mn KHOJKI VOWEL SIGN VOCALIC R
+112DF ; T # Mn KHUDAWADI SIGN ANUSVARA
+112E3..112EA ; T # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+11300..11301 ; T # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+1133B..1133C ; T # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+11340 ; T # Mn GRANTHA VOWEL SIGN II
+11366..1136C ; T # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; T # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+11438..1143F ; T # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11442..11444 ; T # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11446 ; T # Mn NEWA SIGN NUKTA
+1145E ; T # Mn NEWA SANDHI MARK
+114B3..114B8 ; T # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114BA ; T # Mn TIRHUTA VOWEL SIGN SHORT E
+114BF..114C0 ; T # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C2..114C3 ; T # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+115B2..115B5 ; T # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115BC..115BD ; T # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BF..115C0 ; T # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115DC..115DD ; T # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11633..1163A ; T # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163D ; T # Mn MODI SIGN ANUSVARA
+1163F..11640 ; T # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+116AB ; T # Mn TAKRI SIGN ANUSVARA
+116AD ; T # Mn TAKRI VOWEL SIGN AA
+116B0..116B5 ; T # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B7 ; T # Mn TAKRI SIGN NUKTA
+1171D..1171F ; T # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11722..11725 ; T # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11727..1172B ; T # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+1182F..11837 ; T # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; T # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+1193B..1193C ; T # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193E ; T # Mn DIVES AKURU VIRAMA
+11943 ; T # Mn DIVES AKURU SIGN NUKTA
+119D4..119D7 ; T # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; T # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; T # Mn NANDINAGARI SIGN VIRAMA
+11A01..11A0A ; T # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A33..11A38 ; T # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A3B..11A3E ; T # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; T # Mn ZANABAZAR SQUARE SUBJOINER
+11A51..11A56 ; T # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A59..11A5B ; T # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A8A..11A96 ; T # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A98..11A99 ; T # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11C30..11C36 ; T # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; T # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3F ; T # Mn BHAIKSUKI SIGN VIRAMA
+11C92..11CA7 ; T # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CAA..11CB0 ; T # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB2..11CB3 ; T # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB5..11CB6 ; T # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D31..11D36 ; T # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; T # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; T # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; T # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D47 ; T # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; T # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D95 ; T # Mn GUNJALA GONDI SIGN ANUSVARA
+11D97 ; T # Mn GUNJALA GONDI VIRAMA
+11EF3..11EF4 ; T # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; T # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; T # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; T # Mn KAWI VOWEL SIGN EU
+11F42 ; T # Mn KAWI CONJOINER
+13430..1343F ; T # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE
+13440 ; T # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; T # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+16AF0..16AF4 ; T # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; T # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16F4F ; T # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F8F..16F92 ; T # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16FE4 ; T # Mn KHITAN SMALL SCRIPT FILLER
+1BC9D..1BC9E ; T # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1BCA0..1BCA3 ; T # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+1CF00..1CF2D ; T # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; T # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D167..1D169 ; T # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D173..1D17A ; T # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+1D17B..1D182 ; T # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; T # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; T # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; T # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1DA00..1DA36 ; T # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; T # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; T # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; T # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; T # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; T # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1E000..1E006 ; T # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; T # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; T # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; T # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; T # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; T # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E130..1E136 ; T # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2AE ; T # Mn TOTO SIGN RISING TONE
+1E2EC..1E2EF ; T # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EC..1E4EF ; T # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E8D0..1E8D6 ; T # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E944..1E94A ; T # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1E94B ; T # Lm ADLAM NASALIZATION MARK
+E0001 ; T # Cf LANGUAGE TAG
+E0020..E007F ; T # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF ; T # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 2150
+
+# EOF
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 0000000..3207ad0
--- /dev/null
+++ b/src/test/java/com/networknt/schema/AbsoluteIriTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 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: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 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 0000000..7df7b31
--- /dev/null
+++ b/src/test/java/com/networknt/schema/AbstractJsonSchemaTest.java
@@ -0,0 +1,70 @@
+package com.networknt.schema;
+
+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.text.MessageFormat;
+import java.util.Optional;
+import java.util.Set;
+
+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
+ */
+public abstract class AbstractJsonSchemaTest {
+
+ private static final String SCHEMA = "$schema";
+ private static final SpecVersion.VersionFlag DEFAULT_VERSION_FLAG = SpecVersion.VersionFlag.V202012;
+ private static final String ASSERT_MSG_ERROR_CODE = "Validation result should contain {0} error code";
+ private static final String ASSERT_MSG_TYPE = "Validation result should contain {0} type";
+
+ protected Set<ValidationMessage> validate(String dataPath) {
+ JsonNode dataNode = getJsonNodeFromPath(dataPath);
+ return getJsonSchemaFromDataNode(dataNode).validate(dataNode);
+ }
+
+ protected void assertValidatorType(String filename, ValidatorTypeCode validatorTypeCode) {
+ Set<ValidationMessage> validationMessages = validate(getDataTestFolder() + filename);
+
+ assertTrue(
+ validationMessages.stream().anyMatch(vm -> validatorTypeCode.getErrorCode().equals(vm.getCode())),
+ () -> MessageFormat.format(ASSERT_MSG_ERROR_CODE, validatorTypeCode.getErrorCode()));
+ assertTrue(
+ validationMessages.stream().anyMatch(vm -> validatorTypeCode.getValue().equals(vm.getType())),
+ () -> MessageFormat.format(ASSERT_MSG_TYPE, validatorTypeCode.getValue()));
+ }
+
+ protected abstract String getDataTestFolder();
+
+ private JsonSchema 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 JsonSchema getJsonSchema(JsonNode schemaNode) {
+ return JsonSchemaFactory
+ .getInstance(SpecVersionDetector.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 0000000..66a6856
--- /dev/null
+++ b/src/test/java/com/networknt/schema/AbstractJsonSchemaTestSuite.java
@@ -0,0 +1,254 @@
+/*
+ * 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.ObjectMapper;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+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.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.SpecVersionDetector.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;
+
+public abstract class AbstractJsonSchemaTestSuite extends HTTPServiceSupport {
+
+
+ protected ObjectMapper mapper = JsonMapperFactory.getInstance();
+
+ private static String toForwardSlashPath(Path file) {
+ return file.toString().replace('\\', '/');
+ }
+
+ private static void executeTest(JsonSchema schema, TestSpec testSpec) {
+ Set<ValidationMessage> errors = schema.validate(testSpec.getData(), OutputFormat.DEFAULT, (executionContext, validationContext) -> {
+ if (testSpec.getTestCase().getSource().getPath().getParent().toString().endsWith("format")) {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(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(ValidationMessage::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<String> actual = errors.stream().map(ValidationMessage::getMessage).collect(Collectors.toSet());
+ Set<String> expected = testSpec.getValidationMessages();
+ 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<? extends DynamicNode> 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<DynamicNode> createTests(VersionFlag 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<String> reason(@SuppressWarnings("unused") Path path) {
+ return Optional.empty();
+ }
+
+ private Stream<DynamicNode> buildContainers(VersionFlag 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<DynamicNode> buildContainer(VersionFlag defaultVersion, TestSource testSource) {
+ return testSource.getTestCases().stream().map(testCase -> buildContainer(defaultVersion, testCase));
+ }
+
+ private DynamicNode buildContainer(VersionFlag defaultVersion, TestCase testCase) {
+ try {
+ JsonSchemaFactory validatorFactory = buildValidatorFactory(defaultVersion, testCase);
+
+ return dynamicContainer(testCase.getDisplayName(), testCase.getTests().stream().map(testSpec -> {
+ return buildTest(validatorFactory, testSpec);
+ }));
+ } catch (JsonSchemaException e) {
+ String msg = e.getMessage();
+ if (msg.endsWith("' is unrecognizable schema")) {
+ return dynamicContainer(testCase.getDisplayName(), unsupportedMetaSchema(testCase));
+ }
+ throw e;
+ }
+ }
+
+ private JsonSchemaFactory buildValidatorFactory(VersionFlag defaultVersion, TestCase testCase) {
+ if (testCase.isDisabled()) return null;
+
+ VersionFlag specVersion = detectVersion(testCase.getSchema(), testCase.getSpecification(), defaultVersion, false);
+ JsonSchemaFactory base = JsonSchemaFactory.getInstance(specVersion);
+ return JsonSchemaFactory
+ .builder(base)
+ .jsonMapper(this.mapper)
+ .schemaMappers(schemaMappers -> schemaMappers
+ .mapPrefix("https://", "http://")
+ .mapPrefix("http://json-schema.org", "resource:"))
+ .build();
+ }
+
+ private DynamicNode buildTest(JsonSchemaFactory validatorFactory, TestSpec testSpec) {
+ if (testSpec.isDisabled()) {
+ return dynamicTest(testSpec.getDescription(), () -> abortAndReset(testSpec.getReason()));
+ }
+
+ // 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();
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setTypeLoose(typeLoose);
+ config.setEcma262Validator(TestSpec.RegexKind.JDK != testSpec.getRegex());
+ testSpec.getStrictness().forEach(config::setStrict);
+
+ if (testSpec.getConfig() != null) {
+ if (testSpec.getConfig().containsKey("isCustomMessageSupported")) {
+ config.setCustomMessageSupported((Boolean) testSpec.getConfig().get("isCustomMessageSupported"));
+ }
+ if (testSpec.getConfig().containsKey("readOnly")) {
+ config.setReadOnly((Boolean) testSpec.getConfig().get("readOnly"));
+ }
+ if (testSpec.getConfig().containsKey("writeOnly")) {
+ config.setWriteOnly((Boolean) testSpec.getConfig().get("writeOnly"));
+ }
+ }
+
+ SchemaLocation testCaseFileUri = SchemaLocation.of("classpath:" + toForwardSlashPath(testSpec.getTestCase().getSpecification()));
+ JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testSpec.getTestCase().getSchema(), config);
+
+ return dynamicTest(testSpec.getDescription(), () -> executeAndReset(schema, testSpec));
+ }
+
+ private void abortAndReset(String reason) {
+ try {
+ abort(reason);
+ } finally {
+ cleanup();
+ }
+ }
+
+ private void executeAndReset(JsonSchema schema, TestSpec testSpec) {
+ try {
+ executeTest(schema, testSpec);
+ } finally {
+ cleanup();
+ }
+ }
+
+ private List<Path> findTestCases(String basePath) {
+ try (Stream<Path> 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/AdditionalPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java
new file mode 100644
index 0000000..c133d83
--- /dev/null
+++ b/src/test/java/com/networknt/schema/AdditionalPropertiesValidatorTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * AdditionalPropertiesValidatorTest.
+ */
+public 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"foo\":\"hello\",\r\n"
+ + " \"bar\":\"world\"\r\n"
+ + "}";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"foo\":\"hello\",\r\n"
+ + " \"bar\":\"world\"\r\n"
+ + "}";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ assertNull(message.getProperty());
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java
new file mode 100644
index 0000000..5650cd4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.serialization.JsonMapperFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * Created by steve on 22/10/16.
+ */
+public class BaseJsonSchemaValidatorTest {
+
+ private static final ObjectMapper mapper = JsonMapperFactory.getInstance();
+
+ public static JsonNode getJsonNodeFromClasspath(String name) throws IOException {
+ InputStream is1 = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(name);
+ return mapper.readTree(is1);
+ }
+
+ public static JsonNode getJsonNodeFromStringContent(String content) throws IOException {
+ return mapper.readTree(content);
+ }
+
+ public static JsonNode getJsonNodeFromUrl(String url) throws IOException {
+ return mapper.readTree(new URL(url));
+ }
+
+ public static JsonSchema getJsonSchemaFromClasspath(String name) {
+ return getJsonSchemaFromClasspath(name, SpecVersion.VersionFlag.V4, null);
+ }
+
+ public static JsonSchema getJsonSchemaFromClasspath(String name, SpecVersion.VersionFlag schemaVersion) {
+ return getJsonSchemaFromClasspath(name, schemaVersion, null);
+ }
+
+ public static JsonSchema getJsonSchemaFromClasspath(String name, SpecVersion.VersionFlag schemaVersion, SchemaValidatorsConfig config) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(schemaVersion);
+ InputStream is = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(name);
+ if (config == null) {
+ return factory.getSchema(is);
+ }
+ return factory.getSchema(is, config);
+ }
+
+ public static JsonSchema getJsonSchemaFromStringContent(String schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ return factory.getSchema(schemaContent);
+ }
+
+ public static JsonSchema getJsonSchemaFromUrl(String uri) throws URISyntaxException {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ return factory.getSchema(SchemaLocation.of(uri));
+ }
+
+ public static JsonSchema getJsonSchemaFromJsonNode(JsonNode jsonNode) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ return factory.getSchema(jsonNode);
+ }
+
+ // Automatically detect version for given JsonNode
+ public static JsonSchema getJsonSchemaFromJsonNodeAutomaticVersion(JsonNode jsonNode) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(jsonNode));
+ return factory.getSchema(jsonNode);
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/CollectorContextTest.java b/src/test/java/com/networknt/schema/CollectorContextTest.java
new file mode 100644
index 0000000..ea281e7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CollectorContextTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+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.Test;
+
+import java.io.IOException;
+import java.util.*;
+
+public class CollectorContextTest {
+
+ private static final String SAMPLE_COLLECTOR = "sampleCollector";
+
+ private static final String SAMPLE_COLLECTOR_OTHER = "sampleCollectorOther";
+
+ private JsonSchema jsonSchema;
+
+ private JsonSchema jsonSchemaForCombine;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ setupSchema();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCollectorContextWithKeyword() throws Exception {
+ ValidationResult validationResult = validate("{\"test-property1\":\"sample1\",\"test-property2\":\"sample2\"}");
+ Assertions.assertEquals(0, validationResult.getValidationMessages().size());
+ List<String> contextValues = (List<String>) validationResult.getCollectorContext().get(SAMPLE_COLLECTOR);
+ contextValues.sort(null);
+ Assertions.assertEquals(0, validationResult.getValidationMessages().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 {
+
+ ValidationThread validationRunnable1 = new ValidationThread("{\"test-property1\":\"sample1\" }", "thread1");
+ ValidationThread validationRunnable2 = new ValidationThread("{\"test-property1\":\"sample2\" }", "thread2");
+ ValidationThread validationRunnable3 = new ValidationThread("{\"test-property1\":\"sample3\" }", "thread3");
+
+ // This simulates calling the validateAndCollect method from three different
+ // threads.It should be noted that all these three threads use same
+ // json schema instance.Create three threads with there own Runnables.
+ Thread thread1 = new Thread(validationRunnable1);
+ Thread thread2 = new Thread(validationRunnable2);
+ Thread thread3 = new Thread(validationRunnable3);
+
+ thread1.start();
+ thread2.start();
+ thread3.start();
+
+ thread1.join();
+ thread2.join();
+ thread3.join();
+
+ ValidationResult validationResult1 = validationRunnable1.getValidationResult();
+ ValidationResult validationResult2 = validationRunnable2.getValidationResult();
+ ValidationResult validationResult3 = validationRunnable3.getValidationResult();
+
+ Assertions.assertEquals(0, validationResult1.getValidationMessages().size());
+ Assertions.assertEquals(0, validationResult2.getValidationMessages().size());
+ Assertions.assertEquals(0, validationResult3.getValidationMessages().size());
+
+ List<String> contextValue1 = (List<String>) validationResult1.getCollectorContext().get(SAMPLE_COLLECTOR);
+ List<String> contextValue2 = (List<String>) validationResult2.getCollectorContext().get(SAMPLE_COLLECTOR);
+ List<String> contextValue3 = (List<String>) validationResult3.getCollectorContext().get(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 {
+ ObjectMapper objectMapper = new ObjectMapper();
+ ExecutionContext executionContext = jsonSchemaForCombine.createExecutionContext();
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
+ ValidationResult validationResult = jsonSchemaForCombine.validateAndCollect(executionContext, objectMapper
+ .readTree("{\"property1\":\"sample1\",\"property2\":\"sample2\",\"property3\":\"sample3\" }"));
+ CollectorContext collectorContext = validationResult.getCollectorContext();
+ Assertions.assertEquals(((List<String>) collectorContext.get(SAMPLE_COLLECTOR)).size(), 1);
+ Assertions.assertEquals(((List<String>) collectorContext.get(SAMPLE_COLLECTOR_OTHER)).size(), 3);
+ }
+
+ private JsonMetaSchema getJsonMetaSchema(String uri) throws Exception {
+ JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(uri, JsonMetaSchema.getV201909())
+ .keyword(new CustomKeyword()).keyword(new CustomKeyword1()).format(new Format() {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean matches(ExecutionContext executionContext, String value) {
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+ if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
+ collectorContext.add(SAMPLE_COLLECTOR, new ArrayList<String>());
+ }
+ List<String> returnList = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
+ 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;
+ }
+
+ private void setupSchema() throws Exception {
+ final JsonMetaSchema metaSchema = getJsonMetaSchema(
+ "https://github.com/networknt/json-schema-validator/tests/schemas/example01");
+ final JsonSchemaFactory schemaFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
+ .build();
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ this.jsonSchema = schemaFactory.getSchema(getSchemaString(), schemaValidatorsConfig);
+ this.jsonSchemaForCombine = schemaFactory.getSchema(getSchemaStringMultipleProperties(), schemaValidatorsConfig);
+ }
+
+ private String getSchemaString() {
+ return "{"
+ + "\"$schema\": \"https://github.com/networknt/json-schema-validator/tests/schemas/example01\","
+ + "\"title\" : \"Sample test schema\",\n"
+ + "\"description\" : \"Sample schema definition\","
+ + "\"type\" : \"object\","
+ + "\"properties\" :"
+ + "{"
+ + "\"test-property1\" : "
+ + "{"
+ + "\"title\": \"Test Property1\","
+ + "\"type\": \"string\", "
+ + "\"custom-keyword\":[\"x\",\"y\"]"
+ + "},"
+ + "\"test-property2\" : "
+ + "{"
+ + "\"title\": \"Test Property2\","
+ + "\"type\": \"string\", "
+ + "\"custom-keyword\":[\"x\",\"y\"]"
+ + "}"
+ + "},"
+ + "\"additionalProperties\":\"false\","
+ + "\"required\": [\"test-property1\"]\n"
+ + "}";
+ }
+
+ private String getSchemaStringMultipleProperties() {
+ return "{"
+ + "\"$schema\": \"https://github.com/networknt/json-schema-validator/tests/schemas/example01\","
+ + "\"title\" : \"Sample test schema\","
+ + "\"description\" : \"Sample schema definition\","
+ + "\"type\" : \"object\","
+ + "\"properties\" :"
+ + "{"
+ + "\"property1\" : "
+ + "{"
+ + "\"title\": \"Property1\","
+ + "\"type\": \"string\", "
+ + "\"custom-keyword1\":[\"x\",\"y\"],"
+ + "\"format\":\"sample-format\""
+ + "},"
+ + "\"property2\" : "
+ + "{"
+ + "\"title\": \"Property2\","
+ + "\"type\": \"string\", "
+ + "\"custom-keyword1\":[\"x\",\"y\"]"
+ + "},"
+ + "\"property3\" : "
+ + "{"
+ + "\"title\": \"Property3\","
+ + "\"type\": \"string\", "
+ + "\"custom-keyword1\":[\"x\",\"y\"]"
+ + "}"
+ + "}"
+ + "}";
+ }
+
+ private class ValidationThread implements Runnable {
+
+ private String data;
+
+ private String name;
+
+ private ValidationResult validationResult;
+
+ ValidationThread(String data, String name) {
+ this.name = name;
+ this.data = data;
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.validationResult = validate(data);
+ } catch (JsonMappingException e) {
+ e.printStackTrace();
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ ValidationResult getValidationResult() {
+ return this.validationResult;
+ }
+
+ @Override
+ public String toString() {
+ return "ValidationThread [data=" + data + ", name=" + name + ", validationResult=" + validationResult + "]";
+ }
+
+ }
+
+ /**
+ * Our own custom keyword. In this case we don't use this keyword. It is just
+ * for demonstration purpose.
+ */
+ private class CustomKeyword implements Keyword {
+ @Override
+ public String getValue() {
+ return "custom-keyword";
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
+ if (schemaNode != null && schemaNode.isArray()) {
+ return new CustomValidator(schemaLocation, evaluationPath, schemaNode);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * We will be collecting information/data by adding the data in the form of
+ * collectors into collector context object while we are validating this node.
+ * 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 extends AbstractJsonValidator {
+ public CustomValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode) {
+ super(schemaLocation, evaluationPath, new CustomKeyword(), schemaNode);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ // Get an instance of collector context.
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+ if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
+ collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector());
+ }
+ collectorContext.combineWithCollector(SAMPLE_COLLECTOR, node.textValue());
+ return new TreeSet<ValidationMessage>();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ // Ignore this method for testing.
+ return null;
+ }
+ }
+
+ private class CustomCollector extends AbstractCollector<List<String>> {
+
+ List<String> returnList = new ArrayList<String>();
+
+ private Map<String, String> referenceMap = null;
+
+ public CustomCollector() {
+ referenceMap = getDatasourceMap();
+ }
+
+ @Override
+ public List<String> collect() {
+ return returnList;
+ }
+
+ @Override
+ public void combine(Object object) {
+ returnList.add(referenceMap.get((String) object));
+ }
+
+ }
+
+ /**
+ * Our own custom keyword. In this case we don't use this keyword. It is just
+ * for demonstration purpose.
+ */
+ private class CustomKeyword1 implements Keyword {
+ @Override
+ public String getValue() {
+ return "custom-keyword1";
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
+ if (schemaNode != null && schemaNode.isArray()) {
+ return new CustomValidator1(schemaLocation, evaluationPath, schemaNode);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * We will be collecting information/data by adding the data in the form of
+ * collectors into collector context object while we are validating this node.
+ * 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. In this test case
+ * we expect this validator to be called multiple times as the associated
+ * keyword has been used multiple times in JSON Schema.
+ */
+ private class CustomValidator1 extends AbstractJsonValidator {
+ public CustomValidator1(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode) {
+ super(schemaLocation, evaluationPath,new CustomKeyword(), schemaNode);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ // Get an instance of collector context.
+ 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<String>());
+ }
+ List<String> returnList = (List<String>) collectorContext.get(SAMPLE_COLLECTOR_OTHER);
+ returnList.add(node.textValue());
+ return new TreeSet<ValidationMessage>();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ // Ignore this method for testing.
+ return null;
+ }
+ }
+
+ private ValidationResult validate(String jsonData) throws JsonMappingException, JsonProcessingException, Exception {
+ ObjectMapper objectMapper = new ObjectMapper();
+ return this.jsonSchema.validateAndCollect(objectMapper.readTree(jsonData));
+ }
+
+ private Map<String, String> getDatasourceMap() {
+ Map<String, String> map = new HashMap<String, String>();
+ 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;
+ }
+}
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 0000000..d5f4607
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ContentSchemaValidatorTest.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 org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.output.OutputUnit;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * ContentSchemaValidatorTest.
+ */
+public 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ String inputData = "\"helloworld\"";
+
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(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 0000000..64d7e29
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CustomMessageTest.java
@@ -0,0 +1,26 @@
+package com.networknt.schema;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+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")
+public class CustomMessageTest extends AbstractJsonSchemaTestSuite {
+
+ @TestFactory
+ @DisplayName("Draft 2019-09 - Custom Messages Enabled")
+ Stream<DynamicNode> draft201909__customMessagesEnabled() {
+ return createTests(VersionFlag.V201909, "src/test/resources/schema/customMessageTests/custom-message-tests.json");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 2019-09 - Custom Messages Disabled")
+ Stream<DynamicNode> draft201909__customMessagesDisabled() {
+ return createTests(VersionFlag.V201909, "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
new file mode 100644
index 0000000..d26ea8c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.core.JsonProcessingException;
+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.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CustomMetaSchemaTest {
+
+ /**
+ * Introduces the keyword "enumNames".
+ * <p>
+ * The keyword is used together with "enum" and must have the same length.
+ * <p>
+ * This keyword always produces a warning during validation -
+ * so it makes no sense in reality but should be useful for demonstration / testing purposes.
+ *
+ * @author klaskalass
+ */
+ public static class EnumNamesKeyword extends AbstractKeyword {
+
+ private static final class Validator extends AbstractJsonValidator {
+ private final List<String> enumValues;
+ private final List<String> enumNames;
+ private final String keyword;
+
+ private Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, String keyword,
+ List<String> enumValues, List<String> enumNames, JsonNode schemaNode) {
+ super(schemaLocation, evaluationPath, new EnumNamesKeyword(), schemaNode);
+ 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<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath 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);
+ Set<ValidationMessage> messages = new HashSet<>();
+ ValidationMessage validationMessage = ValidationMessage.builder().type(keyword)
+ .schemaNode(node)
+ .instanceNode(node)
+ .code("tests.example.enumNames").message("{0}: enumName is {1}").instanceLocation(instanceLocation)
+ .arguments(valueName).build();
+ messages.add(validationMessage);
+ return messages;
+ }
+ }
+
+
+ public EnumNamesKeyword() {
+ super("enumNames");
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, 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");
+ }
+ JsonNode parentSchemaNode = parentSchema.getSchemaNode();
+ if (!parentSchemaNode.has("enum")) {
+ throw new JsonSchemaException("Keyword enumNames needs to have a sibling enum keyword");
+ }
+ JsonNode enumSchemaNode = parentSchemaNode.get("enum");
+
+ return new Validator(schemaLocation, evaluationPath, getValue(), readStringList(enumSchemaNode),
+ readStringList(schemaNode), schemaNode);
+ }
+
+ private List<String> readStringList(JsonNode node) {
+ if (!node.isArray()) {
+ throw new JsonSchemaException("Keyword enum needs to receive an array");
+ }
+ ArrayList<String> result = new ArrayList<String>(node.size());
+ for (JsonNode child : node) {
+ result.add(child.asText());
+ }
+ return result;
+ }
+ }
+
+ @Test
+ public void customMetaSchemaWithIgnoredKeyword() throws JsonProcessingException, IOException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ final JsonMetaSchema metaSchema = JsonMetaSchema
+ .builder("https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV4())
+ // Generated UI uses enumNames to render Labels for enum values
+ .keyword(new EnumNamesKeyword())
+ .build();
+
+ final JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).metaSchema(metaSchema).build();
+ final JsonSchema 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<ValidationMessage> messages = schema.validate(objectMapper.readTree("\"foo\""));
+ assertEquals(1, messages.size());
+
+ ValidationMessage message = messages.iterator().next();
+ assertEquals("$: enumName is Foo !", message.getMessage());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/CustomUriTest.java b/src/test/java/com/networknt/schema/CustomUriTest.java
new file mode 100644
index 0000000..a39e158
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CustomUriTest.java
@@ -0,0 +1,46 @@
+package com.networknt.schema;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.resource.InputStreamSource;
+import com.networknt.schema.resource.SchemaLoader;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class CustomUriTest {
+ @Test
+ public void customUri() throws Exception {
+ /* Given */
+ final JsonSchemaFactory factory = buildJsonSchemaFactory();
+ final JsonSchema 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<ValidationMessage> errors = schema.validate(value);
+
+ /* Then */
+ assertThat(errors.isEmpty(), is(true));
+ }
+
+ private JsonSchemaFactory buildJsonSchemaFactory() {
+ return JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
+ .schemaLoaders(schemaLoaders -> schemaLoaders.add(new CustomUriFetcher())).build();
+ }
+
+ private static class CustomUriFetcher implements SchemaLoader {
+ private static final String SCHEMA = "{\"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\"$id\":\"custom:date\",\"type\":\"string\",\"format\":\"date\"}";
+
+ @Override
+ public InputStreamSource getSchema(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
new file mode 100644
index 0000000..8dd294d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CyclicDependencyTest.java
@@ -0,0 +1,41 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CyclicDependencyTest {
+
+ @Test
+ public void whenDependencyBetweenSchemaThenValidationSuccessful() throws Exception {
+
+ JsonSchemaFactory schemaFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4))
+ .build();
+ String jsonObject = "{\n" +
+ " \"element\": {\n" +
+ " \"id\": \"top\",\n" +
+ " \"extension\": [\n" +
+ " {\n" +
+ " \"url\": \"http://inner.test\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"extension\": [\n" +
+ " {\n" +
+ " \"url\": \"http://top.test\",\n" +
+ " \"valueElement\": {\n" +
+ " \"id\": \"inner\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ JsonSchema schema = schemaFactory.getSchema(SchemaLocation.of("resource:/draft4/issue258/Master.json"), config);
+ 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
new file mode 100644
index 0000000..035d1a3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DateTimeDSTTest.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.Set;
+
+public class DateTimeDSTTest {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(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;
+ }
+
+ @Test
+ public 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);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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 0000000..f8dc64d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DefaultJsonSchemaIdValidatorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Tests for the non-standard DefaultJsonSchemaIdValidator.
+ */
+public class DefaultJsonSchemaIdValidatorTest {
+ @Test
+ void givenRelativeIdShouldThrowInvalidSchemaException() {
+ String schema = "{\r\n" + " \"$id\": \"0\",\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setSchemaIdValidator(JsonSchemaIdValidator.DEFAULT);
+ assertThrowsExactly(InvalidSchemaException.class,
+ () -> JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config));
+ try {
+ JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config);
+ } catch (InvalidSchemaException e) {
+ assertEquals("/$id: '0' is not a valid $id", e.getMessage());
+ }
+ }
+
+ @Test
+ void givenFragmentWithNoContextShouldNotThrowInvalidSchemaException() {
+ String schema = "{\r\n" + " \"$id\": \"#0\",\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setSchemaIdValidator(JsonSchemaIdValidator.DEFAULT);
+ assertDoesNotThrow(() -> JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config));
+ }
+
+ @Test
+ void givenSlashWithNoContextShouldNotThrowInvalidSchemaException() {
+ String schema = "{\r\n" + " \"$id\": \"/base\",\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setSchemaIdValidator(JsonSchemaIdValidator.DEFAULT);
+ assertDoesNotThrow(() -> JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config));
+ }
+
+ @Test
+ void givenRelativeIdWithClasspathBaseShouldNotThrowInvalidSchemaException() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setSchemaIdValidator(JsonSchemaIdValidator.DEFAULT);
+ assertDoesNotThrow(() -> JsonSchemaFactory.getInstance(VersionFlag.V202012)
+ .getSchema(SchemaLocation.of("classpath:schema/id-relative.json"), config));
+ }
+}
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 0000000..bbc3a95
--- /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.Set;
+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 {
+
+ public 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 JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ private static final JsonSchema schema = factory.getSchema(SCHEMA);
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @Test
+ void shouldReturnNoErrorMessagesForObjectWithoutOptionalField() throws IOException {
+
+ Set<ValidationMessage> messages = whenValidate("{}");
+
+ assertThat(messages, empty());
+ }
+
+ @Test
+ void shouldReturnErrorMessageForObjectWithoutDependentRequiredField() throws IOException {
+
+ Set<ValidationMessage> messages = whenValidate("{ \"optional\": \"present\" }");
+
+ assertThat(
+ messages.stream().map(ValidationMessage::getMessage).collect(Collectors.toList()),
+ contains("$: has a missing property 'requiredWhenOptionalPresent' which is dependent required because 'optional' is present"));
+ }
+
+ @Test
+ void shouldReturnNoErrorMessagesForObjectWithOptionalAndDependentRequiredFieldSet() throws JsonProcessingException {
+
+ Set<ValidationMessage> messages =
+ whenValidate("{ \"optional\": \"present\", \"requiredWhenOptionalPresent\": \"present\" }");
+
+ assertThat(messages, empty());
+ }
+
+ private static Set<ValidationMessage> 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/DisallowUnknownJsonMetaSchemaFactoryTest.java b/src/test/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactoryTest.java
new file mode 100644
index 0000000..8560c2b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DisallowUnknownJsonMetaSchemaFactoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Tests for DisallowUnknownJsonMetaSchemaFactory.
+ */
+class DisallowUnknownJsonMetaSchemaFactoryTest {
+ private static final String DRAFT_202012_SCHEMA = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"type\": \"object\"\r\n"
+ + "}";
+
+ private static final String DRAFT_7_SCHEMA = "{\r\n"
+ + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n"
+ + " \"type\": \"object\"\r\n"
+ + "}";
+ @Test
+ void defaultHandling() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ assertDoesNotThrow(() -> factory.getSchema(DRAFT_202012_SCHEMA));
+ assertDoesNotThrow(() -> factory.getSchema(DRAFT_7_SCHEMA));
+ }
+
+ @Test
+ void draft202012() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance()));
+ assertDoesNotThrow(() -> factory.getSchema(DRAFT_202012_SCHEMA));
+ assertThrows(InvalidSchemaException.class, () -> factory.getSchema(DRAFT_7_SCHEMA));
+ }
+
+ @Test
+ void draft7() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V7,
+ builder -> builder.metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance()));
+ assertDoesNotThrow(() -> factory.getSchema(DRAFT_7_SCHEMA));
+ assertThrows(InvalidSchemaException.class, () -> factory.getSchema(DRAFT_202012_SCHEMA));
+ }
+}
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 0000000..f2b397a
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DisallowUnknownKeywordFactoryTest.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.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+class DisallowUnknownKeywordFactoryTest {
+ @Test
+ void shouldThrowForUnknownKeywords() {
+ DisallowUnknownKeywordFactory factory = DisallowUnknownKeywordFactory.getInstance();
+ assertThrows(InvalidSchemaException.class, () -> factory.getKeyword("helloworld", null));
+ }
+
+ @Test
+ void getSchemaShouldThrowForUnknownKeywords() {
+ JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012())
+ .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()).build();
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.metaSchema(metaSchema));
+ 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 0000000..eb2f92f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java
@@ -0,0 +1,790 @@
+/*
+ * 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.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test for discriminator.
+ */
+public 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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"
+ + " }";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ }
+
+ /**
+ * 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", list.get(0).getType());
+ assertEquals("required", list.get(1).getType());
+ 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", list.get(0).getType());
+ assertEquals("required", list.get(1).getType());
+ 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> 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<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", list.get(0).getType());
+ assertEquals("required", list.get(1).getType());
+ 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 = "{}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size());
+ List<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", list.get(0).getType());
+ assertEquals("required", list.get(1).getType());
+ assertEquals("required", list.get(2).getType());
+ }
+
+ /**
+ * 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"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ List<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("required", list.get(0).getType());
+ }
+}
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 0000000..bcf80d3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/DurationFormatValidatorTest.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;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DurationFormatValidatorTest {
+
+ @Test
+ public void durationFormatValidatorTest() throws JsonProcessingException, 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 JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).build();
+ final JsonSchema validatorSchema = validatorFactory.getSchema(schema);
+
+ Set<ValidationMessage> messages = validatorSchema.validate(validTargetNode);
+ assertEquals(0, messages.size());
+
+ messages = validatorSchema.validate(invalidTargetNode, OutputFormat.DEFAULT, (executionContext, validationContext) -> {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertEquals(1, messages.size());
+
+ }
+}
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 0000000..ca3650d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ExampleTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public class ExampleTest {
+ @Test
+ public void exampleSchemaLocation() throws Exception {
+ // This creates a schema factory that will use Draft 2012-12 as the default if $schema is not specified in the initial schema
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder ->
+ builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.example.org/", "classpath:schema/"))
+ );
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.example.org/example-main.json"), config);
+ 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(SchemaId.V7, schema.getValidationContext().getMetaSchema().getIri());
+ Set<ValidationMessage> assertions = schema.validate(input, InputFormat.JSON);
+ assertEquals(1, assertions.size());
+
+ // The example-ref.json schema defines $schema with Draft 2019-09
+ JsonSchema refSchema = schema.getValidationContext().getSchemaResources().get("https://www.example.org/example-ref.json#");
+ assertEquals(SchemaId.V201909, refSchema.getValidationContext().getMetaSchema().getIri());
+ }
+
+ @Test
+ public void exampleClasspath() throws Exception {
+ // This creates a schema factory that will use Draft 2012-12 as the default if $schema is not specified in the initial schema
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of("classpath:schema/example-main.json"), config);
+ 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(SchemaId.V7, schema.getValidationContext().getMetaSchema().getIri());
+ Set<ValidationMessage> assertions = schema.validate(input, InputFormat.JSON);
+ assertEquals(1, assertions.size());
+
+ // The example-ref.json schema defines $schema with Draft 2019-09
+ JsonSchema refSchema = schema.getValidationContext().getSchemaResources().get("classpath:schema/example-ref.json#");
+ assertEquals(SchemaId.V201909, refSchema.getValidationContext().getMetaSchema().getIri());
+ }
+}
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 0000000..fb645c4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.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;
+
+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.SpecVersion.VersionFlag;
+
+public class FormatKeywordFactoryTest {
+
+ public static class CustomFormatKeyword extends FormatKeyword {
+ public CustomFormatKeyword(Map<String, Format> formats) {
+ super(formats);
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Test
+ void shouldUseFormatKeyword() {
+ JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012())
+ .formatKeywordFactory(CustomFormatKeyword::new).build();
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.metaSchema(metaSchema));
+ String schemaData = "{\r\n"
+ + " \"format\": \"hello\"\r\n"
+ + "}";
+ assertThrows(JsonSchemaException.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 0000000..2d0ec9b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/FormatValidatorTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.Set;
+
+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.SpecVersion.VersionFlag;
+import com.networknt.schema.format.PatternFormat;
+import com.networknt.schema.output.OutputUnit;
+
+/**
+ * Test for format validator.
+ */
+public class FormatValidatorTest {
+ @Test
+ void unknownFormatNoVocab() {
+ String schemaData = "{\r\n"
+ + " \"format\":\"unknown\"\r\n"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate("\"hello\"", InputFormat.JSON, executionContext -> {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertEquals(0, messages.size());
+ }
+
+ @Test
+ void unknownFormatNoVocabStrictTrue() {
+ String schemaData = "{\r\n"
+ + " \"format\":\"unknown\"\r\n"
+ + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setStrict("format", true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"hello\"", InputFormat.JSON, executionContext -> {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(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"
+ + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ JsonSchema schema = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder
+ .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections.singletonMap("https://www.example.com/format-assertion/schema", metaSchemaData))))
+ .getSchema(schemaData, config);
+ Set<ValidationMessage> 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ OutputUnit outputUnit = schema.validate("\"hello\"", InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionContext.getExecutionConfig().setAnnotationCollectionFilter(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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(formatSchema, config);
+ Set<ValidationMessage> messages = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertFalse(messages.isEmpty());
+ }
+
+ /**
+ * This tests that the changes to use message key doesn't cause a regression to
+ * the existing message.
+ */
+ @SuppressWarnings("deprecation")
+ @Test
+ void patternFormatDeprecated() {
+ JsonMetaSchema customMetaSchema = JsonMetaSchema
+ .builder("https://www.example.com/schema", JsonMetaSchema.getV7())
+ .formats(formats -> {
+ PatternFormat format = new PatternFormat("custom", "test", "must be test");
+ formats.put(format.getName(), format);
+ })
+ .build();
+
+ JsonSchemaFactory factory = new JsonSchemaFactory.Builder().defaultMetaSchemaIri(customMetaSchema.getIri())
+ .metaSchema(customMetaSchema).build();
+ String formatSchema = "{\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"format\": \"custom\"\r\n"
+ + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(formatSchema, config);
+ Set<ValidationMessage> messages = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertFalse(messages.isEmpty());
+ assertEquals(": does not match the custom pattern must be test", messages.iterator().next().getMessage());
+ }
+
+ public static class CustomNumberFormat implements Format {
+ private final BigDecimal compare;
+
+ public CustomNumberFormat(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 "custom-number";
+ }
+ }
+
+ @Test
+ void shouldAllowNumberFormat() {
+ JsonMetaSchema customMetaSchema = JsonMetaSchema
+ .builder("https://www.example.com/schema", JsonMetaSchema.getV7())
+ .formats(formats -> {
+ CustomNumberFormat format = new CustomNumberFormat(new BigDecimal("12345"));
+ formats.put(format.getName(), format);
+ })
+ .build();
+
+ JsonSchemaFactory factory = new JsonSchemaFactory.Builder().defaultMetaSchemaIri(customMetaSchema.getIri())
+ .metaSchema(customMetaSchema).build();
+ String formatSchema = "{\r\n"
+ + " \"type\": \"number\",\r\n"
+ + " \"format\": \"custom-number\"\r\n"
+ + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(formatSchema, config);
+ Set<ValidationMessage> messages = schema.validate("123451", InputFormat.JSON, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertFalse(messages.isEmpty());
+ assertEquals(": does not match the custom-number pattern ", messages.iterator().next().getMessage());
+ messages = schema.validate("12345", InputFormat.JSON, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertTrue(messages.isEmpty());
+
+ }
+}
diff --git a/src/test/java/com/networknt/schema/HTTPServiceSupport.java b/src/test/java/com/networknt/schema/HTTPServiceSupport.java
new file mode 100644
index 0000000..bf1e826
--- /dev/null
+++ b/src/test/java/com/networknt/schema/HTTPServiceSupport.java
@@ -0,0 +1,87 @@
+/*
+ * 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 io.undertow.Undertow;
+import io.undertow.server.handlers.PathHandler;
+import io.undertow.server.handlers.resource.FileResourceManager;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import java.io.File;
+
+import static io.undertow.Handlers.*;
+
+public abstract class HTTPServiceSupport {
+
+ protected static Undertow server = null;
+
+ @BeforeAll
+ public static void setUp() {
+ if (server == null) {
+ PathHandler pathHandler = path(resource(
+ new FileResourceManager(
+ new File("./src/test/suite/remotes"),
+ 100
+ ))
+ );
+
+ pathHandler.addPrefixPath("folder", resource(
+ new FileResourceManager(
+ new File("./src/test/resources/remotes/folder"),
+ 100
+ ))
+ );
+
+ pathHandler.addPrefixPath("id_schema", resource(
+ new FileResourceManager(
+ new File("./src/test/resources/remotes/id_schema"),
+ 100
+ ))
+ );
+
+ pathHandler.addPrefixPath("self_ref", resource(
+ new FileResourceManager(
+ new File("./src/test/resources/remotes/self_ref"),
+ 100
+ ))
+ );
+
+ server = Undertow.builder()
+ .addHttpListener(1234, "localhost")
+ .setHandler(pathHandler)
+ .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 cleanup() {
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue255Test.java b/src/test/java/com/networknt/schema/Issue255Test.java
new file mode 100644
index 0000000..e79b403
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue255Test.java
@@ -0,0 +1,49 @@
+/*
+ * 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.Test;
+
+import java.io.InputStream;
+import java.util.Set;
+
+public class Issue255Test {
+ 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;
+ }
+
+ @Test
+ public void shouldFailWhenRequiredPropertiesDoNotExistInReferencedSubSchema() throws Exception {
+ String schemaPath = "/draft2019-09/issue255.json";
+ String dataPath = "/data/issue255.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..ce77eee
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue285Test.java
@@ -0,0 +1,132 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Set;
+
+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))
+ .jsonMapper(mapper)
+ .schemaMappers(schemaMappers -> schemaMappers
+ .mapPrefix("http://json-schema.org", "resource:")
+ .mapPrefix("https://json-schema.org", "resource:"))
+ .build();
+
+
+ String schemaStr = "{\n" +
+ " \"$id\": \"https://example.com/person.schema.json\",\n" +
+ " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" +
+ " \"title\": \"Person\",\n" +
+ " \"type\": \"object\",\n" +
+ " \"additionalProperties\": false,\n" +
+ " \"properties\": {\n" +
+ " \"name\": {\n" +
+ " \"type\": \"object\",\n" +
+ " \"additionalProperties\": false,\n" +
+ " \"properties\": {\n" +
+ " \"firstName\": {\n" +
+ " \"type\": \"string\",\n" +
+ " \"minLength\": 3,\n" +
+ " \"description\": \"The person's first name.\"\n" +
+ " },\n" +
+ " \"lastName\": {\n" +
+ " \"type\": \"string\",\n" +
+ " \"description\": \"The person's last name.\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ String person = "{\n" +
+ " \"name\": {\n" +
+ " \"firstName\": \"John\",\n" +
+ " \"lastName\": true\n" +
+ " }\n" +
+ "}\n";
+
+ // 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.
+ @Test
+ public void nestedValidation() throws IOException {
+ JsonSchema jsonSchema = schemaFactory.getSchema(schemaStr);
+ Set<ValidationMessage> validationMessages = jsonSchema.validate(mapper.readTree(person));
+
+ System.err.println("\n" + Arrays.toString(validationMessages.toArray()));
+
+ assertFalse(validationMessages.isEmpty());
+
+
+ }
+
+ String invalidNestedSchema = "{\n" +
+ " \"$id\": \"https://example.com/person.schema.json\",\n" +
+ " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" +
+ " \"title\": \"Person\",\n" +
+ " \"type\": \"object\",\n" +
+ " \"additionalProperties\": false,\n" +
+ " \"properties\": {\n" +
+ " \"name\": {\n" +
+ " \"type\": \"foo\",\n" +
+ " \"additionalProperties\": false,\n" +
+ " \"properties\": {\n" +
+ " \"firstName\": {\n" +
+ " \"type\": \"string\",\n" +
+ " \"minLength\": 3,\n" +
+ " \"description\": \"The person's first name.\"\n" +
+ " },\n" +
+ " \"lastName\": {\n" +
+ " \"type\": \"foo\",\n" +
+ " \"description\": \"The person's last name.\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ // 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.
+ @Test
+ public void nestedTypeValidation() throws IOException, URISyntaxException {
+ SchemaLocation uri = SchemaLocation.of("https://json-schema.org/draft/2019-09/schema");
+ JsonSchema jsonSchema = schemaFactory.getSchema(uri);
+ Set<ValidationMessage> validationMessages = jsonSchema.validate(mapper.readTree(invalidNestedSchema));
+
+ System.err.println("\n" + Arrays.toString(validationMessages.toArray()));
+
+ assertFalse(validationMessages.isEmpty());
+ }
+
+ String invalidSchema = "{\n" +
+ " \"$id\": \"https://example.com/person.schema.json\",\n" +
+ " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" +
+ " \"title\": \"Person\",\n" +
+ " \"type\": \"foo\",\n" +
+ " \"additionalProperties\": false\n" +
+ "}";
+
+ // 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]'.
+ @Test
+ public void typeValidation() throws IOException, URISyntaxException {
+ SchemaLocation uri = SchemaLocation.of("https://json-schema.org/draft/2019-09/schema");
+ JsonSchema jsonSchema = schemaFactory.getSchema(uri);
+ Set<ValidationMessage> validationMessages = jsonSchema.validate(mapper.readTree(invalidSchema));
+
+ System.err.println("\n" + Arrays.toString(validationMessages.toArray()));
+
+ assertFalse(validationMessages.isEmpty());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue295Test.java b/src/test/java/com/networknt/schema/Issue295Test.java
new file mode 100644
index 0000000..f596277
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue295Test.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.Set;
+
+public class Issue295Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(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;
+ }
+
+ @Test
+ public void shouldWorkV7() throws Exception {
+ String schemaPath = "/schema/issue295-v7.json";
+ String dataPath = "/data/issue295.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..867e85e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue313Test.java
@@ -0,0 +1,54 @@
+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.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.util.Set;
+
+public class Issue313Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonSchema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readTree(content);
+ return node;
+ }
+
+ @Test
+ @Disabled
+ public 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);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(2, errors.size());
+ }
+
+ @Test
+ public void shouldFailV7() throws Exception {
+ String schemaPath = "/schema/issue313-v7.json";
+ String dataPath = "/data/issue313.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..66e688e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue314Test.java
@@ -0,0 +1,25 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+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))
+ .metaSchema(
+ JsonMetaSchema.builder(
+ "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#",
+ JsonMetaSchema.getV7())
+ .build())
+ .build();
+
+ @Test
+ public void testNormalizeHttpOnly() {
+ String schemaPath = "/schema/issue314-v7.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = FACTORY.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
new file mode 100644
index 0000000..e2fe17f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue327Test.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.Set;
+
+public class Issue327Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(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;
+ }
+
+ @Test
+ public void shouldWorkV7() throws Exception {
+ String schemaPath = "/schema/issue327-v7.json";
+ String dataPath = "/data/issue327.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..b6f4419
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue342Test.java
@@ -0,0 +1,38 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readTree(content);
+ return node;
+ }
+
+ @Test
+ public void propertyNameEnumShouldFailV7() throws Exception {
+ String schemaPath = "/schema/issue342-v7.json";
+ String dataPath = "/data/issue342.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(1, errors.size());
+ final ValidationMessage 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.getMessage());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue347Test.java b/src/test/java/com/networknt/schema/Issue347Test.java
new file mode 100644
index 0000000..20e0991
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue347Test.java
@@ -0,0 +1,23 @@
+package com.networknt.schema;
+
+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 {
+
+ @Test
+ public void failure() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ assertThrows(JsonSchemaException.class, () -> factory.getSchema(Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/issue347-v7.json")));
+ try {
+ factory.getSchema(Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/issue347-v7.json"));
+ } catch (Throwable e) {
+ assertThat(e, instanceOf(JsonSchemaException.class));
+ assertEquals("/$id: 'test' is not a valid $id", e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue366FailFastTest.java b/src/test/java/com/networknt/schema/Issue366FailFastTest.java
new file mode 100644
index 0000000..55c94ab
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue366FailFastTest.java
@@ -0,0 +1,100 @@
+package com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+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.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)).jsonMapper(objectMapper).build();
+
+ schemaValidatorsConfig.setTypeLoose(false);
+
+ SchemaLocation 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(0);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(1);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(2);
+ JsonNode dataNode = testNode.get("data");
+ assertEquals(1, jsonSchema.validate(dataNode).size());
+ }
+
+ @Test
+ public void neitherValid() throws Exception {
+ String dataPath = "/data/issue366.json";
+
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ List<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(3);
+ JsonNode dataNode = testNode.get("data");
+ assertEquals(1, jsonSchema.validate(dataNode).size());
+ }
+
+ 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
new file mode 100644
index 0000000..7802d4f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue366FailSlowTest.java
@@ -0,0 +1,104 @@
+package com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.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))
+ .jsonMapper(objectMapper)
+ .build();
+
+ schemaValidatorsConfig.setTypeLoose(false);
+
+ SchemaLocation 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(0);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(1);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(2);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> 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<JsonNode> testNodes = node.findValues("tests");
+ JsonNode testNode = testNodes.get(0).get(3);
+ JsonNode dataNode = testNode.get("data");
+ Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
+ assertTrue(!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
new file mode 100644
index 0000000..5135339
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue375Test.java
@@ -0,0 +1,61 @@
+/*
+ * 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.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);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void shouldFailAndShowValidationValuesWithError() throws Exception {
+ String schemaPath = "/draft2019-09/issue375.json";
+ String dataPath = "/data/issue375.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ List<String> errorMessages = new ArrayList<String>();
+ for (ValidationMessage error: errors) {
+ errorMessages.add(error.getMessage());
+ }
+
+ List<String> expectedMessages = Arrays.asList(
+ "$.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
new file mode 100644
index 0000000..fc4dd87
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue383Test.java
@@ -0,0 +1,35 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readTree(content);
+ return node;
+ }
+
+ @Test
+ public void nestedOneOfsShouldStillMatchV7() throws Exception {
+ String schemaPath = "/schema/issue383-v7.json";
+ String dataPath = "/data/issue383.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(0, errors.size());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue396Test.java b/src/test/java/com/networknt/schema/Issue396Test.java
new file mode 100644
index 0000000..81a4c08
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue396Test.java
@@ -0,0 +1,45 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readTree(content);
+ return node;
+ }
+
+ @Test
+ public void testComplexPropertyNamesV7() throws Exception {
+ String schemaPath = "/schema/issue396-v7.json";
+ String dataPath = "/data/issue396.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+
+ final Set<String> expected = new HashSet<>();
+ node.fields().forEachRemaining(entry -> {
+ if (!entry.getValue().asBoolean())
+ expected.add(entry.getKey());
+ });
+
+ Set<ValidationMessage> errors = schema.validate(node);
+ final Set<String> actual = errors.stream().map(ValidationMessage::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
new file mode 100644
index 0000000..07b3068
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue404Test.java
@@ -0,0 +1,35 @@
+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.Set;
+
+public class Issue404Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(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;
+ }
+
+ @Test
+ public void expectObjectNotIntegerV7() throws Exception {
+ String schemaPath = "/schema/issue404-v7.json";
+ String dataPath = "/data/issue404.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(0, errors.size());
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/Issue406Test.java b/src/test/java/com/networknt/schema/Issue406Test.java
new file mode 100644
index 0000000..b4f0533
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue406Test.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, 2021 Oracle and/or its affiliates.
+ */
+package com.networknt.schema;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+public 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);
+ // 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,
+ new Executable() {
+ @Override
+ public void execute() {
+ schema.initializeValidators();
+ }
+ },
+ "#/$ref: Reference urn:unresolved cannot be resolved");
+ }
+
+ @Test
+ public void testPreloadingHappeningForCircularDependency() {
+ final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ final JsonSchema schema = factory.getSchema(CIRCULAR_$REF_SCHEMA);
+ schema.initializeValidators();
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue426Test.java b/src/test/java/com/networknt/schema/Issue426Test.java
new file mode 100644
index 0000000..07c76c3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue426Test.java
@@ -0,0 +1,42 @@
+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.Set;
+
+/**
+ * Validating custom message
+ */
+public class Issue426Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void shouldWorkV7() throws Exception {
+ String schemaPath = "/schema/issue426-v7.json";
+ String dataPath = "/data/issue426.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(2, errors.size());
+ final JsonNode message = schema.schemaNode.get("message");
+ for(ValidationMessage error : errors) {
+ //validating custom message
+ Assertions.assertEquals(message.get(error.getType()).asText(), error.getMessage());
+ }
+ }
+}
+
diff --git a/src/test/java/com/networknt/schema/Issue428Test.java b/src/test/java/com/networknt/schema/Issue428Test.java
new file mode 100644
index 0000000..c2d3318
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue428Test.java
@@ -0,0 +1,90 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class Issue428Test extends HTTPServiceSupport {
+ protected ObjectMapper mapper = new ObjectMapper();
+ protected JsonSchemaFactory validatorFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).jsonMapper(mapper).build();
+
+ private void runTestFile(String testCaseFile) throws Exception {
+ final SchemaLocation testCaseFileUri = SchemaLocation.of("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<ValidationMessage> errors = new ArrayList<ValidationMessage>(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);
+ }
+ }
+ //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/issue428.json");
+ }
+}
diff --git a/src/test/java/com/networknt/schema/Issue451Test.java b/src/test/java/com/networknt/schema/Issue451Test.java
new file mode 100644
index 0000000..53a829e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue451Test.java
@@ -0,0 +1,95 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+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.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Validating anyOf walker
+ */
+public class Issue451Test {
+
+ private static final String COLLECTOR_ID = "collector-451";
+
+ 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);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void shouldWalkAnyOfProperties() {
+ walk(null, false);
+ }
+
+ @Test
+ public void shouldWalkAnyOfPropertiesWithWithPayloadAndValidation() throws Exception {
+ JsonNode data = getJsonNodeFromStreamContent(Issue451Test.class.getResourceAsStream(
+ "/data/issue451.json"));
+ walk(data,true);
+ }
+
+ @Test
+ public void shouldWalkAnyOfPropertiesWithWithPayload() throws Exception {
+ JsonNode data = getJsonNodeFromStreamContent(Issue451Test.class.getResourceAsStream(
+ "/data/issue451.json"));
+ walk(data, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void walk(JsonNode data, boolean shouldValidate) {
+ String schemaPath = "/schema/issue451-v7.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+
+ CollectorContext collectorContext = schema.walk(data, shouldValidate).getCollectorContext();
+
+ Map<String, Integer> collector = (Map<String, Integer>) 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 {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ 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<ValidationMessage> validationMessages) {
+
+ }
+
+ private Map<String, Integer> collector(ExecutionContext executionContext) {
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> collector = (Map<String, Integer>) executionContext.getCollectorContext().get(COLLECTOR_ID);
+ if(collector == null) {
+ collector = new HashMap<>();
+ executionContext.getCollectorContext().add(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
new file mode 100644
index 0000000..723637f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue456Test.java
@@ -0,0 +1,48 @@
+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.Set;
+
+public class Issue456Test {
+
+ protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ return factory.getSchema(schemaContent);
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void shouldWorkT2() throws Exception {
+ String schemaPath = "/schema/issue456-v7.json";
+ String dataPath = "/data/issue456-T2.json";
+// String dataT3Path = "/data/issue456-T3.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(0, errors.size());
+ }
+
+ @Test
+ public 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);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..239f617
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue461Test.java
@@ -0,0 +1,47 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+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.URISyntaxException;
+import java.util.Set;
+
+public class Issue461Test {
+ protected ObjectMapper mapper = new ObjectMapper();
+
+ protected JsonSchema getJsonSchemaFromStreamContentV7(SchemaLocation schemaUri) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ SchemaValidatorsConfig svc = new SchemaValidatorsConfig();
+ svc.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new Walker());
+ return factory.getSchema(schemaUri, svc);
+ }
+
+ @Test
+ public void shouldWalkWithValidation() throws URISyntaxException, IOException {
+ JsonSchema 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());
+ }
+
+ /**
+ * Example NOP walker
+ */
+ private static class Walker implements JsonSchemaWalkListener {
+ @Override
+ public WalkFlow onWalkStart(final WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(final WalkEvent walkEvent,
+ final Set<ValidationMessage> validationMessages) {
+ }
+ }
+}
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 0000000..ad8bb14
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue467Test.java
@@ -0,0 +1,92 @@
+/*
+ * 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.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.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
+
+public class Issue467Test {
+ private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ private static String schemaPath = "/schema/issue467.json";
+
+ protected ObjectMapper mapper = new ObjectMapper();
+
+ @Test
+ public void shouldWalkKeywordWithValidation() throws URISyntaxException, IOException {
+ InputStream schemaInputStream = Issue467Test.class.getResourceAsStream(schemaPath);
+ final Set<JsonNodePath> properties = new LinkedHashSet<>();
+ final SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ properties.add(walkEvent.getSchema().getEvaluationPath().append(walkEvent.getKeyword()));
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> set) {
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaInputStream, config);
+ JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json"));
+ ValidationResult result = schema.walk(data, true);
+ assertEquals(new HashSet<>(Arrays.asList("$.properties", "$.properties.tags.items[0].properties")),
+ properties.stream().map(Object::toString).collect(Collectors.toSet()));
+ assertEquals(1, result.getValidationMessages().size());
+ }
+
+ @Test
+ public void shouldWalkPropertiesWithValidation() throws URISyntaxException, IOException {
+ InputStream schemaInputStream = Issue467Test.class.getResourceAsStream(schemaPath);
+ final Set<JsonNodePath> properties = new LinkedHashSet<>();
+ final SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ properties.add(walkEvent.getSchema().getEvaluationPath());
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> set) {
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaInputStream, config);
+ JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json"));
+ ValidationResult result = schema.walk(data, true);
+ 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.getValidationMessages().size());
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/Issue471Test.java b/src/test/java/com/networknt/schema/Issue471Test.java
new file mode 100644
index 0000000..c74108d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue471Test.java
@@ -0,0 +1,90 @@
+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.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+class Issue471Test {
+ private final String DATA_PATH = "/data/issue471.json";
+ private final String SCHEMA_PATH = "/schema/issue471-2019-09.json";
+
+ // Only one test method is allowed at a time as the ResourceBundle is statically initialized
+
+ @Test
+ @Disabled
+ void shouldFailV201909_with_enUS() throws Exception {
+ Locale.setDefault(Locale.US);
+ Map<String, String> errorsMap = validate();
+ Assertions.assertEquals("$.title: may only be 10 characters long", errorsMap.get("$.title"));
+ Assertions.assertEquals("$.pictures: there must be a maximum of 2 items in the array", errorsMap.get("$.pictures"));
+ }
+
+ @Test
+ @Disabled
+ void shouldFailV201909_with_zhCN() throws Exception {
+ Locale.setDefault(Locale.CHINA);
+ Map<String, String> errorsMap = validate();
+ Assertions.assertEquals("$.title:å¯èƒ½åªæœ‰ 10 个字符长", errorsMap.get("$.title"));
+ Assertions.assertEquals("$.pictures:数组中最多必须有 2 个项目", errorsMap.get("$.pictures"));
+ }
+
+ @Test
+ @Disabled
+ void shouldFailV201909_with_deDE() throws Exception {
+ Locale.setDefault(Locale.GERMANY);
+ Map<String, String> 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 shouldFailV201909_with_frFR() throws Exception {
+ Locale.setDefault(Locale.FRANCE);
+ Map<String, String> 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 shouldFailV201909_with_frIT() throws Exception {
+ Locale.setDefault(Locale.ITALIAN);
+ Map<String, String> 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<String, String> validate() throws Exception {
+ InputStream schemaInputStream = Issue471Test.class.getResourceAsStream(SCHEMA_PATH);
+ JsonSchema schema = getJsonSchemaFromStreamContentV201909(schemaInputStream);
+ InputStream dataInputStream = Issue471Test.class.getResourceAsStream(DATA_PATH);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+
+ Set<ValidationMessage> validationMessages = schema.validate(node);
+ return convertValidationMessagesToMap(validationMessages);
+ }
+
+ private Map<String, String> convertValidationMessagesToMap(Set<ValidationMessage> validationMessages) {
+ return validationMessages.stream().collect(Collectors.toMap(m -> m.getInstanceLocation().toString(), ValidationMessage::getMessage));
+ }
+
+ private JsonSchema getJsonSchemaFromStreamContentV201909(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ return factory.getSchema(schemaContent);
+ }
+
+ private JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+}
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 0000000..e9944cb
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue475Test.java
@@ -0,0 +1,125 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * Tests for validation of schema against meta schema.
+ */
+public 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
+ public void draft4() throws Exception {
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V4, builder -> builder
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("http://json-schema.org", "classpath:")));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V4), config);
+
+ Set<ValidationMessage> 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
+ public void draft6() throws Exception {
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V6, builder -> builder
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("http://json-schema.org", "classpath:")));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V6), config);
+
+ Set<ValidationMessage> 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
+ public void draft7() throws Exception {
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V7, builder -> builder
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("http://json-schema.org", "classpath:")));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V7), config);
+
+ Set<ValidationMessage> 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
+ public void draft201909() throws Exception {
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V201909, builder -> builder
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://json-schema.org", "classpath:")));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V201909), config);
+
+ Set<ValidationMessage> 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
+ public void draft202012() throws Exception {
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://json-schema.org", "classpath:")));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V202012), config);
+
+ Set<ValidationMessage> 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/Issue493Test.java b/src/test/java/com/networknt/schema/Issue493Test.java
new file mode 100644
index 0000000..c6e2f50
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue493Test.java
@@ -0,0 +1,96 @@
+package com.networknt.schema;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+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;
+
+class Issue493Test
+{
+
+ private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ private static String schemaPath1 = "/schema/issue493.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 with required item only")
+ void testValidJson1 ()
+ throws Exception
+ {
+ InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1);
+ JsonSchema schema = factory.getSchema(schemaInputStream);
+ JsonNode node = getJsonNodeFromJsonData("/data/issue493-valid-1.json");
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertTrue(errors.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Test valid with optional item")
+ void testValidJson2 ()
+ throws Exception
+ {
+ InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1);
+ JsonSchema schema = factory.getSchema(schemaInputStream);
+ JsonNode node = getJsonNodeFromJsonData("/data/issue493-valid-2.json");
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertTrue(errors.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Test invalid with required item but wrong type")
+ void testInvalidJson1 ()
+ throws Exception
+ {
+ InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1);
+ JsonSchema schema = factory.getSchema(schemaInputStream);
+ JsonNode node = getJsonNodeFromJsonData("/data/issue493-invalid-1.json");
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(2, errors.size());
+
+ Set<String> allErrorMessages = new HashSet<>();
+ errors.forEach(vm -> {
+ allErrorMessages.add(vm.getMessage());
+ });
+ assertThat(allErrorMessages,
+ Matchers.containsInAnyOrder("$.parameters[0].value: string found, integer expected",
+ "$.parameters[0].value: does not match the regex pattern ^\\{\\{.+\\}\\}$"));
+ }
+
+ @Test
+ @DisplayName("Test invalid with optional item but wrong type")
+ void testInvalidJson2 ()
+ throws Exception
+ {
+ InputStream schemaInputStream = Issue493Test.class.getResourceAsStream(schemaPath1);
+ JsonSchema schema = factory.getSchema(schemaInputStream);
+ JsonNode node = getJsonNodeFromJsonData("/data/issue493-invalid-2.json");
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(3, errors.size());
+
+ Set<String> allErrorMessages = new HashSet<>();
+ errors.forEach(vm -> {
+ allErrorMessages.add(vm.getMessage());
+ });
+ 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
new file mode 100644
index 0000000..2e2da1c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue510Test.java
@@ -0,0 +1,13 @@
+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)).jsonMapper(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
new file mode 100644
index 0000000..984d7bb
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue518Test.java
@@ -0,0 +1,28 @@
+package com.networknt.schema;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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())
+ .build();
+
+ private static final JsonSchemaFactory FACTORY =
+ JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
+ .metaSchema(igluMetaSchema)
+ .build();
+
+ @Test
+ public void testPreservingEmptyFragmentSuffix() {
+ String schemaPath = "/schema/issue518-v7.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = FACTORY.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
new file mode 100644
index 0000000..bb380cd
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue532Test.java
@@ -0,0 +1,16 @@
+package com.networknt.schema;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class Issue532Test {
+ @Test
+ public void failure() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ JsonSchemaException ex = assertThrows(JsonSchemaException.class, () -> {
+ factory.getSchema("{ \"$schema\": true }");
+ });
+ assertEquals("Unknown MetaSchema: 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 0000000..741b274
--- /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.Set;
+
+
+public class Issue550Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(String schemaPath) {
+ InputStream schemaContent = getClass().getResourceAsStream(schemaPath);
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ 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";
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataPath);
+
+ Set<ValidationMessage> errors = schema.validate(node);
+ ValidationMessage validationMessage = errors.stream().findFirst().get();
+
+ Assertions.assertEquals("https://example.com/person.schema.json#/properties/age/minimum", validationMessage.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";
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataPath);
+
+ Set<ValidationMessage> errors = schema.validate(node);
+ ValidationMessage validationMessage = 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", validationMessage.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 0000000..dd22d37
--- /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.Set;
+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.
+ */
+public class Issue575Test {
+ private static JsonSchema schema;
+
+ @BeforeAll
+ static void init() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ String schemaPath = "/schema/issue575-2019-09.json";
+ InputStream schemaInputStream = Issue575Test.class.getResourceAsStream(schemaPath);
+ schema = factory.getSchema(schemaInputStream);
+ }
+
+ public static Stream<Arguments> 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 {
+ Set<ValidationMessage> errors = schema.validate(new ObjectMapper().readTree(jsonObject));
+ Assertions.assertTrue(errors.isEmpty());
+ }
+
+ public static Stream<Arguments> 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 {
+ Set<ValidationMessage> errors = schema.validate(new ObjectMapper().readTree(jsonObject), OutputFormat.DEFAULT, (executionContext, validationContext) -> {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(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 0000000..40e68d8
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue604Test.java
@@ -0,0 +1,21 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+public class Issue604Test {
+ @Test
+ public void failure() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, false, false));
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ JsonSchema schema = factory.getSchema("{ \"type\": \"object\", \"properties\": { \"foo\": { \"type\": \"object\", \"properties\": { \"bar\": { \"type\": \"boolean\", \"default\": false } } } } }", config);
+ ObjectMapper objectMapper = new ObjectMapper();
+ assertDoesNotThrow(() -> {
+ schema.walk(objectMapper.readTree("{}"), false);
+ });
+ }
+
+}
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 0000000..d018f17
--- /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.Set;
+
+public class Issue606Test {
+ protected JsonSchema getJsonSchemaFromStreamContentV7(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;
+ }
+
+ @Test
+ public void shouldWorkV7() throws Exception {
+ String schemaPath = "/schema/issue606-v7.json";
+ String dataPath = "/data/issue606.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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 0000000..a743b7d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue619Test.java
@@ -0,0 +1,166 @@
+/*
+ * 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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static com.networknt.schema.BaseJsonSchemaValidatorTest.getJsonNodeFromStringContent;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class Issue619Test extends HTTPServiceSupport {
+
+ private JsonSchemaFactory factory;
+ private JsonNode one;
+ private JsonNode two;
+ private JsonNode three;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ one = getJsonNodeFromStringContent("1");
+ two = getJsonNodeFromStringContent("2");
+ three = getJsonNodeFromStringContent("3");
+ }
+
+ @Test
+ public void bundledSchemaLoadsAndValidatesCorrectly_Ref() {
+ JsonSchema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json\" }");
+
+ assertTrue(referencingRootSchema.validate(one).isEmpty());
+ assertTrue(referencingRootSchema.validate(two).isEmpty());
+ assertFalse(referencingRootSchema.validate(three).isEmpty());
+ }
+
+ @Test
+ public void bundledSchemaLoadsAndValidatesCorrectly_Uri() throws Exception {
+ JsonSchema 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
+ public void uriWithEmptyFragment_Ref() {
+ JsonSchema referencingRootSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#\" }");
+
+ assertTrue(referencingRootSchema.validate(one).isEmpty());
+ assertTrue(referencingRootSchema.validate(two).isEmpty());
+ assertFalse(referencingRootSchema.validate(three).isEmpty());
+ }
+
+ @Test
+ public void uriWithEmptyFragment_Uri() throws Exception {
+ JsonSchema 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
+ public void uriThatPointsToTwoShouldOnlyValidateTwo_Ref() {
+ JsonSchema 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
+ public void uriThatPointsToOneShouldOnlyValidateOne_Uri() throws Exception {
+ JsonSchema 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
+ public void uriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne_Ref() {
+ JsonSchema 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
+ public void uriThatPointsToNodeThatInTurnReferencesOneShouldOnlyValidateOne_Uri() throws Exception {
+ JsonSchema 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
+ public void uriThatPointsToSchemaWithIdThatHasDifferentUri_Ref() throws Exception {
+ JsonNode oneArray = getJsonNodeFromStringContent("[[1]]");
+ JsonNode textArray = getJsonNodeFromStringContent("[[\"a\"]]");
+
+ JsonSchema schemaWithIdFromRef = factory.getSchema("{ \"$ref\": \"resource:tests/draft4/refRemote.json#/3/schema\" }");
+ assertTrue(schemaWithIdFromRef.validate(oneArray).isEmpty());
+ assertFalse(schemaWithIdFromRef.validate(textArray).isEmpty());
+ }
+
+ @Test
+ public void uriThatPointsToSchemaWithIdThatHasDifferentUri_Uri() throws Exception {
+ JsonNode oneArray = getJsonNodeFromStringContent("[[1]]");
+ JsonNode textArray = getJsonNodeFromStringContent("[[\"a\"]]");
+
+ JsonSchema schemaWithIdFromUri = factory.getSchema(SchemaLocation.of("resource:tests/draft4/refRemote.json#/3/schema"));
+ assertTrue(schemaWithIdFromUri.validate(oneArray).isEmpty());
+ assertFalse(schemaWithIdFromUri.validate(textArray).isEmpty());
+ }
+
+ @Test
+ public void uriThatPointsToSchemaThatDoesNotExistShouldFail_Ref() {
+ JsonSchema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:data/schema-that-does-not-exist.json#/definitions/something\" }");
+
+ assertThrows(JsonSchemaException.class, () -> referencingNonexistentSchema.validate(one));
+ }
+
+ @Test
+ public void uriThatPointsToSchemaThatDoesNotExistShouldFail_Uri() {
+ assertThrows(JsonSchemaException.class, () -> factory.getSchema(SchemaLocation.of("resource:data/schema-that-does-not-exist.json#/definitions/something")));
+ }
+
+ @Test
+ public void uriThatPointsToNodeThatDoesNotExistShouldFail_Ref() {
+ JsonSchema referencingNonexistentSchema = factory.getSchema("{ \"$ref\": \"resource:schema/issue619.json#/definitions/node-that-does-not-exist\" }");
+
+ assertThrows(JsonSchemaException.class, () -> referencingNonexistentSchema.validate(one));
+ }
+
+ @Test
+ public 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(JsonSchemaException.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 0000000..02bffbf
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue650Test.java
@@ -0,0 +1,83 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+import java.util.Set;
+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
+ */
+public 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
+ public 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");
+ JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7).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:
+ Set<ValidationMessage> 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
+ */
+ 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 0000000..94ebb3f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue662Test.java
@@ -0,0 +1,60 @@
+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 java.util.Set;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class Issue662Test extends BaseJsonSchemaValidatorTest {
+
+ private static final String RESOURCE_PREFIX = "issues/662/";
+ private static JsonSchema schema;
+
+ @BeforeAll
+ static void setup() {
+ schema = getJsonSchemaFromClasspath(resource("schema.json"), SpecVersion.VersionFlag.V7);
+ }
+
+ @Test
+ void testNoErrorsForEmptyObject() throws IOException {
+ JsonNode node = getJsonNodeFromClasspath(resource("emptyObject.json"));
+ Set<ValidationMessage> 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"));
+ Set<ValidationMessage> 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"));
+ Set<ValidationMessage> errors = schema.validate(node);
+ List<String> errorMessages = errors.stream()
+ .map(v -> v.getEvaluationPath() + " = " + v.getMessage())
+ .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 0000000..7056ab2
--- /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 JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ 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);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ List<String> errorSchemaPaths = schema.validate(node).stream().map(ValidationMessage::getSchemaLocation)
+ .map(Object::toString).collect(Collectors.toList());
+
+ List<String> 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 0000000..23044c0
--- /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.Set;
+
+public class Issue665Test extends BaseJsonSchemaValidatorTest {
+
+ @Test
+ void testUrnUriAsLocalRef() throws IOException {
+ JsonSchema schema = getJsonSchemaFromClasspath("draft7/urn/issue665.json", SpecVersion.VersionFlag.V7);
+ Assertions.assertNotNull(schema);
+ Assertions.assertDoesNotThrow(schema::initializeValidators);
+ Set<ValidationMessage> messages = schema.validate(getJsonNodeFromStringContent(
+ "{\"myData\": {\"value\": \"hello\"}}"));
+ Assertions.assertEquals(messages, Collections.emptySet());
+ }
+
+ @Test
+ void testUrnUriAsLocalRef_ExternalURN() {
+ JsonSchemaFactory factory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
+ .schemaMappers(schemaMappers -> {
+ schemaMappers.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")) {
+ JsonSchema schema = factory.getSchema(is);
+ Assertions.assertNotNull(schema);
+ Assertions.assertDoesNotThrow(schema::initializeValidators);
+ Set<ValidationMessage> messages = schema.validate(getJsonNodeFromStringContent(
+ "{\"myData\": {\"value\": \"hello\"}}"));
+ Assertions.assertEquals(messages, Collections.emptySet());
+ } 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 0000000..bdecac7
--- /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 JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) throws Exception {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+ 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);
+ JsonSchema 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 0000000..1b35f06
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue686Test.java
@@ -0,0 +1,68 @@
+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.Locale;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue686Test {
+
+ @Test
+ void testDefaults() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ assertEquals(DefaultMessageSource.getInstance(), config.getMessageSource());
+ }
+
+ @Test
+ void testValidationWithDefaultBundleAndLocale() throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ ResourceBundle resourceBundle = ResourceBundle.getBundle(DefaultMessageSource.BUNDLE_BASE_NAME, Locale.getDefault());
+ String expectedMessage = new MessageFormat(resourceBundle.getString("type")).format(new String[] {"$.foo", "integer", "string"});
+ verify(config, expectedMessage);
+ }
+
+ @Test
+ void testValidationWithDefaultBundleAndCustomLocale() throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setLocale(Locale.ITALIAN);
+ verify(config, "$.foo: integer trovato, string previsto");
+ }
+
+ @Test
+ void testValidationWithCustomBundle() throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setMessageSource(new ResourceBundleMessageSource("issue686/translations"));
+ config.setLocale(Locale.FRENCH);
+ verify(config, "$.foo: integer found, string expected (TEST) (FR)");
+ }
+
+ @Test
+ void testLocaleSwitch() throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setLocale(Locale.ITALIAN);
+ verify(config, "$.foo: integer trovato, string previsto");
+ config.setLocale(Locale.FRENCH);
+ verify(config, "$.foo: integer trouvé, string attendu");
+ }
+
+ private JsonSchema getSchema(SchemaValidatorsConfig config) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ 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\" } } } }", config);
+ }
+
+ private void verify(SchemaValidatorsConfig config, String expectedMessage) throws JsonProcessingException {
+ Set<ValidationMessage> messages = getSchema(config).validate(new ObjectMapper().readTree(" { \"foo\": 123 } "));
+ assertEquals(1, messages.size());
+ assertEquals(expectedMessage, messages.iterator().next().getMessage());
+ }
+
+}
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 0000000..9621050
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue687Test.java
@@ -0,0 +1,134 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public 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);
+ }
+
+ public static Stream<Arguments> 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")
+ );
+ }
+
+ public static Stream<Arguments> 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")
+ );
+ }
+
+ public static Stream<Arguments> validationMessages() {
+ 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) {
+ assertEquals(expected, pathType.append(currentPath, token));
+ }
+
+ @ParameterizedTest
+ @MethodSource("appendIndexes")
+ void testAppendIndex(PathType pathType, String currentPath, Integer index, String expected) {
+ assertEquals(expected, pathType.append(currentPath, index));
+ }
+
+ @ParameterizedTest
+ @MethodSource("validationMessages")
+ void testValidationMessage(PathType pathType, String schemaPath, String content, String[] expectedMessagePaths) throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(pathType);
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ JsonSchema schema = factory.getSchema(Issue687Test.class.getResourceAsStream(schemaPath), config);
+ Set<ValidationMessage> 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())));
+ }
+ }
+
+ public static Stream<Arguments> 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();
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ schemaValidatorsConfig.setPathType(pathType);
+ JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)
+ .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" +
+ "}"), schemaValidatorsConfig);
+ Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree("{\""+propertyName+"\": 1}"));
+ assertEquals(1, validationMessages.size());
+ assertEquals(expectedPath, validationMessages.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 0000000..47b060c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue724Test.java
@@ -0,0 +1,87 @@
+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 java.util.Set;
+
+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.SpecVersion.VersionFlag;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
+
+class Issue724Test {
+
+ @Test
+ void test() throws JsonProcessingException {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ StringCollector stringCollector = new StringCollector();
+ config.addKeywordWalkListener(stringCollector);
+
+ 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";
+
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config);
+ jsonSchema.walk(new ObjectMapper().readTree(data), /* shouldValidateSchema= */ false);
+
+ System.out.println(stringCollector.strings);
+ assertLinesMatch(Arrays.asList("my_credit_card", "my_billing_address"), stringCollector.strings);
+ }
+
+ static class StringCollector implements JsonSchemaWalkListener {
+ final List<String> 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, Set<ValidationMessage> validationMessages) {
+ // 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 0000000..5be980c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue769ContainsTest.java
@@ -0,0 +1,39 @@
+package com.networknt.schema;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * <p>Test class for issue <a href="https://github.com/networknt/json-schema-validator/issues/769">#769</a></p>
+ * <p>This test class asserts that correct messages are returned for contains, minContains et maxContains keywords</p>
+ * <p>Tested class: {@link ContainsValidator}</p>
+ *
+ * @author vwuilbea
+ */
+class Issue769ContainsTest extends AbstractJsonSchemaTest {
+
+ @Override
+ protected String getDataTestFolder() {
+ return "/data/contains/issue769/";
+ }
+
+ @Test
+ void shouldReturnMinContainsKeyword() {
+ assertValidatorType("min-contains.json", ValidatorTypeCode.MIN_CONTAINS);
+ }
+
+ @Test
+ void shouldReturnContainsKeywordForMinContainsV7() {
+ assertValidatorType("min-contains-v7.json", ValidatorTypeCode.CONTAINS);
+ }
+
+ @Test
+ void shouldReturnMaxContainsKeyword() {
+ assertValidatorType("max-contains.json", ValidatorTypeCode.MAX_CONTAINS);
+ }
+
+ @Test
+ void shouldReturnContainsKeywordForMaxContainsV7() {
+ assertValidatorType("max-contains-v7.json", ValidatorTypeCode.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 0000000..4f8dbdc
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue784Test.java
@@ -0,0 +1,81 @@
+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.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public 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);
+ }
+
+ @Override
+ public String getErrorMessageDescription() {
+ return null;
+ }
+ }
+
+ @Test
+ public void allowToOverrideDataTime() throws IOException {
+ JsonSchema 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
+ public void useDefaultValidatorIfNotOverriden() throws IOException {
+ JsonSchema 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 Set<ValidationMessage> validate(JsonSchema jsonSchema, String myDateTimeContent) throws JsonProcessingException {
+ return jsonSchema.validate(new ObjectMapper().readTree(" { \"my-date-time\": \"" + myDateTimeContent + "\" } "));
+ }
+
+ private JsonSchema createSchema(boolean useCustomDateFormat) {
+ JsonMetaSchema overrideDateTimeValidator =JsonMetaSchema
+ .builder(JsonMetaSchema.getV7().getIri(), JsonMetaSchema.getV7())
+ .formats(formats -> {
+ if (useCustomDateFormat) {
+ CustomDateTimeFormat format = new CustomDateTimeFormat();
+ formats.put(format.getName(), format);
+ }
+ })
+ .build();
+
+ return new JsonSchemaFactory
+ .Builder()
+ .defaultMetaSchemaIri(overrideDateTimeValidator.getIri())
+ .metaSchema(overrideDateTimeValidator)
+ .build()
+ .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 0000000..935336e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue792.java
@@ -0,0 +1,40 @@
+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;
+
+public class Issue792 {
+
+ @Test
+ void test() throws JsonProcessingException {
+ JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+
+ 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" +
+ "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setTypeLoose(false);
+ config.setFailFast(true);
+
+ JsonSchema jsonSchema = schemaFactory.getSchema(schemaDef, config);
+ 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 0000000..fe8babb
--- /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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class Issue824Test {
+ @Test
+ void validate() throws JsonProcessingException {
+ final JsonSchema v201909SpecSchema = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
+ .schemaMappers(schemaMappers -> {
+ schemaMappers.mapPrefix("https://json-schema.org", "resource:");
+ }).build()
+ .getSchema(SchemaLocation.of(JsonMetaSchema.getV201909().getIri()));
+ v201909SpecSchema.preloadJsonSchema();
+ 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 Set<ValidationMessage> validationErrors1 = v201909SpecSchema.validate(invalidSchema);
+ final Set<ValidationMessage> 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 0000000..1518968
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue832Test.java
@@ -0,0 +1,64 @@
+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.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public 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 getErrorMessageDescription() {
+ return "always fail match";
+ }
+ }
+
+ private JsonSchemaFactory buildV7PlusNoFormatSchemaFactory() {
+ List<Format> formats;
+ formats = new ArrayList<>();
+ formats.add(new NoMatchFormat());
+
+ JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(
+ JsonMetaSchema.getV7().getIri(),
+ JsonMetaSchema.getV7())
+ .formats(formats)
+ .build();
+ return new JsonSchemaFactory.Builder().defaultMetaSchemaIri(jsonMetaSchema.getIri()).metaSchema(jsonMetaSchema).build();
+ }
+
+ protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void testV7WithNonMatchingCustomFormat() throws IOException {
+ String schemaPath = "/schema/issue832-v7.json";
+ String dataPath = "/data/issue832.json";
+ InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+ JsonSchemaFactory factory = buildV7PlusNoFormatSchemaFactory();
+ JsonSchema schema = factory.getSchema(schemaInputStream);
+ InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ Set<ValidationMessage> 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 0000000..8d533e5
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue857Test.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 java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFailFast(true);
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ Set<ValidationMessage> result = factory.getSchema(schema, config).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 0000000..ab57404
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue877Test.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.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+public class Issue877Test {
+ @Test
+ public void test() throws Exception {
+ String schemaData = "{\n"
+ + " \"type\": \"object\",\n"
+ + " \"unevaluatedProperties\": false\n"
+ + "}";
+
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ JsonSchema schema = jsonSchemaFactory.getSchema(schemaData);
+ String input = "{}";
+ ValidationResult result = schema.walk(JsonMapperFactory.getInstance().readTree(input), true);
+ assertEquals(0, result.getValidationMessages().size());
+
+ input = "";
+ result = schema.walk(JsonMapperFactory.getInstance().readTree(input), true);
+ assertEquals(1, result.getValidationMessages().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 0000000..1ec5f3e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue898Test.java
@@ -0,0 +1,31 @@
+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 {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setLocale(Locale.FRENCH);
+
+ JsonSchema schema = getJsonSchemaFromClasspath("schema/issue898.json", SpecVersion.VersionFlag.V202012, config);
+ JsonNode node = getJsonNodeFromClasspath("data/issue898.json");
+
+ List<String> messages = schema.validate(node).stream()
+ .map(ValidationMessage::getMessage)
+ .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 0000000..5a56e2c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue927Test.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * Test that the code isn't confused by an anchor in the id.
+ */
+public class Issue927Test {
+ @Test
+ void test() throws JsonMappingException, 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"
+ + "}";
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V7)
+ .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"
+ + "}";
+ Set<ValidationMessage> 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 0000000..df933da
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue928Test.java
@@ -0,0 +1,58 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class Issue928Test {
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private JsonSchemaFactory factoryFor(SpecVersion.VersionFlag version) {
+ return JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(version))
+ .jsonMapper(mapper)
+ .schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://example.org", "classpath:"))
+ .build();
+ }
+
+ @Test
+ public void test_07() {
+ test_spec(SpecVersion.VersionFlag.V7);
+ }
+
+ @Test
+ public void test_201909() {
+ test_spec(SpecVersion.VersionFlag.V201909);
+ }
+
+ @Test
+ public void test_202012() {
+ test_spec(SpecVersion.VersionFlag.V202012);
+ }
+
+ public void test_spec(SpecVersion.VersionFlag specVersion) {
+ JsonSchemaFactory schemaFactory = factoryFor(specVersion);
+
+ String versionId = specVersion.getId();
+ 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);
+
+ JsonSchema 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);
+
+ JsonSchema 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 0000000..fdc0186
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue935Test.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;
+
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public class Issue935Test {
+ @Test
+ void shouldThrowInvalidSchemaException() {
+ String schema = "{ \"$schema\": \"0\" }";
+ assertThrowsExactly(InvalidSchemaException.class,
+ () -> JsonSchemaFactory.getInstance(VersionFlag.V201909).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 0000000..b9be21f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue936Test.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.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public class Issue936Test {
+ @Test
+ void shouldThrowInvalidSchemaException() {
+ String schema = "{\r\n" + " \"$id\": \"0\",\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\r\n" + "}";
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setSchemaIdValidator(JsonSchemaIdValidator.DEFAULT);
+ assertThrowsExactly(InvalidSchemaException.class,
+ () -> JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config));
+ try {
+ JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schema, config);
+ } catch (InvalidSchemaException e) {
+ assertEquals("/$id: '0' is not a valid $id", e.getMessage());
+ }
+ }
+}
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 0000000..19b36c7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue939Test.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.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public 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"
+ + " }";
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schema);
+ assertDoesNotThrow(() -> jsonSchema.initializeValidators());
+ Set<ValidationMessage> 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 0000000..de30e9b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue940Test.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;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public 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"
+ + "}";
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V7).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 0000000..82fbc6a
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue943Test.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+public class Issue943Test {
+ @Test
+ void test() {
+ Map<String, String> 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(external)));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ 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/ItemsValidator202012Test.java b/src/test/java/com/networknt/schema/ItemsValidator202012Test.java
new file mode 100644
index 0000000..401e182
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ItemsValidator202012Test.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;
+
+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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * ItemsValidatorTest.
+ */
+public class ItemsValidator202012Test {
+ /**
+ * 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\"}"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[1, \"x\"]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ assertNull(message.getProperty());
+ }
+}
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 0000000..8823028
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ItemsValidatorTest.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;
+
+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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * ItemsValidatorTest.
+ */
+public 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\"}"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[1, \"x\"]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ 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\"}"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[ null, 2, 3, \"foo\" ]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[ null, 2, 3, \"foo\" ]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ assertNull(message.getProperty());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/JsonNodeAnnotationsTest.java b/src/test/java/com/networknt/schema/JsonNodeAnnotationsTest.java
new file mode 100644
index 0000000..5963939
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonNodeAnnotationsTest.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.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.annotation.JsonNodeAnnotations;
+
+/**
+ * JsonNodeAnnotationsTest.
+ */
+class JsonNodeAnnotationsTest {
+ @Test
+ void put() {
+ JsonNodeAnnotations annotations = new JsonNodeAnnotations();
+ JsonNodeAnnotation annotation = new JsonNodeAnnotation("unevaluatedProperties",
+ new JsonNodePath(PathType.JSON_POINTER), SchemaLocation.of(""), new JsonNodePath(PathType.JSON_POINTER),
+ "test");
+ annotations.put(annotation);
+ assertTrue(annotations.asMap().get(annotation.getInstanceLocation()).contains(annotation));
+ }
+}
diff --git a/src/test/java/com/networknt/schema/JsonNodePathTest.java b/src/test/java/com/networknt/schema/JsonNodePathTest.java
new file mode 100644
index 0000000..9f91ca9
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonNodePathTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+class JsonNodePathTest {
+
+ @Test
+ void getNameCount() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ JsonNodePath path = root.append("hello").append("world");
+ assertEquals(2, path.getNameCount());
+ }
+
+ @Test
+ void getName() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ JsonNodePath 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() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ JsonNodePath a = root.append("a");
+ JsonNodePath aa = a.append("a");
+
+ JsonNodePath b = root.append("b");
+ JsonNodePath bb = b.append("b");
+ JsonNodePath b1 = b.append(1);
+ JsonNodePath bbb = bb.append("b");
+
+ JsonNodePath c = root.append("c");
+ JsonNodePath cc = c.append("c");
+
+ List<JsonNodePath> 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() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ JsonNodePath a1 = root.append("a");
+ JsonNodePath a2 = root.append("a");
+ assertEquals(a1, a2);
+ }
+
+ @Test
+ void hashCodeEquals() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ JsonNodePath a1 = root.append("a");
+ JsonNodePath a2 = root.append("a");
+ assertEquals(a1.hashCode(), a2.hashCode());
+ }
+
+ @Test
+ void getPathType() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_POINTER);
+ assertEquals(PathType.JSON_POINTER, root.getPathType());
+ }
+
+ @Test
+ void getElement() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_PATH);
+ JsonNodePath 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() {
+ JsonNodePath root = new JsonNodePath(PathType.JSON_PATH);
+ JsonNodePath path = root.append("items");
+ JsonNodePath 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/JsonSchemaFactoryUriCacheTest.java b/src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java
new file mode 100644
index 0000000..27cac9a
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonSchemaFactoryUriCacheTest.java
@@ -0,0 +1,71 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.resource.InputStreamSource;
+import com.networknt.schema.resource.SchemaLoader;
+
+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;
+
+public class JsonSchemaFactoryUriCacheTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ public void cacheEnabled() throws JsonProcessingException {
+ runCacheTest(true);
+ }
+
+ @Test
+ public void cacheDisabled() throws JsonProcessingException {
+ runCacheTest(false);
+ }
+
+ private void runCacheTest(boolean enableCache) throws JsonProcessingException {
+ CustomURIFetcher fetcher = new CustomURIFetcher();
+ JsonSchemaFactory 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, new SchemaValidatorsConfig()).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, new SchemaValidatorsConfig()).schemaNode);
+ }
+
+ private JsonSchemaFactory buildJsonSchemaFactory(CustomURIFetcher uriFetcher, boolean enableSchemaCache) {
+ return JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012))
+ .enableSchemaCache(enableSchemaCache)
+ .schemaLoaders(schemaLoaders -> schemaLoaders.add(uriFetcher))
+ .metaSchema(JsonMetaSchema.getV202012())
+ .build();
+ }
+
+ private class CustomURIFetcher implements SchemaLoader {
+
+ private Map<AbsoluteIri, InputStream> 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 getSchema(AbsoluteIri absoluteIri) {
+ return () -> uriToResource.get(absoluteIri);
+ }
+ }
+}
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 0000000..f3f3800
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteExtrasTest.java
@@ -0,0 +1,53 @@
+package com.networknt.schema;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+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<DynamicNode> draft2022012() {
+ return createTests(VersionFlag.V202012, "src/test/resources/draft2020-12");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 2019-09")
+ Stream<DynamicNode> draft201909() {
+ return createTests(VersionFlag.V201909, "src/test/resources/draft2019-09");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 7")
+ Stream<DynamicNode> draft7() {
+ return createTests(VersionFlag.V7, "src/test/resources/draft7");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 6")
+ Stream<DynamicNode> draft6() {
+ return createTests(VersionFlag.V6, "src/test/resources/draft6");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 4")
+ Stream<DynamicNode> draft4() {
+ return createTests(VersionFlag.V4, "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 0000000..8528b12
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonSchemaTestSuiteTest.java
@@ -0,0 +1,90 @@
+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;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+@DisplayName("JSON Schema Test Suite")
+class JsonSchemaTestSuiteTest extends AbstractJsonSchemaTestSuite {
+
+ private final Map<Path, String> disabled;
+
+ public JsonSchemaTestSuiteTest() {
+ this.disabled = new HashMap<>();
+
+ disableV202012Tests();
+ disableV201909Tests();
+ disableV7Tests();
+ disableV6Tests();
+ disableV4Tests();
+ }
+
+ @TestFactory
+ @DisplayName("Draft 2020-12")
+ Stream<DynamicNode> draft2022012() {
+ return createTests(VersionFlag.V202012, "src/test/suite/tests/draft2020-12");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 2019-09")
+ Stream<DynamicNode> draft201909() {
+ return createTests(VersionFlag.V201909, "src/test/suite/tests/draft2019-09");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 7")
+ Stream<DynamicNode> draft7() {
+ return createTests(VersionFlag.V7, "src/test/suite/tests/draft7");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 6")
+ Stream<DynamicNode> draft6() {
+ return createTests(VersionFlag.V6, "src/test/suite/tests/draft6");
+ }
+
+ @TestFactory
+ @DisplayName("Draft 4")
+ Stream<DynamicNode> draft4() {
+ return createTests(VersionFlag.V4, "src/test/suite/tests/draft4");
+ }
+
+ @Override
+ protected boolean enabled(Path path) {
+ return !this.disabled.containsKey(path);
+ }
+
+ @Override
+ protected Optional<String> 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
new file mode 100644
index 0000000..abb0b06
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java
@@ -0,0 +1,134 @@
+package com.networknt.schema;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.hamcrest.Matchers;
+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);
+ 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",
+ "$.outer.reference.stringValue_missing_with_default_null: null found, string expected"));
+ } else {
+ assertThat(result.getValidationMessages(), Matchers.empty());
+ }
+ // TODO: In Java 14 use text blocks
+ assertEquals(
+ objectMapper.readTree(
+ "{\"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);
+ }
+
+ @Test
+ 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",
+ "$.outer.reference.stringValue_missing_with_default_null: null found, string expected"));
+ assertEquals(
+ objectMapper.readTree(
+ "{\"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);
+ }
+
+ @Test
+ 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",
+ "$.outer.reference.stringValue_missing_with_default_null: null found, string expected"));
+ assertEquals(
+ objectMapper.readTree(
+ "{\"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);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "walkWithEmptyStrategy", "walkWithNoDefaults", "validateWithApplyAllDefaults"} )
+ 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<ValidationMessage> validationMessages;
+ switch (method) {
+ case "walkWithEmptyStrategy": {
+ JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(false, false, false));
+ validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages();
+ break;
+ }
+ case "walkWithNoDefaults": {
+ // same empty strategy, but tests for NullPointerException
+ JsonSchema jsonSchema = createSchema(null);
+ validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages();
+ break;
+ }
+ case "validateWithApplyAllDefaults": {
+ JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, true));
+ validationMessages = jsonSchema.validate(inputNode);
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException();
+ }
+ assertThat(validationMessages.stream().map(ValidationMessage::getMessage).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);
+ }
+
+ @Test
+ void testIllegalArgumentException() {
+ try {
+ new ApplyDefaultsStrategy(false, true, false);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
+
+ 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);
+ }
+}
diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java
new file mode 100644
index 0000000..3ff08e1
--- /dev/null
+++ b/src/test/java/com/networknt/schema/JsonWalkTest.java
@@ -0,0 +1,222 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
+
+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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JsonWalkTest {
+
+ private JsonSchema jsonSchema;
+
+ private JsonSchema jsonSchema1;
+
+ private static final String SAMPLE_WALK_COLLECTOR_TYPE = "sampleWalkCollectorType";
+
+ private static final String CUSTOM_KEYWORD = "custom-keyword";
+
+ @BeforeEach
+ public void setup() {
+ setupSchema();
+ }
+
+ private void setupSchema() {
+ final JsonMetaSchema metaSchema = getJsonMetaSchema();
+ // Create Schema.
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener());
+ schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener());
+ schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(),
+ new PropertiesKeywordListener());
+ final JsonSchemaFactory schemaFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
+ .build();
+ this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
+ // Create another Schema.
+ SchemaValidatorsConfig schemaValidatorsConfig1 = new SchemaValidatorsConfig();
+ schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener());
+ schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(),
+ new PropertiesKeywordListener());
+ this.jsonSchema1 = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig1);
+ }
+
+ private JsonMetaSchema getJsonMetaSchema() {
+ return JsonMetaSchema.builder(
+ "https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV201909())
+ .keyword(new CustomKeyword()).build();
+ }
+
+ @Test
+ public void testWalk() throws IOException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ ValidationResult result = jsonSchema.walk(
+ objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false);
+ JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE);
+ assertEquals(collectedNode, (objectMapper.readTree("{" +
+ " \"PROPERTY1\": \"sample1\","
+ + " \"PROPERTY2\": \"sample2\","
+ + " \"property3\": {"
+ + " \"street_address\":\"test-address\","
+ + " \"phone_number\": {"
+ + " \"country-code\": \"091\","
+ + " \"number\": \"123456789\""
+ + " }"
+ + " }"
+ + "}")));
+ }
+
+ @Test
+ public 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);
+ JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE);
+ assertEquals(collectedNode, (objectMapper.readTree("{" +
+ " \"PROPERTY1\": \"sample1\","
+ + " \"PROPERTY2\": \"sample2\","
+ + " \"property3\": {"
+ + " \"street_address\":\"test-address\","
+ + " \"phone_number\": {"
+ + " \"country-code\": \"091\","
+ + " \"number\": \"123456789\""
+ + " }"
+ + " }"
+ + "}")));
+ // This instance of schema contains one listener removed.
+ result = jsonSchema1.walk(objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false);
+ collectedNode = (JsonNode) result.getExecutionContext().getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE);
+ assertEquals(collectedNode, (objectMapper.readTree("{"
+ + " \"property3\": {"
+ + " \"street_address\":\"test-address\","
+ + " \"phone_number\": {"
+ + " \"country-code\": \"091\","
+ + " \"number\": \"123456789\""
+ + " }"
+ + " }"
+ + "}")));
+ }
+
+ private InputStream getSchema() {
+ return getClass().getClassLoader().getResourceAsStream("schema/walk-schema.json");
+ }
+
+ /**
+ * Our own custom keyword.
+ */
+ private static class CustomKeyword implements Keyword {
+ @Override
+ public String getValue() {
+ return "custom-keyword";
+ }
+
+ @Override
+ public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException {
+ if (schemaNode != null && schemaNode.isArray()) {
+ return new CustomValidator(schemaLocation, evaluationPath, schemaNode);
+ }
+ return null;
+ }
+
+ /**
+ * We will be collecting information/data by adding the data in the form of
+ * collectors into collector context object while we are validating this node.
+ * 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 extends AbstractJsonValidator {
+
+ public CustomValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode) {
+ super(schemaLocation, evaluationPath, new CustomKeyword(), schemaNode);
+ }
+
+ @Override
+ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+ return new TreeSet<ValidationMessage>();
+ }
+
+ @Override
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ return new LinkedHashSet<ValidationMessage>();
+ }
+ }
+ }
+
+ private static class AllKeywordListener implements JsonSchemaWalkListener {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
+ ObjectMapper mapper = new ObjectMapper();
+ 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());
+ }
+ if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) {
+ ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE);
+ objectNode.put(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toUpperCase(),
+ keywordWalkEvent.getInstanceNode().textValue());
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) {
+
+ }
+ }
+
+ private static class RefKeywordListener implements JsonSchemaWalkListener {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
+ ObjectMapper mapper = new ObjectMapper();
+ CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext();
+ if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) {
+ collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode());
+ }
+ ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE);
+ objectNode.set(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toLowerCase(),
+ keywordWalkEvent.getInstanceNode());
+ return WalkFlow.SKIP;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) {
+
+ }
+ }
+
+ private static class PropertiesKeywordListener implements JsonSchemaWalkListener {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
+ JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
+ if (schemaNode.get("title").textValue().equals("Property3")) {
+ return WalkFlow.SKIP;
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) {
+
+ }
+ }
+
+}
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 0000000..4fc386f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/LocaleTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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 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.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.i18n.Locales;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+public class LocaleTest {
+ private JsonSchema getSchema(SchemaValidatorsConfig config) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+ 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\" } } } }",
+ config);
+ }
+
+ /**
+ * 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 } ");
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ JsonSchema 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.getExecutionConfig().setLocale(locale);
+ Set<ValidationMessage> messages = jsonSchema.validate(executionContext, rootNode);
+ assertEquals(1, messages.size());
+ assertEquals("$.foo: integer trouvé, string attendu", messages.iterator().next().getMessage());
+
+ locale = Locales.findSupported("it;q=1.0,fr;q=0.9"); // it
+ executionContext = jsonSchema.createExecutionContext();
+ assertEquals(config.getLocale(), executionContext.getExecutionConfig().getLocale());
+ executionContext.getExecutionConfig().setLocale(locale);
+ messages = jsonSchema.validate(executionContext, rootNode);
+ assertEquals(1, messages.size());
+ assertEquals("$.foo: integer trovato, string previsto", messages.iterator().next().getMessage());
+ }
+
+ /**
+ * Issue 949.
+ * <p>
+ * 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"
+ + "}";
+ JsonSchema jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V7)
+ .getSchema(JsonMapperFactory.getInstance().readTree(schema));
+ String input = "1";
+ Set<ValidationMessage> messages = jsonSchema.validate(input, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ assertEquals("$: integer gefunden, object erwartet", messages.iterator().next().toString());
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setLocale(Locale.ENGLISH);
+ jsonSchema = JsonSchemaFactory.getInstance(VersionFlag.V7)
+ .getSchema(JsonMapperFactory.getInstance().readTree(schema), config);
+ 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.
+ * <p>
+ * Java 8 does not support UTF-8 encoded resource bundles. That is only
+ * supported in Java 9 and above.
+ */
+ @Test
+ void encoding() {
+ Map<String, String> 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("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<String, String> expectedAlternate = new HashMap<>();
+ expectedAlternate.put("ar","$: يجب أن يكون طوله Ù¥ حرÙًا على الأكثر");
+ expectedAlternate.put("fa","$: باید حداکثر ۵ کاراکتر باشد");
+
+ String schemaData = "{\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"maxLength\": 5\r\n"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData);
+ List<Locale> locales = Locales.getSupportedLocales();
+ for (Locale locale : locales) {
+ Set<ValidationMessage> messages = schema.validate("\"aaaaaa\"", InputFormat.JSON, executionContext -> {
+ executionContext.getExecutionConfig().setLocale(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
new file mode 100644
index 0000000..99d9027
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MaximumValidatorPerfTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@Disabled
+public class MaximumValidatorPerfTest {
+ MaximumValidatorTest test = new MaximumValidatorTest();
+
+ @Test
+ public void testTime() throws InvocationTargetException, IllegalAccessException {
+ String[] testMethodsToBeExecuted = {"testMaximumDoubleValue"};
+ List<Method> testMethods = getTestMethods(testMethodsToBeExecuted);
+ long start = System.currentTimeMillis();
+ executeTests(testMethods, 200000);
+ long end = System.currentTimeMillis();
+ System.out.println("time to execute all tests using:" + (end - start) + "ms");
+ }
+
+ public void executeTests(List<Method> methods, int executeTimes) throws InvocationTargetException, IllegalAccessException {
+
+ for (int i = 0; i < executeTimes; i++) {
+ for (Method testMethod : methods) {
+ testMethod.invoke(test);
+ }
+ }
+
+ }
+
+ public List<Method> getTestMethods(String[] methodNames) {
+ Method[] methods = test.getClass().getMethods();
+ List<Method> testMethods = new ArrayList<Method>();
+ if (methodNames.length > 0) {
+ for (String name : methodNames) {
+ Collection<Method> listOfMethodNames = new ArrayList<Method>();
+ for (Method testMethod : methods) {
+ if (testMethod.getName().equals(name)) {
+ listOfMethodNames.add(testMethod);
+ }
+ }
+ testMethods.addAll(listOfMethodNames);
+ }
+ return testMethods;
+ }
+ for (Method m : methods) {
+ Annotation[] annotations = m.getDeclaredAnnotations();
+ boolean isTestMethod = false;
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType() == Test.class) {
+ isTestMethod = true;
+ }
+ }
+ if (isTestMethod) {
+ //filter out incompatible test cases.
+ if (!m.getName().equals("doubleValueCoarsing") && !m.getName().equals("negativeDoubleOverflowTest"))
+ testMethods.add(m);
+ }
+ }
+ return testMethods;
+ }
+}
diff --git a/src/test/java/com/networknt/schema/MaximumValidatorTest.java b/src/test/java/com/networknt/schema/MaximumValidatorTest.java
new file mode 100644
index 0000000..6f5a9cb
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MaximumValidatorTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Set;
+
+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 {
+ 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 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);
+
+ static String[][] augmentWithQuotes(String[][] values) {
+ for (int i = 0; i < values.length; i++) {
+ String[] pair = values[i];
+ values[i] = new String[]{pair[0], format("\"%s\"", pair[1])};
+ }
+ return values;
+ }
+
+ @Test
+ public void positiveNumber() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+ {"1000.1", "1000"},
+ {"1000", "1E3"},
+ });
+
+ expectNoMessages(values, NUMBER);
+
+ }
+
+ @Test
+ public void negativeNumber() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+// These values overflow 64bit IEEE 754
+ {"1.7976931348623157e+308", "1.7976931348623159e+308"},
+ {"1.7976931348623156e+308", "1.7976931348623157e+308"},
+
+// Here, threshold is parsed as integral number, yet payload is 'number'
+ {"1000", "1000.1"},
+
+// See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+// {"1.7976931348623157e+308", "1.7976931348623158e+308"},
+ });
+
+ expectSomeMessages(values, NUMBER);
+
+ expectSomeMessages(values, NUMBER, mapper, bigDecimalMapper);
+
+ expectSomeMessages(values, NUMBER, bigDecimalMapper, bigDecimalMapper);
+ }
+
+ @Test
+ public void positiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+ {"9223372036854775807", "9223372036854775807"},
+ {"9223372036854775808", "9223372036854775808"},
+
+// testIntegerTypeWithFloatMaxPositive
+ {"37.7", "37"},
+
+// testMaximumDoubleValue
+ {"1E39", "1000"},
+ });
+
+ expectNoMessages(values, INTEGER);
+
+ expectNoMessages(values, INTEGER, bigIntegerMapper);
+ }
+
+ @Test
+ public void negativeInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+ {"9223372036854775800", "9223372036854775855"},
+ {"9223372036854775807", "9223372036854775808"},
+ {"9223372036854775807", new BigDecimal(String.valueOf(Double.MAX_VALUE)).add(BigDecimal.ONE).toString()},
+ {"9223372036854775806", new BigDecimal(String.valueOf(Double.MAX_VALUE)).add(BigDecimal.ONE).toString()},
+ {"9223372036854776000", "9223372036854776001"},
+ {"1000", "1E39"},
+ {"37.7", "38"},
+ });
+
+ expectSomeMessages(values, INTEGER);
+
+ expectSomeMessages(values, INTEGER, mapper, bigIntegerMapper);
+ }
+
+ @Test
+ public void positiveExclusiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+ {"9223372036854775000", "9223372036854774988"},
+ {"20", "10"},
+
+// threshold outside long range
+ {"9223372036854775809", "9223372036854775806"},
+
+// both threshold and value are outside long range
+ {"9223372036854775809", "9223372036854775808"},
+ });
+
+ expectNoMessages(values, EXCLUSIVE_INTEGER);
+
+ expectNoMessages(values, EXCLUSIVE_INTEGER, bigIntegerMapper);
+ }
+
+ @Test
+ public void negativeExclusiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// maximum, value
+ {"10", "20"},
+
+// value outside long range
+ {"9223372036854775806", "9223372036854775808"},
+
+// both threshold and value are outside long range
+ {"9223372036854775808", "9223372036854775809"},
+ });
+
+ expectSomeMessages(values, EXCLUSIVE_INTEGER);
+
+ expectSomeMessages(values, EXCLUSIVE_INTEGER, mapper, bigIntegerMapper);
+ }
+
+ @Test
+ public 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
+ {"1.79769313486231571E+308", "1.79769313486231572e+308"},
+// while underflow in not captures in previous case (unquoted number is parsed as double)
+// it is captured if value is passed as string, which is correctly parsed by BidDecimal
+// thus effective comparison is between
+// maximum 1.7976931348623157E+308 and
+// value 1.79769313486231572e+308
+// {"1.79769313486231571E+308", "\"1.79769313486231572e+308\""},
+ {"1.7976931348623157E+309", "1.7976931348623157e+309"},
+ {"1.7976931348623157E+309", "\"1.7976931348623157e+309\""},
+ {"1.000000000000000000000001E+400", "1.000000000000000000000001E+401"},
+ {"1.000000000000000000000001E+400", "\"1.000000000000000000000001E+401\""},
+ {"1.000000000000000000000001E+400", "1.000000000000000000000002E+400"},
+ {"1.000000000000000000000001E+400", "\"1.000000000000000000000002E+400\""},
+ {"1.000000000000000000000001E+400", "1.0000000000000000000000011E+400"},
+ {"1.000000000000000000000001E+400", "\"1.0000000000000000000000011E+400\""},
+ };
+
+ 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);
+ JsonNode doc = mapper.readTree(value);
+ Set<ValidationMessage> 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<ValidationMessage> 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 {
+ assertFalse(messages2.isEmpty(), format("Maximum %s is smaller than value %s , should be validation error reported", maximum, value));
+ }
+
+
+ // schema and document parsed with BigDecimal
+ v = factory.getSchema(bigDecimalMapper.readTree(schema), config);
+ Set<ValidationMessage> 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)) {
+ 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));
+ }
+ }
+ }
+
+ /**
+ * value of 1.7976931348623158e+308 is not converted to POSITIVE_INFINITY for some reason
+ * the only way to spot this is to use BigDecimal for schema (and for document)
+ */
+ @Test
+ public 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));
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper");
+
+ doc = bigDecimalMapper.readTree(content);
+ messages = v.validate(doc);
+ // "1.7976931348623158e+308" == "1.7976931348623157e+308" == Double.MAX_VALUE
+ // new BigDecimal("1.7976931348623158e+308").compareTo(new BigDecimal("1.7976931348623157e+308")) > 0
+ assertFalse(messages.isEmpty(), "Validation should not succeed because content is using bigDecimalMapper, and bigger than the maximum");
+
+ /*
+ * Note: technically this is where 1.7976931348623158e+308 rounding to 1.7976931348623157e+308 could be spotted,
+ * yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values above
+ * 1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
+ * "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
+ * seems infeasible.
+ */
+ v = factory.getSchema(bigDecimalMapper.readTree(schema));
+ messages = v.validate(doc);
+ assertFalse(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper");
+ }
+
+ /**
+ * 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 {
+ 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));
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper");
+
+ doc = bigDecimalMapper.readTree(content);
+ messages = v.validate(doc);
+ // "1.7976931348623158e+308" == "1.7976931348623157e+308" == Double.MAX_VALUE
+ // new BigDecimal("1.7976931348623158e+308").compareTo(new BigDecimal("1.7976931348623157e+308")) > 0
+ assertTrue(messages.isEmpty(), "Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY");
+
+ /*
+ * Note: technically this is where 1.7976931348623158e+308 rounding to 1.7976931348623157e+308 could be spotted,
+ * yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values above
+ * 1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
+ * "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
+ * seems infeasible.
+ */
+ 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");
+ }
+
+ private static final String POSITIVE_TEST_CASE_TEMPLATE = "Expecting no validation errors, maximum %s is greater than value %s";
+
+ private static void expectNoMessages(String[][] values, String schemaTemplate) throws IOException {
+ expectNoMessages(values, schemaTemplate, mapper);
+ }
+
+ private static void expectNoMessages(String[][] values, String schemaTemplate, ObjectMapper mapper) throws IOException {
+ for (String[] aTestCycle : values) {
+ 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);
+ JsonNode doc = mapper.readTree(value);
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), format(MaximumValidatorTest.POSITIVE_TEST_CASE_TEMPLATE, maximum, value));
+ }
+ }
+
+ private static final String NEGATIVE_TEST_CASE_TEMPLATE = "Expecting validation error, value %s is greater than maximum %s";
+
+ private static void expectSomeMessages(String[][] values, String schemaTemplate) throws IOException {
+ expectSomeMessages(values, schemaTemplate, mapper, mapper);
+ }
+
+ private static void expectSomeMessages(String[][] values, String schemaTemplate, ObjectMapper mapper, ObjectMapper mapper2) throws IOException {
+ for (String[] aTestCycle : values) {
+ String maximum = aTestCycle[0];
+ String value = aTestCycle[1];
+ String schema = format(schemaTemplate, maximum);
+
+ JsonSchema v = factory.getSchema(mapper.readTree(schema));
+ JsonNode doc = mapper2.readTree(value);
+
+ Set<ValidationMessage> 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 0000000..3a3faa7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MessageTest.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 java.util.Collections;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test for messages.
+ */
+public class MessageTest {
+ public static 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<ValidationMessage> 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();
+ }
+ }
+
+ public static 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);
+ }
+ }
+
+ @Test
+ void message() {
+ JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012().getIri(), JsonMetaSchema.getV202012())
+ .keyword(new EqualsKeyword()).build();
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema));
+ String schemaData = "{\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"equals\": \"helloworld\"\r\n"
+ + "}";
+ JsonSchema schema = factory.getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate("\"helloworlda\"", InputFormat.JSON);
+ assertEquals(1, messages.size());
+ assertEquals("$: must be equal to 'helloworld'", messages.iterator().next().getMessage());
+
+ 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 0000000..4abd434
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MetaSchemaValidationTest.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.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * Tests for meta schema validating a schema.
+ */
+public 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/v31/petstore.json")) {
+ JsonNode inputData = JsonMapperFactory.getInstance().readTree(input);
+ 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/v31")
+ .mapPrefix("https://json-schema.org", "classpath:")))
+ .getSchema(SchemaLocation.of("https://spec.openapis.org/oas/3.1/schema-base/2022-10-07"), config);
+ Set<ValidationMessage> 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
new file mode 100644
index 0000000..c43cd65
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MinimumValidatorTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.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 {
+ 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 ObjectMapper mapper;
+ private static ObjectMapper bigDecimalMapper;
+ private static ObjectMapper bigIntegerMapper;
+
+ @BeforeEach
+ public 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
+ // https://github.com/FasterXML/jackson-databind/issues/2087
+ bigDecimalMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+ bigIntegerMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
+
+ }
+
+ @Test
+ public void positiveNumber() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"1000", "1000.1"},
+ });
+
+ expectNoMessages(values, NUMBER, mapper);
+ }
+
+ @Test
+ public void negativeNumber() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"-1.7976931348623157e+308", "-1.7976931348623159e+308"},
+ {"-1.7976931348623156e+308", "-1.7976931348623157e+308"},
+ {"-1000", "-1E309"},
+ {"1000.1", "1000"},
+// See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+// {"-1.7976931348623157e+308", "-1.7976931348623158e+308"},
+ });
+
+ expectSomeMessages(values, NUMBER, mapper, mapper);
+
+ expectSomeMessages(values, NUMBER, mapper, bigDecimalMapper);
+
+ expectSomeMessages(values, NUMBER, bigDecimalMapper, bigDecimalMapper);
+ }
+
+ @Test
+ public void positiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"-1E309", "-1000"},
+ {"-9223372036854775808", "-9223372036854775808"},
+ });
+
+ expectNoMessages(values, INTEGER, mapper);
+ expectNoMessages(values, INTEGER, mapper, bigIntegerMapper);
+ }
+
+ @Test
+ public void negativeInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"-9223372036854775800", "-9223372036854775855"},
+ {"-9223372036854775808", "-9223372036854775809"},
+ {"-9223372036854775808", new BigDecimal(String.valueOf(-Double.MAX_VALUE)).subtract(BigDecimal.ONE).toString()},
+ {"-9223372036854775807", new BigDecimal(String.valueOf(-Double.MAX_VALUE)).subtract(BigDecimal.ONE).toString()},
+ {"-9223372036854776000", "-9223372036854776001"},
+ });
+ expectSomeMessages(values, INTEGER, mapper, mapper);
+ }
+
+ @Test
+ public void positiveExclusiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"-9223372036854775000", "-9223372036854774988"},
+ {"10", "20"},
+// threshold in outside long range
+ {"-9223372036854775809", "-9223372036854775807"},
+// both threshold and value are outside long range
+ {"-9223372036854775810", "-9223372036854775809"},
+ });
+
+ expectNoMessages(values, EXCLUSIVE_INTEGER, mapper);
+
+ expectNoMessages(values, EXCLUSIVE_INTEGER, bigIntegerMapper);
+ }
+
+ @Test
+ public void negativeExclusiveInteger() throws IOException {
+ String[][] values = augmentWithQuotes(new String[][]{
+// minimum, value
+ {"20", "10"},
+
+// value is outside long range
+ {"-9223372036854775807", "-9223372036854775809"},
+
+// both threshold and value are outside long range
+ {"-9223372036854775809", "-9223372036854775810"},
+ });
+
+ expectSomeMessages(values, EXCLUSIVE_INTEGER, mapper, bigIntegerMapper);
+ }
+
+ @Test
+ public void negativeDoubleOverflowTest() throws IOException {
+ String[][] values = {
+// minimum, value
+ {"-1.79769313486231571E+308", "-1.79769313486231572e+308"},
+// while underflow in not captures in previous case (unquoted number is parsed as double)
+// it is captured if value is passed as string, which is correctly parsed by BidDecimal
+// thus effective comparison is between
+// minimum -1.7976931348623157E+308 and
+// value -1.79769313486231572e+308
+// {"-1.79769313486231571E+308", "\"-1.79769313486231572e+308\""},
+ {"-1.7976931348623157E+309", "-1.7976931348623157e+309"},
+ {"-1.7976931348623157E+309", "\"-1.7976931348623157e+309\""},
+ {"-1.000000000000000000000001E+308", "-1.000000000000000000000001E+308"},
+// Similar to statements above
+// minimum -1.0E+308 and
+// value -1.000000000000000000000001E+308
+// {"-1.000000000000000000000001E+308", "\"-1.000000000000000000000001E+308\""},
+ {"-1.000000000000000000000001E+400", "-1.000000000000000000000001E+401"},
+ {"-1.000000000000000000000001E+400", "\"-1.000000000000000000000001E+401\""},
+ {"-1.000000000000000000000001E+400", "-1.000000000000000000000002E+400"},
+ {"-1.000000000000000000000001E+400", "\"-1.000000000000000000000002E+400\""},
+ {"-1.000000000000000000000001E+400", "-1.0000000000000000000000011E+400"},
+ {"-1.000000000000000000000001E+400", "\"-1.0000000000000000000000011E+400\""},
+ };
+
+ for (String[] aTestCycle : values) {
+ String minimum = aTestCycle[0];
+ String value = aTestCycle[1];
+ String schema = format(NUMBER, minimum);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setTypeLoose(true);
+
+ // Schema and document parsed with just double
+ JsonSchema v = factory.getSchema(mapper.readTree(schema), config);
+ JsonNode doc = mapper.readTree(value);
+ Set<ValidationMessage> 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<ValidationMessage> 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)) {
+ /*
+ * {"-1.000000000000000000000001E+308", "-1.000000000000000000000001E+308"} will be false
+ * because the different between two mappers, without using big decimal, it loses some precises.
+ */
+ assertTrue(messages2.isEmpty(), format("Minimum %s and value %s are equal, thus no schema violation should be reported", minimum, value));
+ } else {
+ assertFalse(messages2.isEmpty(), format("Minimum %s is larger than value %s , should be validation error reported", minimum, value));
+ }
+
+ // schema and document parsed with BigDecimal
+ v = factory.getSchema(bigDecimalMapper.readTree(schema), config);
+ Set<ValidationMessage> 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)) {
+ 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));
+ }
+ }
+ }
+
+ /**
+ * value of -1.7976931348623158e+308 is not converted to NEGATIVE_INFINITY for some reason
+ * the only way to spot this is to use BigDecimal for schema (and for document)
+ */
+ @Test
+ public 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));
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper");
+
+ doc = bigDecimalMapper.readTree(content);
+ messages = v.validate(doc);
+ assertFalse(messages.isEmpty(), "Validation should not succeed because content is using bigDecimalMapper, and smaller than the minimum");
+
+ /*
+ * Note: technically this is where -1.7976931348623158e+308 rounding to -1.7976931348623157e+308 could be
+ * spotted, yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values below
+ * -1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
+ * "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
+ * seems infeasible.
+ */
+ v = factory.getSchema(bigDecimalMapper.readTree(schema));
+ messages = v.validate(doc);
+ assertFalse(messages.isEmpty(), "Validation should not succeed because content is using bigDecimalMapper, and smaller than the minimum");
+ }
+
+ /**
+ * 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 {
+ 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));
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper");
+
+ doc = bigDecimalMapper.readTree(content);
+ messages = v.validate(doc);
+ assertTrue(messages.isEmpty(), "Validation should succeed due to the bug of BigDecimal option of mapper");
+
+ 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");
+ }
+
+ private void expectSomeMessages(String[][] values, String number, ObjectMapper mapper, ObjectMapper mapper2) throws IOException {
+ for (String[] aTestCycle : values) {
+ String minimum = aTestCycle[0];
+ String value = aTestCycle[1];
+ String schema = format(number, minimum);
+
+ JsonSchema v = factory.getSchema(mapper.readTree(schema));
+ JsonNode doc = mapper2.readTree(value);
+
+ Set<ValidationMessage> messages = v.validate(doc);
+ assertFalse(messages.isEmpty(), format(MinimumValidatorTest.NEGATIVE_MESSAGE_TEMPLATE, value, minimum));
+ }
+ }
+
+ private void expectNoMessages(String[][] values, String number, ObjectMapper mapper) throws IOException {
+ expectNoMessages(values, number, mapper, mapper);
+ }
+
+ private void expectNoMessages(String[][] values, String integer, ObjectMapper mapper, ObjectMapper bigIntegerMapper) throws IOException {
+ for (String[] aTestCycle : values) {
+ 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);
+ JsonNode doc = bigIntegerMapper.readTree(value);
+
+ Set<ValidationMessage> 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 0000000..d53edf3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test MultipleOfValidator validator.
+ */
+public 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() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ JsonSchema 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}";
+
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size());
+ assertEquals(3, messages.stream().filter(m -> "multipleOf".equals(m.getType())).count());
+
+ messages = schema.validate(validData, InputFormat.JSON);
+ assertEquals(0, messages.size());
+ }
+
+ @Test
+ void testTypeLoose() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ JsonSchema 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
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size());
+ assertEquals(2, messages.stream().filter(m -> "type".equals(m.getType())).count());
+ assertEquals(1, messages.stream().filter(m -> "multipleOf".equals(m.getType())).count());
+
+ // 2 type errors
+ messages = schema.validate(validTypeLooseInputData, InputFormat.JSON);
+ assertEquals(2, messages.size());
+ assertEquals(2, messages.stream().filter(m -> "type".equals(m.getType())).count());
+
+ // With type loose this has 3 multipleOf errors
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setTypeLoose(true);
+ JsonSchema typeLoose = factory.getSchema(schemaData, config);
+ messages = typeLoose.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size());
+ assertEquals(3, messages.stream().filter(m -> "multipleOf".equals(m.getType())).count());
+
+ // No errors
+ messages = typeLoose.validate(validTypeLooseInputData, InputFormat.JSON);
+ assertEquals(0, messages.size());
+ }
+}
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 0000000..a849be0
--- /dev/null
+++ b/src/test/java/com/networknt/schema/NotAllowedValidatorTest.java
@@ -0,0 +1,25 @@
+package com.networknt.schema;
+
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * 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", ValidatorTypeCode.NOT_ALLOWED);
+ }
+}
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 0000000..f83d7f9
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * OneOfValidatorTest.
+ */
+public 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size()); // even if more than 1 matches the mismatch errors are still reported
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", assertions.get(0).getType());
+ 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).getMessage());
+ }
+
+ @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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(4, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", assertions.get(0).getType());
+ 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).getMessage());
+
+ assertEquals("additionalProperties", assertions.get(1).getType());
+ assertEquals("$", assertions.get(1).getInstanceLocation().toString());
+ assertEquals("$.oneOf[0].additionalProperties", assertions.get(1).getEvaluationPath().toString());
+
+ assertEquals("type", assertions.get(2).getType());
+ assertEquals("$.test", assertions.get(2).getInstanceLocation().toString());
+ assertEquals("$.oneOf[1].additionalProperties.type", assertions.get(2).getEvaluationPath().toString());
+
+ assertEquals("type", assertions.get(3).getType());
+ assertEquals("$.test", assertions.get(3).getInstanceLocation().toString());
+ assertEquals("$.oneOf[2].additionalProperties.type", assertions.get(3).getEvaluationPath().toString());
+ }
+}
diff --git a/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java b/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java
new file mode 100644
index 0000000..157efc3
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OpenAPI30JsonSchemaTest.java
@@ -0,0 +1,90 @@
+package com.networknt.schema;
+
+import java.io.InputStream;
+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 org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class OpenAPI30JsonSchemaTest extends HTTPServiceSupport {
+ protected ObjectMapper mapper = new ObjectMapper();
+ protected JsonSchemaFactory validatorFactory = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).jsonMapper(mapper).build();
+
+ public OpenAPI30JsonSchemaTest() {
+ }
+
+ private void runTestFile(String testCaseFile) throws Exception {
+ final SchemaLocation testCaseFileUri = SchemaLocation.of("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(true);
+ JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config);
+
+ List<ValidationMessage> errors = new ArrayList<ValidationMessage>(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);
+ }
+ }
+ 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);
+ 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 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 0000000..8fbd35b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OutputFormatTest.java
@@ -0,0 +1,50 @@
+package com.networknt.schema;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.InputStream;
+import java.util.Set;
+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;
+
+class OutputFormatTest {
+
+ private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ private static 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);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaInputStream, config);
+ JsonNode node = getJsonNodeFromJsonData("/data/output-format-input.json");
+ Set<ValidationMessage> errors = schema.validate(node);
+ Assertions.assertEquals(3, errors.size());
+
+ Set<String[]> messages = errors.stream().map(m -> new String[] { m.getEvaluationPath().toString(),
+ m.getSchemaLocation().toString(), m.getInstanceLocation().toString(), m.getMessage() })
+ .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"}));
+ }
+}
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 0000000..f613a13
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OutputUnitTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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.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.SpecVersion.VersionFlag;
+import com.networknt.schema.output.OutputUnit;
+import com.networknt.schema.serialization.JsonMapperFactory;
+
+/**
+ * OutputUnitTest.
+ *
+ * @see <a href=
+ * "https://github.com/json-schema-org/json-schema-spec/blob/main/jsonschema-validation-output-machines.md">A
+ * Specification for Machine-Readable Output for JSON Schema Validation and
+ * Annotation</a>
+ */
+public 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 {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ String inputData = inputData1;
+
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(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 {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ String inputData = inputData1;
+
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(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 {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ String inputData = inputData2;
+
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(formatSchema, config);
+ OutputUnit outputUnit = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(formatSchema, config);
+ OutputUnit outputUnit = schema.validate("\"inval!i:d^(abc]\"", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true);
+ executionConfiguration.getExecutionConfig().setFormatAssertionsEnabled(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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(typeSchema, config);
+ OutputUnit outputUnit = schema.validate("1", InputFormat.JSON, OutputFormat.LIST, executionConfiguration -> {
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionConfiguration.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true);
+ });
+ assertFalse(outputUnit.isValid());
+ OutputUnit details = outputUnit.getDetails().get(0);
+ assertNotNull(details.getErrors().get("type"));
+ }
+
+ @Test
+ void unevaluatedProperties() throws JsonProcessingException {
+ Map<String, String> 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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(external)));
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ // 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.getExecutionConfig()
+ .setAnnotationCollectionFilter(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 <a href=
+ * "https://github.com/json-schema-org/json-schema-spec/blob/f8967bcbc6cee27753046f63024b55336a9b1b54/jsonschema-core.md?plain=1#L1717-L1720">anyOf</a>
+ * @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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+
+ String inputData = "{\r\n"
+ + " \"foo\": \"hello\",\r\n"
+ + " \"bar\": 1\r\n"
+ + "}";
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionContext.getExecutionConfig().setAnnotationCollectionFilter(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);
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/OverrideValidatorTest.java b/src/test/java/com/networknt/schema/OverrideValidatorTest.java
new file mode 100644
index 0000000..b5dbcd8
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OverrideValidatorTest.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;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.format.PatternFormat;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OverrideValidatorTest {
+
+ @Test
+ public 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" +
+ "}";
+ final JsonNode targetNode = objectMapper.readTree("{\"mailaddress\": \"a-zA-Z0-9.!#$%&'*+@a---a.a--a\"}");
+ // Use Default EmailValidator
+ final JsonMetaSchema validatorMetaSchema = JsonMetaSchema
+ .builder(URI, JsonMetaSchema.getV201909())
+ .build();
+
+ final JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(validatorMetaSchema).build();
+ final JsonSchema validatorSchema = validatorFactory.getSchema(schema);
+
+ Set<ValidationMessage> messages = validatorSchema.validate(targetNode, OutputFormat.DEFAULT, (executionContext, validationContext) -> {
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ assertEquals(1, messages.size());
+
+ // Override EmailValidator
+ final JsonMetaSchema overrideValidatorMetaSchema = JsonMetaSchema
+ .builder(URI, JsonMetaSchema.getV201909())
+ .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)).metaSchema(overrideValidatorMetaSchema).build();
+ final JsonSchema overrideValidatorSchema = overrideValidatorFactory.getSchema(schema);
+
+ messages = overrideValidatorSchema.validate(targetNode);
+ assertEquals(0, messages.size());
+
+
+ }
+}
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 0000000..a84d4ff
--- /dev/null
+++ b/src/test/java/com/networknt/schema/OverwritingCustomMessageBugTest.java
@@ -0,0 +1,52 @@
+package com.networknt.schema;
+
+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.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class OverwritingCustomMessageBugTest {
+ private JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V7);
+ return factory.getSchema(schemaContent);
+ }
+
+ private JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(content);
+ }
+
+ @Test
+ public void customMessageIsNotOverwritten() throws Exception {
+ Set<ValidationMessage> errors = validate();
+ Map<String, String> 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 Set<ValidationMessage> validate() throws Exception {
+ String schemaPath = "/schema/OverwritingCustomMessageBug.json";
+ String dataPath = "/data/OverwritingCustomMessageBug.json";
+ InputStream schemaInputStream = OverwritingCustomMessageBugTest.class.getResourceAsStream(schemaPath);
+ JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+ InputStream dataInputStream = OverwritingCustomMessageBugTest.class.getResourceAsStream(dataPath);
+ JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+ return schema.validate(node);
+ }
+
+ private Map<String, String> transferErrorMsg(Set<ValidationMessage> validationMessages) {
+ Map<String, String> pathToMessage = new HashMap<>();
+ validationMessages.forEach(msg -> {
+ pathToMessage.put(msg.getInstanceLocation().toString(), msg.getMessage());
+ });
+ return pathToMessage;
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/networknt/schema/PathTypeTest.java b/src/test/java/com/networknt/schema/PathTypeTest.java
new file mode 100644
index 0000000..0f317e2
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PathTypeTest.java
@@ -0,0 +1,49 @@
+package com.networknt.schema;
+
+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/PatternPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
new file mode 100644
index 0000000..22059d4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.SpecVersion.VersionFlag;
+import com.networknt.schema.output.OutputUnit;
+
+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.Set;
+
+/**
+ * Created by steve on 22/10/16.
+ */
+public 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}");
+
+ JsonNode node = getJsonNodeFromStringContent("");
+ Set<ValidationMessage> 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);
+
+ JsonNode node = getJsonNodeFromStringContent("");
+ Set<ValidationMessage> 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "{\n"
+ + " \"valid_array\": [\"array1_value\", \"array2_value\"],\n"
+ + " \"valid_string\": \"string_value\",\n"
+ + " \"valid_key\": 5\n"
+ + "}";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ 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.getMessage());
+ 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "{\n"
+ + " \"test\": 5\n"
+ + "}";
+ OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionContext.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true);
+ });
+ Set<String> patternProperties = (Set<String>) 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.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionContext.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true);
+ });
+ patternProperties = (Set<String>) outputUnit.getAnnotations().get("patternProperties");
+ Set<String> all = new HashSet<>();
+ all.add("valid_array");
+ all.add("valid_string");
+ assertTrue(patternProperties.containsAll(patternProperties));
+ }
+}
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 0000000..b57d822
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java
@@ -0,0 +1,112 @@
+package com.networknt.schema;
+
+import org.junit.jupiter.api.DynamicContainer;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+import java.util.Set;
+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}
+ */
+public class PrefixItemsValidatorTest extends AbstractJsonSchemaTestSuite {
+
+ /**
+ * this method create test cases from JSON and run those test cases with assertion
+ */
+ @Test
+ void testEmptyPrefixItemsException() {
+ Stream<DynamicNode> dynamicNodeStream = createTests(SpecVersion.VersionFlag.V7, "src/test/resources/prefixItemsException");
+ dynamicNodeStream.forEach(
+ dynamicNode -> {
+ assertThrows(JsonSchemaException.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\"}]"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[1, \"x\"]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage message = messages.iterator().next();
+ assertEquals("/prefixItems/type", message.getEvaluationPath().toString());
+ assertEquals("https://www.example.org/schema#/prefixItems/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.getMessage());
+ 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\"}]"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[\"x\", 1, 1]";
+ Set<ValidationMessage> 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "[\"x\", 1, 1, 2]";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ assertNull(message.getProperty());
+ }
+}
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 0000000..aaca8cb
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PropertiesTest.java
@@ -0,0 +1,20 @@
+package com.networknt.schema;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+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")
+public class PropertiesTest extends AbstractJsonSchemaTestSuite {
+
+ @TestFactory
+ @DisplayName("Draft 2019-09")
+ Stream<DynamicNode> draft201909() {
+ return createTests(VersionFlag.V201909, "src/test/resources/draft2019-09/properties.json");
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/PropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PropertiesValidatorTest.java
new file mode 100644
index 0000000..887f6ca
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PropertiesValidatorTest.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;
+
+/**
+ * Created by josejulio on 25/04/22.
+ */
+public class PropertiesValidatorTest extends BaseJsonSchemaValidatorTest {
+
+ @Test
+ public void testDoesNotThrowWhenApplyingDefaultPropertiesToNonObjects() throws Exception {
+ Assertions.assertDoesNotThrow(() -> {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(
+ true,
+ true,
+ true
+ ));
+
+ JsonSchema schema = factory.getSchema("{\"type\":\"object\",\"properties\":{\"foo\":{\"type\":\"object\", \"properties\": {} },\"i-have-default\":{\"type\":\"string\",\"default\":\"foo\"}}}", schemaValidatorsConfig);
+ JsonNode node = getJsonNodeFromStringContent("{\"foo\": \"bar\"}");
+ ValidationResult result = schema.walk(node, true);
+ Assertions.assertEquals(result.getValidationMessages().size(), 1);
+ });
+ }
+}
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 0000000..9b81d4c
--- /dev/null
+++ b/src/test/java/com/networknt/schema/PropertyNamesValidatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * PropertyNamesValidatorTest.
+ */
+public 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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"foo\": {},\r\n"
+ + " \"foobar\": {}\r\n"
+ + "}";
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ ValidationMessage 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.getMessage());
+ assertEquals("foobar", message.getProperty());
+ }
+}
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 0000000..7ac8379
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ReadOnlyValidatorTest.java
@@ -0,0 +1,61 @@
+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.Set;
+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();
+ Set<ValidationMessage> errors = loadJsonSchema(false).validate(node);
+ assertTrue(errors.isEmpty());
+ }
+
+ @Test
+ void givenConfigWriteTrueWhenReadOnlyTrueThenDenies() throws IOException {
+ ObjectNode node = getJsonNode();
+ Set<ValidationMessage> errors = loadJsonSchema(true).validate(node);
+ assertFalse(errors.isEmpty());
+ assertEquals("$.firstName: is a readonly field, it cannot be changed",
+ errors.stream().map(e -> e.getMessage()).collect(Collectors.toList()).get(0));
+ }
+
+ private JsonSchema loadJsonSchema(Boolean write) {
+ JsonSchema schema = this.getJsonSchema(write);
+ schema.initializeValidators();
+ return schema;
+
+ }
+
+ private JsonSchema getJsonSchema(Boolean write) {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ SchemaValidatorsConfig schemaConfig = createSchemaConfig(write);
+ InputStream schema = getClass().getClassLoader().getResourceAsStream("schema/read-only-schema.json");
+ return factory.getSchema(schema, schemaConfig);
+ }
+
+ private SchemaValidatorsConfig createSchemaConfig(Boolean write) {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setReadOnly(write);
+ return config;
+ }
+
+ 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 0000000..1159a57
--- /dev/null
+++ b/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java
@@ -0,0 +1,34 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+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\" }";
+ JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ JsonSchema jsonSchema = jsonSchemaFactory.getSchema(invalidSchemaJson);
+ JsonNode schemaNode = jsonSchema.getSchemaNode();
+ ValidationContext validationContext = new ValidationContext(jsonSchema.getValidationContext().getMetaSchema(),
+ jsonSchemaFactory, null);
+
+ // Act and Assert
+ assertThrows(JsonSchemaException.class, () -> {
+ new RecursiveRefValidator(SchemaLocation.of(""), new JsonNodePath(PathType.JSON_POINTER), schemaNode, null, validationContext);
+ });
+ }
+
+}
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 0000000..6a50457
--- /dev/null
+++ b/src/test/java/com/networknt/schema/RefTest.java
@@ -0,0 +1,69 @@
+package com.networknt.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+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.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+
+public class RefTest {
+ private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder().build();
+
+ @Test
+ void shouldLoadRelativeClasspathReference() throws JsonMappingException, JsonProcessingException {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(SchemaLocation.of("classpath:///schema/ref-main.json"), config);
+ String input = "{\r\n"
+ + " \"DriverProperties\": {\r\n"
+ + " \"CommonProperties\": {\r\n"
+ + " \"field2\": \"abc-def-xyz\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+ assertEquals(SchemaId.V4, schema.getValidationContext().getMetaSchema().getIri());
+ Set<ValidationMessage> errors = schema.validate(OBJECT_MAPPER.readTree(input));
+ assertEquals(1, errors.size());
+ ValidationMessage 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 JsonMappingException, JsonProcessingException {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ JsonSchema schema = factory.getSchema(SchemaLocation.of("classpath:///schema/ref-main-schema-resource.json"), config);
+ String input = "{\r\n"
+ + " \"DriverProperties\": {\r\n"
+ + " \"CommonProperties\": {\r\n"
+ + " \"field2\": \"abc-def-xyz\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+ assertEquals(SchemaId.V4, schema.getValidationContext().getMetaSchema().getIri());
+ Set<ValidationMessage> errors = schema.validate(OBJECT_MAPPER.readTree(input));
+ assertEquals(1, errors.size());
+ ValidationMessage 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());
+ JsonSchema driver = schema.getValidationContext().getSchemaResources().get("https://www.example.org/driver#");
+ JsonSchema common = schema.getValidationContext().getSchemaResources().get("https://www.example.org/common#");
+ assertEquals(SchemaId.V4, driver.getValidationContext().getMetaSchema().getIri());
+ assertEquals(SchemaId.V7, common.getValidationContext().getMetaSchema().getIri());
+
+ }
+}
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 0000000..2630879
--- /dev/null
+++ b/src/test/java/com/networknt/schema/RefValidatorTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.Collections;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Tests for RefValidator.
+ */
+public 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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(
+ Collections.singletonMap("https://www.example.com/schema/integer.json", otherSchema))));
+ JsonSchema jsonSchema = factory.getSchema(mainSchema);
+ Set<ValidationMessage> 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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(
+ Collections.singletonMap("https://www.example.com/schema/integer.json", otherSchema))));
+ JsonSchema jsonSchema = factory.getSchema(mainSchema);
+ Set<ValidationMessage> 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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(
+ Collections.singletonMap("https://www.example.com/integer.json", otherSchema))));
+ JsonSchema jsonSchema = factory.getSchema(mainSchema);
+ Set<ValidationMessage> 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"
+ + "}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(
+ Collections.singletonMap("https://www.example.com/schema/hello/integer.json", otherSchema))));
+ JsonSchema jsonSchema = factory.getSchema(mainSchema);
+ Set<ValidationMessage> messages = jsonSchema.validate("\"string\"", InputFormat.JSON);
+ assertEquals(1, messages.size());
+ }
+}
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 0000000..897f0b2
--- /dev/null
+++ b/src/test/java/com/networknt/schema/SchemaLocationTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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 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 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 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 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());
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/SelfRefTest.java b/src/test/java/com/networknt/schema/SelfRefTest.java
new file mode 100644
index 0000000..feb60e6
--- /dev/null
+++ b/src/test/java/com/networknt/schema/SelfRefTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Created by stevehu on 2016-12-20.
+ */
+public 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");
+ 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 0000000..6173872
--- /dev/null
+++ b/src/test/java/com/networknt/schema/SharedConfigTest.java
@@ -0,0 +1,53 @@
+package com.networknt.schema;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
+
+/**
+ * Issue 918.
+ */
+public class SharedConfigTest {
+ private static class AllKeywordListener implements JsonSchemaWalkListener {
+ public boolean wasCalled = false;
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ wasCalled = true;
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ }
+
+ @Test
+ public void shouldCallAllKeywordListenerOnWalkStart() throws Exception {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+
+ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+ AllKeywordListener allKeywordListener = new AllKeywordListener();
+ schemaValidatorsConfig.addKeywordWalkListener(allKeywordListener);
+
+ 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
+ JsonSchema firstSchema = factory.getSchema(draft07Schema);
+ firstSchema.walk(new ObjectMapper().readTree("{ \"id\": 123 }"), true);
+
+ // note that only second schema takes overridden schemaValidatorsConfig
+ JsonSchema secondSchema = factory.getSchema(draft07Schema, schemaValidatorsConfig);
+
+ secondSchema.walk(new ObjectMapper().readTree("{ \"id\": 123 }"), true);
+ 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
new file mode 100644
index 0000000..2c78042
--- /dev/null
+++ b/src/test/java/com/networknt/schema/SpecVersionDetectorTest.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 SpecVersionDetectorTest {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @ParameterizedTest
+ @CsvSource({
+ "draft4, V4",
+ "draft6, V6",
+ "draft7, V7",
+ "draft2019-09, V201909",
+ "draft2020-12, V202012"
+ })
+ void detectVersion(String resourceDirectory, SpecVersion.VersionFlag expectedFlag) throws IOException {
+ InputStream in = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(resourceDirectory + "/schemaTag.json");
+ JsonNode node = mapper.readTree(in);
+ SpecVersion.VersionFlag flag = SpecVersionDetector.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);
+ JsonSchemaException exception = assertThrows(JsonSchemaException.class, () -> SpecVersionDetector.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<SpecVersion.VersionFlag> flag = SpecVersionDetector.detectOptionalVersion(node, true);
+ assertEquals(Optional.empty(), flag);
+ }
+}
diff --git a/src/test/java/com/networknt/schema/StringCheckerTest.java b/src/test/java/com/networknt/schema/StringCheckerTest.java
new file mode 100755
index 0000000..6113046
--- /dev/null
+++ b/src/test/java/com/networknt/schema/StringCheckerTest.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.Test;
+
+import static com.networknt.schema.utils.StringChecker.isNumeric;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StringCheckerTest {
+
+ 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(isNumeric(validValue), validValue);
+ }
+ }
+
+ @Test
+ void testNonNumericValues() {
+ for (String invalidValue : invalidNumericValues) {
+ assertFalse(isNumeric(invalidValue), invalidValue);
+ }
+ }
+}
diff --git a/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java b/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java
new file mode 100644
index 0000000..aea1ca4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ThresholdMixinPerfTest.java
@@ -0,0 +1,345 @@
+/*
+ * 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
new file mode 100755
index 0000000..5377bc4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/TypeFactoryTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.node.DecimalNode;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+
+import static com.networknt.schema.TypeFactory.getValueNodeType;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class TypeFactoryTest {
+
+ 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 static final String[] validNonIntegralNumberValues = {
+ "1.1", "-1.1", "1.10"
+ };
+
+ private final SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
+
+ @Test
+ public void testIntegralValuesWithJavaSemantics() {
+ schemaValidatorsConfig.setJavaSemantics(true);
+ for (String validValue : validIntegralValues) {
+ assertSame(JsonType.INTEGER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig),
+ validValue);
+ }
+ for (String validValue : validNonIntegralNumberValues) {
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig),
+ validValue);
+ }
+ }
+
+ @Test
+ public void testIntegralValuesWithoutJavaSemantics() {
+ schemaValidatorsConfig.setJavaSemantics(false);
+ for (String validValue : validIntegralValues) {
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig),
+ validValue);
+ }
+ for (String validValue : validNonIntegralNumberValues) {
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig),
+ validValue);
+ }
+ }
+
+
+ @Test
+ public void testWithLosslessNarrowing() {
+ schemaValidatorsConfig.setLosslessNarrowing(true);
+ for (String validValue : validIntegralValues) {
+ assertSame(JsonType.INTEGER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig),
+ validValue);
+
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig),
+ validValue);
+ }
+ }
+
+ @Test
+ public void testWithoutLosslessNarrowing() {
+ schemaValidatorsConfig.setLosslessNarrowing(false);
+ for (String validValue : validIntegralValues) {
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig),
+ validValue);
+
+ assertSame(JsonType.NUMBER,
+ getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig),
+ validValue);
+ }
+
+ }
+}
diff --git a/src/test/java/com/networknt/schema/TypeValidatorTest.java b/src/test/java/com/networknt/schema/TypeValidatorTest.java
new file mode 100644
index 0000000..8560b56
--- /dev/null
+++ b/src/test/java/com/networknt/schema/TypeValidatorTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test TypeValidator validator.
+ */
+public 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() {
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ JsonSchema 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
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(2, messages.size());
+ assertEquals(2, messages.stream().filter(m -> "type".equals(m.getType())).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.getType())).count());
+
+ // With type loose this has 0 type errors as any item can also be interpreted as an array of 1 item
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setTypeLoose(true);
+ JsonSchema typeLoose = factory.getSchema(schemaData, config);
+ 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> 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.
+ * <p>
+ * 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 <a href=
+ * "https://json-schema.org/draft-06/json-schema-release-notes">Draft-06
+ * Release Notes</a>
+ */
+ @Test
+ void integerDraft4() {
+ String schemaData = "{\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V4).getSchema(schemaData);
+ Set<ValidationMessage> 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());
+ }
+}
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 0000000..1b6a923
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java
@@ -0,0 +1,19 @@
+package com.networknt.schema;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+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")
+public class UnevaluatedItemsTest extends AbstractJsonSchemaTestSuite {
+
+ @TestFactory
+ @DisplayName("Draft 2019-09")
+ Stream<DynamicNode> draft201909() {
+ return createTests(VersionFlag.V201909, "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 0000000..982a571
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnevaluatedItemsValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * UnevaluatedItemsValidatorTest.
+ */
+public 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]";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(2, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("unevaluatedItems", assertions.get(0).getType());
+ assertEquals("$", assertions.get(0).getInstanceLocation().toString());
+ assertEquals("$.unevaluatedItems", assertions.get(0).getEvaluationPath().toString());
+ assertEquals("unevaluatedItems", assertions.get(1).getType());
+ 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]";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(2, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("type", assertions.get(0).getType());
+ assertEquals("$[1]", assertions.get(0).getInstanceLocation().toString());
+ assertEquals("$.unevaluatedItems.type", assertions.get(0).getEvaluationPath().toString());
+ assertEquals("type", assertions.get(1).getType());
+ 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 0000000..fae48a7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnevaluatedPropertiesTest.java
@@ -0,0 +1,20 @@
+package com.networknt.schema;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+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")
+public class UnevaluatedPropertiesTest extends AbstractJsonSchemaTestSuite {
+
+ @TestFactory
+ @DisplayName("Draft 2019-09")
+ Stream<DynamicNode> draft201909() {
+ return createTests(VersionFlag.V201909, "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 0000000..447888d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnevaluatedPropertiesValidatorTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * UnevaluatedPropertiesValidatorTest.
+ */
+public 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(2, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("required", assertions.get(0).getType());
+ assertEquals("key1", assertions.get(0).getProperty());
+ assertEquals("unevaluatedProperties", assertions.get(1).getType());
+ 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("additionalProperties", assertions.get(0).getType());
+ 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909).getSchema(schemaData);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ List<ValidationMessage> assertions = messages.stream().collect(Collectors.toList());
+ assertEquals("type", assertions.get(0).getType());
+ assertEquals("$.unevaluatedProperties.type", assertions.get(0).getEvaluationPath().toString());
+ }
+}
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 0000000..bbcee92
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnknownKeywordFactoryTest.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;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import org.junit.jupiter.api.Test;
+
+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
new file mode 100644
index 0000000..06c35e4
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java
@@ -0,0 +1,75 @@
+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.IOException;
+import java.util.Set;
+
+public 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 String json = "{\"data\":1}";
+
+ @Test
+ public void testSchema1() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(this.json);
+
+ JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).jsonMapper(mapper).build();
+ JsonSchema jsonSchema = factory.getSchema(schema1);
+
+ Set<ValidationMessage> errors = jsonSchema.validate(jsonNode);
+ for(ValidationMessage error:errors) {
+ System.out.println(error.getMessage());
+ }
+ }
+
+ @Test
+ public void testSchema2() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(this.json);
+
+ JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).jsonMapper(mapper).build();
+ JsonSchema jsonSchema = factory.getSchema(schema2);
+
+ Set<ValidationMessage> errors = jsonSchema.validate(jsonNode);
+ for(ValidationMessage error:errors) {
+ System.out.println(error.getMessage());
+ }
+ }
+ @Test
+ public void testSchema3() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(this.json);
+
+ JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).jsonMapper(mapper).build();
+ JsonSchema jsonSchema = factory.getSchema(schema3);
+
+ Set<ValidationMessage> errors = jsonSchema.validate(jsonNode);
+ for(ValidationMessage error:errors) {
+ System.out.println(error.getMessage());
+ }
+ }
+
+ @Test
+ public void testNormalize() throws JsonSchemaException {
+
+ 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 = SchemaId.V7;
+
+ Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01));
+ Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02));
+ Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03));
+ Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04));
+
+ }
+}
diff --git a/src/test/java/com/networknt/schema/UriMappingTest.java b/src/test/java/com/networknt/schema/UriMappingTest.java
new file mode 100644
index 0000000..9d6fe61
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UriMappingTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.networknt.schema.JsonSchemaFactory.Builder;
+import com.networknt.schema.resource.MapSchemaMapper;
+import com.networknt.schema.resource.SchemaMapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class UriMappingTest {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ /**
+ * 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 testBuilderUriMappingUri() throws IOException {
+ URL mappings = UriMappingTest.class.getResource("/draft4/extra/uri_mapping/uri-mapping.json");
+ JsonMetaSchema draftV4 = JsonMetaSchema.getV4();
+ Builder builder = JsonSchemaFactory.builder()
+ .defaultMetaSchemaIri(draftV4.getIri())
+ .metaSchema(draftV4)
+ .schemaMappers(schemaMappers -> schemaMappers.add(getUriMappingsFromUrl(mappings)));
+ JsonSchemaFactory instance = builder.build();
+ JsonSchema 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 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
+ * 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);
+ 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.validate(mapper.createObjectNode());
+ fail("Expected exception not thrown");
+ } catch (JsonSchemaException ex) {
+ Throwable cause = ex.getCause();
+ if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) {
+ fail("Unexpected cause for JsonSchemaException", ex);
+ }
+ // passing, so do nothing
+ } catch (Exception ex) {
+ fail("Unexpected exception thrown", ex);
+ }
+ URL mappings = UriMappingTest.class.getResource("/draft4/extra/uri_mapping/invalid-schema-uri.json");
+ JsonMetaSchema draftV4 = JsonMetaSchema.getV4();
+ Builder builder = JsonSchemaFactory.builder()
+ .defaultMetaSchemaIri(draftV4.getIri())
+ .metaSchema(draftV4)
+ .schemaMappers(schemaMappers -> schemaMappers.add(getUriMappingsFromUrl(mappings)));
+ instance = builder.build();
+ JsonSchema schema = instance.getSchema(example);
+ assertEquals(0, schema.validate(mapper.createObjectNode()).size());
+ }
+
+ /**
+ * 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 testValidatorConfigUriMappingUri() throws IOException {
+ URL mappings = UriMappingTest.class.getResource("/draft4/extra/uri_mapping/uri-mapping.json");
+ JsonSchemaFactory instance = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4))
+ .schemaMappers(schemaMappers -> schemaMappers.add(getUriMappingsFromUrl(mappings))).build();
+ JsonSchema 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
+ * valid, but empty schema.
+ *
+ * @throws IOException if unable to parse the mapping file
+ */
+ @Test
+ public void testValidatorConfigExampleMappings() throws IOException {
+ URL mappings = UriMappingTest.class.getResource("/draft4/extra/uri_mapping/invalid-schema-uri.json");
+ JsonSchemaFactory instance = JsonSchemaFactory
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).build();
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ 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.validate(mapper.createObjectNode());
+ fail("Expected exception not thrown");
+ } catch (JsonSchemaException ex) {
+ Throwable cause = ex.getCause();
+ if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) {
+ fail("Unexpected cause for JsonSchemaException");
+ }
+ // passing, so do nothing
+ } catch (Exception ex) {
+ fail("Unexpected exception thrown");
+ }
+ instance = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4))
+ .schemaMappers(schemaMappers -> schemaMappers.add(getUriMappingsFromUrl(mappings))).build();
+ JsonSchema schema = instance.getSchema(example, config);
+ assertEquals(0, schema.validate(mapper.createObjectNode()).size());
+ }
+
+ @Test
+ public void testMappingsForRef() throws IOException {
+ URL mappings = UriMappingTest.class.getResource("/draft4/extra/uri_mapping/schema-with-ref-mapping.json");
+ JsonSchemaFactory instance = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4))
+ .schemaMappers(schemaMappers -> schemaMappers.add(getUriMappingsFromUrl(mappings))).build();
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ JsonSchema schema = instance.getSchema(SchemaLocation.of("resource:draft4/extra/uri_mapping/schema-with-ref.json"),
+ config);
+ assertEquals(0, schema.validate(mapper.readTree("[]")).size());
+ }
+
+ private SchemaMapper getUriMappingsFromUrl(URL url) {
+ HashMap<String, String> map = new HashMap<String, String>();
+ 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 new MapSchemaMapper(map);
+ }
+}
diff --git a/src/test/java/com/networknt/schema/UrnTest.java b/src/test/java/com/networknt/schema/UrnTest.java
new file mode 100644
index 0000000..b074630
--- /dev/null
+++ b/src/test/java/com/networknt/schema/UrnTest.java
@@ -0,0 +1,46 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class UrnTest
+{
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ /**
+ * 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 {
+ InputStream urlTestData = UrnTest.class.getResourceAsStream("/draft7/urn/test.json");
+ 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()
+ .defaultMetaSchemaIri(draftV7.getIri())
+ .metaSchema(draftV7)
+ .schemaMappers(schemaMappers -> schemaMappers.add(value -> AbsoluteIri.of(String.format("resource:draft7/urn/%s.schema.json", value.toString())))
+ );
+ 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();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/networknt/schema/V4JsonSchemaTest.java b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java
new file mode 100644
index 0000000..1fc0102
--- /dev/null
+++ b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.net.URL;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class V4JsonSchemaTest extends HTTPServiceSupport {
+
+ protected ObjectMapper mapper = new ObjectMapper();
+
+ @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);
+ }
+
+ /**
+ * Although, the data file has three errors, but only on is reported
+ */
+ @Test
+ public void testFailFast_AllErrors() throws IOException {
+ Set<ValidationMessage> 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 {
+ Set<ValidationMessage> 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 {
+ Set<ValidationMessage> 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}&lt;{@link ValidationMessage}&gt; must be empty
+ */
+ @Test
+ public void testFailFast_NoErrors() throws IOException {
+ final Set<ValidationMessage> messages = validateFailingFastSchemaFor("extra/product/product.schema.json",
+ "extra/product/product-no-errors-data.json");
+ assertTrue(messages.isEmpty());
+ }
+
+ private Set<ValidationMessage> 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))
+ .jsonMapper(objectMapper)
+ .build()
+ .getSchema(schema, config)
+ .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));
+
+ }
+}
diff --git a/src/test/java/com/networknt/schema/ValidationMessageTest.java b/src/test/java/com/networknt/schema/ValidationMessageTest.java
new file mode 100644
index 0000000..1b3709d
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ValidationMessageTest.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;
+
+/**
+ * ValidationMessageTest.
+ */
+public class ValidationMessageTest {
+ @Test
+ void testSerialization() throws JsonProcessingException {
+ String value = JsonMapperFactory.getInstance()
+ .writeValueAsString(ValidationMessage.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/ValidatorTypeCodeTest.java b/src/test/java/com/networknt/schema/ValidatorTypeCodeTest.java
new file mode 100644
index 0000000..c7d7291
--- /dev/null
+++ b/src/test/java/com/networknt/schema/ValidatorTypeCodeTest.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;
+
+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 testFromValueMissing() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> assertEquals(ValidatorTypeCode.ADDITIONAL_PROPERTIES, ValidatorTypeCode.fromValue("missing")));
+ }
+
+ @Test
+ public void testIfThenElseNotInV4() {
+ List<ValidatorTypeCode> list = ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V4);
+ Assertions.assertFalse(list.contains(ValidatorTypeCode.fromValue("if")));
+ }
+
+ @Test
+ public void testExclusiveMaximumNotInV4() {
+ List<ValidatorTypeCode> list = ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V4);
+ Assertions.assertFalse(list.contains(ValidatorTypeCode.fromValue("exclusiveMaximum")));
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/VocabularyTest.java b/src/test/java/com/networknt/schema/VocabularyTest.java
new file mode 100644
index 0000000..84a9b4f
--- /dev/null
+++ b/src/test/java/com/networknt/schema/VocabularyTest.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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.output.OutputUnit;
+
+/**
+ * Tests for vocabulary support in meta schemas.
+ */
+public 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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections
+ .singletonMap("https://www.example.com/no-validation-no-format/schema",
+ metaSchemaData))))
+ .getSchema(schemaData);
+
+ String inputDataNoValidation = "{\r\n"
+ + " \"numberProperty\": 1\r\n"
+ + "}";
+
+ Set<ValidationMessage> messages = schema.validate(inputDataNoValidation, InputFormat.JSON);
+ assertEquals(0, messages.size());
+
+ // Set validation vocab
+ schema = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections
+ .singletonMap("https://www.example.com/no-validation-no-format/schema",
+ metaSchemaData.replace("https://www.example.com/vocab/validation",
+ Vocabulary.V202012_VALIDATION.getIri())))))
+ .getSchema(schemaData);
+ messages = schema.validate(inputDataNoValidation, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ assertEquals("minimum", messages.iterator().next().getType());
+ }
+
+ @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"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections
+ .singletonMap("https://www.example.com/no-validation-no-format/schema",
+ metaSchemaData))))
+ .getSchema(schemaData);
+
+ String inputDataNoValidation = "{\r\n"
+ + " \"dateProperty\": \"hello\"\r\n"
+ + "}";
+
+ Set<ValidationMessage> messages = schema.validate(inputDataNoValidation, InputFormat.JSON,
+ executionContext -> executionContext.getExecutionConfig().setFormatAssertionsEnabled(true));
+ assertEquals(0, messages.size());
+
+ // Set format assertion vocab
+ schema = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections
+ .singletonMap("https://www.example.com/no-validation-no-format/schema",
+ metaSchemaData.replace("https://www.example.com/vocab/format",
+ Vocabulary.V202012_FORMAT_ASSERTION.getIri())))))
+ .getSchema(schemaData);
+ messages = schema.validate(inputDataNoValidation, InputFormat.JSON);
+ assertEquals(1, messages.size());
+ assertEquals("format", messages.iterator().next().getType());
+ }
+
+ @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"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(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"
+ + "}";
+ VocabularyFactory vocabularyFactory = uri -> {
+ if ("https://www.example.com/vocab/format".equals(uri)) {
+ return new Vocabulary("https://www.example.com/vocab/format", new AnnotationKeyword("hello"));
+ }
+ return null;
+ };
+
+ JsonMetaSchema metaSchema = JsonMetaSchema
+ .builder(JsonMetaSchema.getV202012().getIri(), JsonMetaSchema.getV202012())
+ .vocabularyFactory(vocabularyFactory)
+ .build();
+ JsonSchemaFactory factory = JsonSchemaFactory
+ .getInstance(VersionFlag.V202012,
+ builder -> builder.metaSchema(metaSchema).schemaLoaders(schemaLoaders -> schemaLoaders.schemas(Collections
+ .singletonMap("https://www.example.com/no-validation-no-format/schema",
+ metaSchemaData))));
+ JsonSchema schema = factory.getSchema(schemaData);
+ OutputUnit outputUnit = schema.validate("{}", InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> {
+ executionContext.getExecutionConfig().setAnnotationCollectionEnabled(true);
+ executionContext.getExecutionConfig().setAnnotationCollectionFilter(keyword -> true);
+ });
+ assertNotNull(outputUnit.getAnnotations().get("hello"));
+ }
+}
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 0000000..d8655bf
--- /dev/null
+++ b/src/test/java/com/networknt/schema/format/IriFormatTest.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 static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.ValidationMessage;
+
+class IriFormatTest {
+ @Test
+ void uriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"iri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf\"",
+ InputFormat.JSON);
+ assertTrue(messages.isEmpty());
+ }
+
+ @Test
+ void queryWithBracketsShouldFail() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"iri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"",
+ InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ }
+
+ @Test
+ void iriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"iri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"",
+ InputFormat.JSON);
+ 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 0000000..4bbaeda
--- /dev/null
+++ b/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.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 static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.ValidationMessage;
+
+class IriReferenceFormatTest {
+ @Test
+ void uriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"iri-reference\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"",
+ InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ }
+
+ @Test
+ void iriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"iri-reference\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"",
+ InputFormat.JSON);
+ assertTrue(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 0000000..52a850e
--- /dev/null
+++ b/src/test/java/com/networknt/schema/format/UriFormatTest.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 static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.ValidationMessage;
+
+class UriFormatTest {
+ @Test
+ void uriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf\"",
+ InputFormat.JSON);
+ assertTrue(messages.isEmpty());
+ }
+
+ @Test
+ void queryWithBracketsShouldFail() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"",
+ InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ }
+
+ @Test
+ void iriShouldFail() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"",
+ InputFormat.JSON);
+ assertFalse(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 0000000..787ba65
--- /dev/null
+++ b/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.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 static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.ValidationMessage;
+
+class UriReferenceFormatTest {
+ @Test
+ void uriShouldPass() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri-reference\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf\"",
+ InputFormat.JSON);
+ assertTrue(messages.isEmpty());
+ }
+
+ @Test
+ void queryWithBracketsShouldFail() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri-reference\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/product.pdf?filter[test]=1\"",
+ InputFormat.JSON);
+ assertFalse(messages.isEmpty());
+ }
+
+ @Test
+ void iriShouldFail() {
+ String schemaData = "{\r\n"
+ + " \"format\": \"uri-reference\"\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setFormatAssertionsEnabled(true);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate("\"https://test.com/assets/produktdatenblätter.pdf\"",
+ InputFormat.JSON);
+ assertFalse(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 0000000..d88f06a
--- /dev/null
+++ b/src/test/java/com/networknt/schema/i18n/LocalesTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 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 0000000..aa69109
--- /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(), "item", 5, 10);
+ assertEquals("item: 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("path: must be valid to any of the schemas value", messageSource.getMessage("anyOf", Locale.ROOT, "path", "value"));
+ }
+}
diff --git a/src/test/java/com/networknt/schema/output/OutputUnitDataTest.java b/src/test/java/com/networknt/schema/output/OutputUnitDataTest.java
new file mode 100644
index 0000000..098ba11
--- /dev/null
+++ b/src/test/java/com/networknt/schema/output/OutputUnitDataTest.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.output;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * OutputUnitDataTest.
+ */
+class OutputUnitDataTest {
+
+ @Test
+ void format() {
+ String result = OutputUnitData.formatMessage("hello:");
+ assertEquals("", result);
+ result = OutputUnitData.formatMessage("hello: ");
+ assertEquals("", result);
+ result = OutputUnitData.formatMessage("hello: ");
+ assertEquals("", result);
+ result = OutputUnitData.formatMessage("hello: world");
+ assertEquals("world", result);
+ }
+}
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 0000000..15ab31c
--- /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/resource/MapSchemaLoaderTest.java b/src/test/java/com/networknt/schema/resource/MapSchemaLoaderTest.java
new file mode 100644
index 0000000..a3178d2
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/MapSchemaLoaderTest.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 MapSchemaLoaderTest {
+ public static class Result {
+ private final String schema;
+
+ public Result(String schema) {
+ this.schema = schema;
+ }
+
+ public String getSchema() {
+ return this.schema;
+ }
+ }
+
+ @Test
+ void testMappingsWithTwoFunctions() throws IOException {
+ Map<String, Result> 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"));
+
+ MapSchemaLoader loader = new MapSchemaLoader(mappings::get, Result::getSchema);
+ InputStreamSource source = loader.getSchema(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.getSchema(AbsoluteIri.of("http://www.example.org/not-found.json"));
+ assertNull(result);
+ }
+}
diff --git a/src/test/java/com/networknt/schema/resource/MapSchemaMapperTest.java b/src/test/java/com/networknt/schema/resource/MapSchemaMapperTest.java
new file mode 100644
index 0000000..d9697b6
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/MapSchemaMapperTest.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 MapSchemaMapperTest {
+
+ @Test
+ void predicateMapping() {
+ MapSchemaMapper mapper = new MapSchemaMapper(test -> test.startsWith("http://www.example.org/"),
+ original -> original.replaceFirst("http://www.example.org/", "classpath:"));
+ AbsoluteIri result = mapper.map(AbsoluteIri.of("http://www.example.org/hello"));
+ assertEquals("classpath:hello", result.toString());
+ result = mapper.map(AbsoluteIri.of("notmatchingprefixhttp://www.example.org/hello"));
+ assertNull(result);
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/resource/MetaSchemaMapperTest.java b/src/test/java/com/networknt/schema/resource/MetaSchemaMapperTest.java
new file mode 100644
index 0000000..dd89804
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/MetaSchemaMapperTest.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.SchemaId;
+
+/**
+ * MetaSchemaMapperTest.
+ */
+class MetaSchemaMapperTest {
+
+ enum MapInput {
+ V4(SchemaId.V4),
+ V6(SchemaId.V6),
+ V7(SchemaId.V7),
+ V201909(SchemaId.V201909),
+ V202012(SchemaId.V202012);
+
+ String iri;
+
+ MapInput(String iri) {
+ this.iri = iri;
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(MapInput.class)
+ void map(MapInput input) throws IOException {
+ MetaSchemaMapper mapper = new MetaSchemaMapper();
+ AbsoluteIri result = mapper.map(AbsoluteIri.of(input.iri));
+ ClasspathSchemaLoader loader = new ClasspathSchemaLoader();
+ InputStreamSource source = loader.getSchema(result);
+ assertNotNull(source);
+ try (InputStream inputStream = source.getInputStream()) {
+ inputStream.read();
+ }
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/resource/UriSchemaLoaderTest.java b/src/test/java/com/networknt/schema/resource/UriSchemaLoaderTest.java
new file mode 100644
index 0000000..dc3e3f7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/UriSchemaLoaderTest.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 UriSchemaLoaderTest {
+ /**
+ * 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 {
+ UriSchemaLoader schemaLoader = new UriSchemaLoader();
+ InputStreamSource inputStreamSource = schemaLoader.getSchema(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 {
+ UriSchemaLoader schemaLoader = new UriSchemaLoader();
+ assertDoesNotThrow(() -> schemaLoader.getSchema(AbsoluteIri.of("https://ç§ã®å›£ä½“ã‚‚.jp/")));
+ }
+
+ @Test
+ void shouldThrowRelativeIri() throws IOException {
+ UriSchemaLoader schemaLoader = new UriSchemaLoader();
+ assertThrows(IllegalArgumentException.class, () -> schemaLoader.getSchema(AbsoluteIri.of("ç§ã®å›£ä½“ã‚‚.jp/")));
+ }
+}
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 0000000..f7cdc14
--- /dev/null
+++ b/src/test/java/com/networknt/schema/suite/TestCase.java
@@ -0,0 +1,150 @@
+package com.networknt.schema.suite;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+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
+ */
+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<TestSpec> tests;
+
+ /**
+ * Indicates whether this test-case should be executed
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ private final boolean disabled;
+
+ /**
+ * Describes why this test-case is disabled.
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ 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<TestSpec> 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<TestSpec> 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 0000000..5479edc
--- /dev/null
+++ b/src/test/java/com/networknt/schema/suite/TestSource.java
@@ -0,0 +1,88 @@
+package com.networknt.schema.suite;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+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<List<TestCase>> testCaseType = new TypeReference<List<TestCase>>() { /* intentionally empty */};
+ private static final ObjectMapper mapper = JsonMapperFactory.getInstance();
+
+ /**
+ * Indicates whether this test-source should be executed
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ private final boolean disabled;
+
+ /**
+ * Describes why this test-source is disabled.
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ private final String reason;
+
+ /**
+ * The location of the specification file containing these test-cases.
+ */
+ private final Path path;
+
+ private final List<TestCase> testCases;
+
+ TestSource(Path path, List<TestCase> 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<TestCase> getTestCases() {
+ return null != this.testCases ? this.testCases : Collections.emptyList();
+ }
+
+ public boolean isDisabled() {
+ return this.disabled;
+ }
+
+ public static Optional<TestSource> loadFrom(Path path, boolean disabled, String reason) {
+ try (InputStream in = new FileInputStream(path.toFile())) {
+ List<TestCase> 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 0000000..115ab30
--- /dev/null
+++ b/src/test/java/com/networknt/schema/suite/TestSpec.java
@@ -0,0 +1,269 @@
+package com.networknt.schema.suite;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+
+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}.
+ * <p>
+ * Each validator has its own understanding of what constitutes strict
+ * and permissive.
+ * <p>
+ * This is an extension of the schema used to describe tests in the compliance suite
+ */
+ private final Map<String, Boolean> strictness = new HashMap<>(0);
+
+ /**
+ * The set of validation messages expected from testing data against the schema
+ * <p>
+ * This is an extension of the schema used to describe tests in the compliance suite
+ * </p>
+ */
+ private final Set<String> validationMessages;
+
+ /**
+ * Indicates whether this test should be executed
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ private final boolean disabled;
+
+ /**
+ * Describes why this test is disabled.
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ */
+ 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.
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ * </p>
+ *
+ * @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.
+ * <p>
+ * 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 SchemaValidatorsConfig} with which schema can be validated
+ * <p>
+ * This is an extension of the schema used to describe tests in the
+ * compliance suite
+ */
+ private final Map<String, Object> 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 validationMessages 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<String, Object> config,
+ @JsonProperty("data") JsonNode data,
+ @JsonProperty("valid") boolean valid,
+ @JsonProperty("strictness") Map<String, Boolean> strictness,
+ @JsonProperty("validationMessages") Set<String> validationMessages,
+ @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.validationMessages = validationMessages;
+ 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 SchemaValidatorsConfig} with which schema can be validated
+ */
+ public Map<String, Object> 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<String, Boolean> getStrictness() {
+ return this.strictness;
+ }
+
+ /**
+ * A sequence of validation messages expected from testing data against the schema.
+ * <p>
+ * This is an extension of the schema used to describe tests in the compliance suite
+ * </p>
+ *
+ * @return a non-null list of expected validation messages
+ */
+ public Set<String> getValidationMessages() {
+ return new HashSet<>(null != this.validationMessages ? this.validationMessages : 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.
+ * <p>
+ * This is an extension of the schema used to describe tests in the compliance suite
+ * </p>
+ *
+ * @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 static 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 0000000..088fa87
--- /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/SetViewTest.java b/src/test/java/com/networknt/schema/utils/SetViewTest.java
new file mode 100644
index 0000000..7b039cc
--- /dev/null
+++ b/src/test/java/com/networknt/schema/utils/SetViewTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+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;
+
+/**
+ * Test for SetView.
+ */
+class SetViewTest {
+
+ @Test
+ void testUnion() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ assertEquals(3, view.size());
+ List<Integer> values = view.stream().collect(Collectors.toList());
+ assertEquals(1, values.get(0));
+ assertEquals(2, values.get(1));
+ assertEquals(3, values.get(2));
+ }
+
+ @Test
+ void testToString() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ String value = view.toString();
+ assertEquals("[1, 2, 3]", value);
+ }
+
+ @Test
+ void testIsEmpty() {
+ Set<Integer> a = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+
+ SetView<Integer> view = new SetView<>();
+ assertTrue(view.isEmpty());
+ view.union(a);
+ assertFalse(view.isEmpty());
+ }
+
+ @Test
+ void testEquals() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ assertEquals(3, view.size());
+
+ Set<Integer> result = new HashSet<>();
+ result.add(1);
+ result.add(2);
+ result.add(3);
+ assertEquals(result, view);
+ }
+
+ @Test
+ void testContains() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ assertTrue(view.contains(1));
+ assertTrue(view.contains(2));
+ assertTrue(view.contains(3));
+ assertFalse(view.contains(4));
+ }
+
+ @Test
+ void testContainsAll() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ Set<Integer> result = new HashSet<>();
+ result.add(1);
+ result.add(2);
+ result.add(3);
+ assertTrue(view.containsAll(result));
+ result.add(4);
+ assertFalse(view.containsAll(result));
+ }
+
+ @Test
+ void testToArray() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ assertEquals(3, view.size());
+
+ Object[] result = view.toArray();
+ assertEquals(3, result.length);
+ assertEquals(1, result[0]);
+ assertEquals(2, result[1]);
+ assertEquals(3, result[2]);
+ }
+
+ @Test
+ void testToArrayArray() {
+ Set<Integer> a = new LinkedHashSet<>();
+ Set<Integer> b = new LinkedHashSet<>();
+ Set<Integer> c = new LinkedHashSet<>();
+ a.add(1);
+ a.add(2);
+ c.add(3);
+
+ Set<Integer> view = new SetView<Integer>().union(a).union(b).union(c);
+ assertEquals(3, view.size());
+
+ Integer[] result = view.toArray(new Integer[0]);
+ assertEquals(3, result.length);
+ assertEquals(1, result[0]);
+ assertEquals(2, result[1]);
+ assertEquals(3, result[2]);
+ }
+
+ @Test
+ void testAddAll() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.addAll(Collections.singleton(1)));
+ }
+
+ @Test
+ void testAdd() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.add(1));
+ }
+
+ @Test
+ void testClear() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.clear());
+ }
+
+ @Test
+ void testRemove() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.remove(1));
+ }
+
+ @Test
+ void testRemoveAll() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.removeAll(Collections.singleton(1)));
+ }
+
+ @Test
+ void testRetainAll() {
+ Set<Integer> view = new SetView<>();
+ assertThrows(UnsupportedOperationException.class, () -> view.retainAll(Collections.singleton(1)));
+ }
+
+}
diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
new file mode 100644
index 0000000..d4b6701
--- /dev/null
+++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
@@ -0,0 +1,814 @@
+/*
+ * 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.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+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.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.ApplyDefaultsStrategy;
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.ItemsValidator;
+import com.networknt.schema.ItemsValidator202012;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.JsonSchemaRef;
+import com.networknt.schema.PathType;
+import com.networknt.schema.PropertiesValidator;
+import com.networknt.schema.SchemaId;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.utils.JsonNodes;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.ValidationMessage;
+import com.networknt.schema.ValidationResult;
+import com.networknt.schema.ValidatorTypeCode;
+
+/**
+ * JsonSchemaWalkListenerTest.
+ */
+class JsonSchemaWalkListenerTest {
+
+ @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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>());
+ propertyKeywords.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ 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"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) 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).getSchema().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).getSchema().getEvaluationPath()
+ .append(propertyKeywords.get(1).getKeyword()).toString());
+ assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getSchema().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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> properties = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("properties", key -> new ArrayList<>());
+ properties.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ 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"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> properties = (List<WalkEvent>) 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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>());
+ items.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ 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"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(2, items.size());
+ assertEquals("items", items.get(0).getValidator().getKeyword());
+ assertTrue(items.get(0).getValidator() instanceof ItemsValidator);
+
+ assertEquals("/tags/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(1).getSchema().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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>());
+ items.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ 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"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(2, items.size());
+ assertEquals("items", items.get(0).getValidator().getKeyword());
+ assertTrue(items.get(0).getValidator() instanceof ItemsValidator202012);
+
+ assertEquals("/tags/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString());
+ }
+
+ @Test
+ void draft201909() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>());
+ propertyKeywords.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909)
+ .getSchema(SchemaLocation.of(SchemaId.V201909), config);
+ 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"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().getCollectorMap().get("propertyKeywords");
+
+ assertEquals(28, propertyKeywords.size());
+
+ assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString());
+ assertEquals("/properties", propertyKeywords.get(0).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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).getSchema().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 JsonMappingException, 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void applyDefaultsWithWalker() throws JsonMappingException, 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ 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, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void applyInvalidDefaultsWithWalker() throws JsonMappingException, 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ 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, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString());
+ assertFalse(result.getValidationMessages().isEmpty());
+
+ inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ result = schema.walk(inputNode, false);
+ assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void missingRequired() throws JsonMappingException, 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<String, JsonNode> missingSchemaNode = new LinkedHashMap<>();
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ JsonNode requiredNode = walkEvent.getSchema().getSchemaNode().get("required");
+ List<String> 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();
+ JsonSchema propertySchema = propertiesValidator.getSchemas().get(requiredProperty);
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(propertySchema);
+ if (schemaRef != null) {
+ propertySchema = schemaRef.getSchema();
+ }
+ missingSchemaNode.put(requiredProperty, propertySchema.getSchemaNode());
+ }
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertFalse(result.getValidationMessages().isEmpty());
+ assertEquals("{\"type\":\"integer\"}", missingSchemaNode.get("s").toString());
+ assertEquals("{\"type\":\"string\"}", missingSchemaNode.get("ref").toString());
+ }
+
+ @Test
+ void generateDataWithWalker() throws JsonMappingException, JsonProcessingException {
+ Map<String, Supplier<String>> 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"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = null;
+ do {
+ schemaRef = JsonSchemaRefs.from(schema);
+ 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, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+}
diff --git a/src/test/resources/data/OverwritingCustomMessageBug.json b/src/test/resources/data/OverwritingCustomMessageBug.json
new file mode 100644
index 0000000..4866053
--- /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 0000000..6baf24e
--- /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 0000000..630ddca
--- /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 0000000..b57ce1e
--- /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 0000000..c0b0f30
--- /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/dstTimes.json b/src/test/resources/data/dstTimes.json
new file mode 100644
index 0000000..928eeb6
--- /dev/null
+++ b/src/test/resources/data/dstTimes.json
@@ -0,0 +1,8 @@
+[
+ "2020-03-29T02:00:00Z",
+ "2020-09-27T02:00:00Z",
+ "2020-03-15T02:00:00Z",
+ "2020-03-08T02:00:00Z",
+ "2020-03-27T02:00:00Z",
+ "2020-08-04T02:00:00Z"
+]
diff --git a/src/test/resources/data/issue255.json b/src/test/resources/data/issue255.json
new file mode 100644
index 0000000..ff95551
--- /dev/null
+++ b/src/test/resources/data/issue255.json
@@ -0,0 +1,6 @@
+{
+ "cars": [
+ {
+ }
+ ]
+}
diff --git a/src/test/resources/data/issue295.json b/src/test/resources/data/issue295.json
new file mode 100644
index 0000000..314b66c
--- /dev/null
+++ b/src/test/resources/data/issue295.json
@@ -0,0 +1 @@
+{ "other": "stuff" }
diff --git a/src/test/resources/data/issue313.json b/src/test/resources/data/issue313.json
new file mode 100644
index 0000000..7164016
--- /dev/null
+++ b/src/test/resources/data/issue313.json
@@ -0,0 +1,8 @@
+{
+ "properties": {
+ "field1": {
+ "type": "invalid-type",
+ "description": "string"
+ }
+ }
+}
diff --git a/src/test/resources/data/issue327.json b/src/test/resources/data/issue327.json
new file mode 100644
index 0000000..3fe0812
--- /dev/null
+++ b/src/test/resources/data/issue327.json
@@ -0,0 +1,5 @@
+{
+ "firstName": "John",
+ "lastName": "Doe",
+ "age": 21
+}
diff --git a/src/test/resources/data/issue342.json b/src/test/resources/data/issue342.json
new file mode 100644
index 0000000..4003576
--- /dev/null
+++ b/src/test/resources/data/issue342.json
@@ -0,0 +1,4 @@
+{
+ "a": "ok",
+ "z": "nope"
+}
diff --git a/src/test/resources/data/issue366.json b/src/test/resources/data/issue366.json
new file mode 100644
index 0000000..ec6c912
--- /dev/null
+++ b/src/test/resources/data/issue366.json
@@ -0,0 +1,24 @@
+{
+ "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
+ }
+ ]
+ } \ No newline at end of file
diff --git a/src/test/resources/data/issue375.json b/src/test/resources/data/issue375.json
new file mode 100644
index 0000000..b940e62
--- /dev/null
+++ b/src/test/resources/data/issue375.json
@@ -0,0 +1,10 @@
+{
+ "fields": {
+ "longName123": {
+ "action": "doStuff"
+ },
+ "a": {
+ "action": "doMagic"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/data/issue383.json b/src/test/resources/data/issue383.json
new file mode 100644
index 0000000..7fed0c5
--- /dev/null
+++ b/src/test/resources/data/issue383.json
@@ -0,0 +1,11 @@
+{
+ "validation": {
+ "all": [
+ {
+ "notEmpty": {
+ "field": "test"
+ }
+ }
+ ]
+ }
+}
diff --git a/src/test/resources/data/issue396.json b/src/test/resources/data/issue396.json
new file mode 100644
index 0000000..55c12d8
--- /dev/null
+++ b/src/test/resources/data/issue396.json
@@ -0,0 +1,10 @@
+{
+ "veryveryverylongallowedname": true,
+ "a": true,
+ "x": true,
+ "z": false,
+ "abc": false,
+ "w": false,
+ "ww": false,
+ "WW": true
+}
diff --git a/src/test/resources/data/issue404.json b/src/test/resources/data/issue404.json
new file mode 100644
index 0000000..80ee379
--- /dev/null
+++ b/src/test/resources/data/issue404.json
@@ -0,0 +1,3 @@
+{
+ "bar": 1
+}
diff --git a/src/test/resources/data/issue426.json b/src/test/resources/data/issue426.json
new file mode 100644
index 0000000..a1d6307
--- /dev/null
+++ b/src/test/resources/data/issue426.json
@@ -0,0 +1,9 @@
+{
+ "firstName": {},
+ "foo": [
+ 1,
+ 2,
+ 3,
+ 4
+ ]
+}
diff --git a/src/test/resources/data/issue428.json b/src/test/resources/data/issue428.json
new file mode 100644
index 0000000..c564e82
--- /dev/null
+++ b/src/test/resources/data/issue428.json
@@ -0,0 +1,248 @@
+[
+ {
+ "description": "Nuallable oneOf validation",
+ "schema": {
+ "properties": {
+ "values": {
+ "description": "desc",
+ "nullable": true,
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "oneOf with valid array",
+ "data": {
+ "values": ["test1","test2"]
+ },
+ "valid": true
+ },
+ {
+ "description": "oneOf with invalid type of array",
+ "data": {
+ "values": [1,2]
+ },
+ "valid": true
+ },
+ {
+ "description": "nullable oneOf with null",
+ "data": {
+ "values": null
+ },
+ "valid": true
+ },
+ {
+ "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
+ }
+ ]
+ },
+ {
+ "description": "oneOf with child node nullable validation",
+ "schema": {
+ "properties": {
+ "values": {
+ "description": "desc",
+ "oneOf": [
+ {
+ "type": "array",
+ "nullable": true,
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "string",
+ "nullable": true
+ }
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "nullable oneOf with null value",
+ "data": {
+ "values": null
+ },
+ "valid": true
+ },
+ {
+ "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
+ },
+ {
+ "description": "oneOf with invalid type of array",
+ "data": {
+ "values": [1,2]
+ },
+ "valid": true
+ }
+ ]
+ },
+ {
+ "description": "oneOf with single child node nullable validation",
+ "schema": {
+ "properties": {
+ "values": {
+ "description": "desc",
+ "oneOf": [
+ {
+ "type": "array",
+ "nullable": true,
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "nullable oneOf with null value",
+ "data": {
+ "values": null
+ },
+ "valid": true
+ },
+ {
+ "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
+ },
+ {
+ "description": "oneOf with invalid type of array",
+ "data": {
+ "values": [1,2]
+ },
+ "valid": true
+ }
+ ]
+ },
+ {
+ "description": "Nuallable oneOf with child node nullable validation",
+ "schema": {
+ "properties": {
+ "values": {
+ "description": "desc",
+ "nullable": true,
+ "oneOf": [
+ {
+ "type": "array",
+ "nullable": true,
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "string",
+ "nullable": true
+ }
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "nullable oneOf",
+ "data": {
+ "values": null
+ },
+ "valid": true
+ },
+ {
+ "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
+ },
+ {
+ "description": "oneOf with invalid type of array",
+ "data": {
+ "values": [1,2]
+ },
+ "valid": true
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/data/issue451.json b/src/test/resources/data/issue451.json
new file mode 100644
index 0000000..15bb756
--- /dev/null
+++ b/src/test/resources/data/issue451.json
@@ -0,0 +1,12 @@
+{
+ "allOfAttr" : {
+ "a": "foo",
+ "x": 1,
+ "y": "xx"
+ },
+ "anyOfAttr" : {
+ "a": "bar",
+ "x": 1
+ }
+
+} \ No newline at end of file
diff --git a/src/test/resources/data/issue456-T2.json b/src/test/resources/data/issue456-T2.json
new file mode 100644
index 0000000..b39deea
--- /dev/null
+++ b/src/test/resources/data/issue456-T2.json
@@ -0,0 +1,7 @@
+{
+ "id": "abc",
+ "details": {
+ "__typename": "T2",
+ "name": "Bob"
+ }
+}
diff --git a/src/test/resources/data/issue456-T3.json b/src/test/resources/data/issue456-T3.json
new file mode 100644
index 0000000..a4688b7
--- /dev/null
+++ b/src/test/resources/data/issue456-T3.json
@@ -0,0 +1,7 @@
+{
+ "id": "def",
+ "details": {
+ "__typename": "T3",
+ "description": "Something"
+ }
+}
diff --git a/src/test/resources/data/issue461-v7.json b/src/test/resources/data/issue461-v7.json
new file mode 100644
index 0000000..79d3e20
--- /dev/null
+++ b/src/test/resources/data/issue461-v7.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "address": {
+ "type": "object",
+ "properties": {
+ "street": {
+ "type": "string"
+ },
+ "city": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "street",
+ "city"
+ ]
+ }
+ },
+ "required": [
+ "firstName",
+ "lastName",
+ "address"
+ ]
+}
diff --git a/src/test/resources/data/issue467.json b/src/test/resources/data/issue467.json
new file mode 100644
index 0000000..7be04db
--- /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/issue471.json b/src/test/resources/data/issue471.json
new file mode 100644
index 0000000..a753562
--- /dev/null
+++ b/src/test/resources/data/issue471.json
@@ -0,0 +1,4 @@
+{
+ "pictures": ["url1", "url2", "url3"],
+ "title": "this is a long title"
+}
diff --git a/src/test/resources/data/issue493-invalid-1.json b/src/test/resources/data/issue493-invalid-1.json
new file mode 100644
index 0000000..3ad7b7d
--- /dev/null
+++ b/src/test/resources/data/issue493-invalid-1.json
@@ -0,0 +1,8 @@
+{
+ "parameters": [
+ {
+ "name": "param-required",
+ "value": "wrongtype"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/test/resources/data/issue493-invalid-2.json b/src/test/resources/data/issue493-invalid-2.json
new file mode 100644
index 0000000..970f4f0
--- /dev/null
+++ b/src/test/resources/data/issue493-invalid-2.json
@@ -0,0 +1,12 @@
+{
+ "parameters": [
+ {
+ "name": "param-required",
+ "value": 123
+ },
+ {
+ "name": "param-optional",
+ "value": "wrongtype"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/test/resources/data/issue493-valid-1.json b/src/test/resources/data/issue493-valid-1.json
new file mode 100644
index 0000000..6f852db
--- /dev/null
+++ b/src/test/resources/data/issue493-valid-1.json
@@ -0,0 +1,12 @@
+{
+ "parameters": [
+ {
+ "name": "param-required",
+ "value": 123
+ },
+ {
+ "name": "param-optional",
+ "value": "{{test}}"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/test/resources/data/issue493-valid-2.json b/src/test/resources/data/issue493-valid-2.json
new file mode 100644
index 0000000..6f852db
--- /dev/null
+++ b/src/test/resources/data/issue493-valid-2.json
@@ -0,0 +1,12 @@
+{
+ "parameters": [
+ {
+ "name": "param-required",
+ "value": 123
+ },
+ {
+ "name": "param-optional",
+ "value": "{{test}}"
+ }
+ ]
+} \ 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 0000000..f9ae1d7
--- /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 0000000..2ff89e6
--- /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 0000000..f3ee32f
--- /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 0000000..e6c5267
--- /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 0000000..386edb3
--- /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 0000000..12f9330
--- /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 0000000..c84fe23
--- /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 0000000..1dc70e4
--- /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 0000000..e719030
--- /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 0000000..8bdb210
--- /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 0000000..cee5610
--- /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/data/schemaTag.json b/src/test/resources/data/schemaTag.json
new file mode 100644
index 0000000..5d9042c
--- /dev/null
+++ b/src/test/resources/data/schemaTag.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#"
+} \ No newline at end of file
diff --git a/src/test/resources/data/schemaTagMissing.json b/src/test/resources/data/schemaTagMissing.json
new file mode 100644
index 0000000..f70b911
--- /dev/null
+++ b/src/test/resources/data/schemaTagMissing.json
@@ -0,0 +1,3 @@
+{
+ "description": "Test"
+} \ No newline at end of file
diff --git a/src/test/resources/data/walk-data-default.json b/src/test/resources/data/walk-data-default.json
new file mode 100644
index 0000000..8a52457
--- /dev/null
+++ b/src/test/resources/data/walk-data-default.json
@@ -0,0 +1,12 @@
+{
+ "outer": {
+ "mixedObject": {
+ "intValue_present": 8,
+ "intValue_null": null
+ },
+ "goodArray": ["hello", null],
+ "badArray": ["hello", null],
+ "reference": {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/data/walk-data.json b/src/test/resources/data/walk-data.json
new file mode 100644
index 0000000..528a70c
--- /dev/null
+++ b/src/test/resources/data/walk-data.json
@@ -0,0 +1,11 @@
+{
+ "property1": "sample1",
+ "property2": "sample2",
+ "property3": {
+ "street_address": "test-address",
+ "phone_number": {
+ "country-code": "091",
+ "number": "123456789"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/draft2019-09/dependencies.json b/src/test/resources/draft2019-09/dependencies.json
new file mode 100644
index 0000000..3a750ff
--- /dev/null
+++ b/src/test/resources/draft2019-09/dependencies.json
@@ -0,0 +1,340 @@
+[
+ {
+ "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/draft2019-09/invalid-min-max-contains.json b/src/test/resources/draft2019-09/invalid-min-max-contains.json
new file mode 100644
index 0000000..5bb15f1
--- /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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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/issue255.json b/src/test/resources/draft2019-09/issue255.json
new file mode 100644
index 0000000..66ab4ff
--- /dev/null
+++ b/src/test/resources/draft2019-09/issue255.json
@@ -0,0 +1,87 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "cars": {
+ "type": "array",
+ "items": {
+ "$ref": "#car"
+ }
+ }
+ },
+ "definitions": {
+ "car": {
+ "$id": "#car",
+ "type": "object",
+ "properties": {
+ "model": {
+ "type": "string"
+ },
+ "manufacturer": {
+ "$ref": "#manufacturer"
+ }
+ },
+ "required": [
+ "model",
+ "manufacturer"
+ ]
+ },
+ "manufacturer": {
+ "$id": "#manufacturer",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "factories": {
+ "type": "array",
+ "items": {
+ "$ref": "#factory"
+ }
+ }
+ }
+ },
+ "factory": {
+ "$id": "#factory",
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#location"
+ }
+ }
+ },
+ "location": {
+ "$id": "#location",
+ "type": "object",
+ "properties": {
+ "country": {
+ "type": "string"
+ },
+ "city": {
+ "type": "string"
+ },
+ "employees": {
+ "type": "array",
+ "items": {
+ "$ref": "#employee"
+ }
+ }
+ },
+ "additionalProperties": false
+ },
+ "employee": {
+ "$id": "#employee",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "surname": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true,
+ "maxProperties": 3
+ }
+ }
+}
diff --git a/src/test/resources/draft2019-09/issue375.json b/src/test/resources/draft2019-09/issue375.json
new file mode 100644
index 0000000..66979c0
--- /dev/null
+++ b/src/test/resources/draft2019-09/issue375.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "test",
+ "description": "asdasdint",
+ "type": "object",
+ "properties": {
+ "fields": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[a-zA-Z]+$",
+ "maxLength": 5,
+ "minLength": 3
+ },
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "action"
+ ]
+ }
+ }
+ }
+} \ No newline at end of file
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 0000000..d148830
--- /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
new file mode 100644
index 0000000..6c96d89
--- /dev/null
+++ b/src/test/resources/draft2019-09/properties.json
@@ -0,0 +1,32 @@
+[
+ {
+ "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/schemaTag.json b/src/test/resources/draft2019-09/schemaTag.json
new file mode 100644
index 0000000..d280da0
--- /dev/null
+++ b/src/test/resources/draft2019-09/schemaTag.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema"
+} \ No newline at end of file
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 0000000..4a3ee68
--- /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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$: 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 0000000..a16a372
--- /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 0000000..ca23935
--- /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,
+ "validationMessages": [
+ "$: 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 0000000..1d93f33
--- /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 0000000..5e2402b
--- /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 },
+ "validationMessages": [ "$.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 },
+ "validationMessages": [ "$.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 },
+ "validationMessages": [
+ "$.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 0000000..d22b883
--- /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/complex.json b/src/test/resources/draft4/complex.json
new file mode 100644
index 0000000..97b51ce
--- /dev/null
+++ b/src/test/resources/draft4/complex.json
@@ -0,0 +1,1090 @@
+[
+ {
+ "description": "test on complex schema",
+ "schema": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop249": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "prop398": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop395": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop397": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop389": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop396": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": []
+ }
+ ]
+ },
+ "prop388": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "lockinbenefits": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "freeShipping": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "actualBenefit": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "potentialBenefits": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "eligibleBenefits": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": []
+ }
+ ]
+ },
+ "hasEarlyAccess": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "fastShipping": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "actualBenefit": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "potentialBenefits": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "eligibleBenefits": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": []
+ }
+ ]
+ }
+ },
+ "required": []
+ }
+ ]
+ },
+ "prop248": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop237": {
+ "type": "string"
+ },
+ "prop250": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop389": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop387": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop247": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop252": {
+ "type": "string"
+ },
+ "prop392": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop391": {
+ "type": "string"
+ },
+ "prop390": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "prop391",
+ "prop390"
+ ]
+ }
+ }
+ ]
+ },
+ "prop240": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop241": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop246": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop245": {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ },
+ "prop244": {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ },
+ "prop243": {
+ "maximum": 1.7976931348623157E308,
+ "type": "number",
+ "minimum": -1.7976931348623157E308
+ },
+ "prop242": {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ },
+ "required": [
+ "prop245",
+ "prop244",
+ "prop243",
+ "prop242"
+ ]
+ }
+ ]
+ },
+ "prop385": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop269": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "prop284": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop282": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop283": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop281": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop280": {
+ "type": "string"
+ },
+ "prop279": {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ },
+ "required": [
+ "prop280",
+ "prop279"
+ ]
+ }
+ ]
+ },
+ "shownOnProductPage": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop278": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop277": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 9223372036854775807,
+ "type": "integer",
+ "minimum": -9223372036854775808
+ }
+ ]
+ },
+ "prop276": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "prop276"
+ ]
+ }
+ }
+ ]
+ },
+ "prop267": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop255": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "type": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": []
+ }
+ }
+ ]
+ },
+ "prop271": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "prop270": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "prop272": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "prop274": {
+ "type": "string"
+ },
+ "prop285": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "prop268": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "displayedFinalPrice": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "prop254": {
+ "type": "string"
+ },
+ "prop253": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "showMRP": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "exchangeDetails": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "pincode": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "offerId": {
+ "type": "string"
+ },
+ "prop278": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ }
+ },
+ "required": [
+ "offerId"
+ ]
+ }
+ ]
+ },
+ "prop275": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop277": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "pattern": "^(19[7-9][0-9]|2\\d{3})-((((0[13578])|(1[02]))-(([0-2][0-9])|(3[01])))|(((0[469])|(11))-(([0-2][0-9])|(30)))|(02-?[0-2][0-9]))([tT ]([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?([zZ]|[+-]\\d{2}(:|)\\d{2}|\\b)|\\b)$",
+ "type": "string"
+ }
+ ]
+ },
+ "prop273": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "prop274",
+ "prop254",
+ "prop273"
+ ]
+ }
+ }
+ ]
+ },
+ "prop399": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop393": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "prop239": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "prop238": {
+ "type": "boolean"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalItems": false,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ }
+ },
+ "required": [
+ "prop238"
+ ]
+ }
+ ]
+ },
+ "latestVersion": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "exchangeDetails": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "pincode": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ },
+ "offerId": {
+ "type": "string"
+ },
+ "prop278": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ }
+ },
+ "required": [
+ "offerId"
+ ]
+ }
+ ]
+ },
+ "prop386": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ },
+ "prop251": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "maximum": 2147483647,
+ "type": "integer",
+ "minimum": -2147483648
+ }
+ ]
+ }
+ },
+ "required": [
+ "prop237",
+ "prop252"
+ ]
+ },
+ "tests": [
+ {
+ "description": "test on complex schema",
+ "data": {
+ "prop399": "SADSADSADASDSAD",
+ "prop398": {
+ "prop397": "sdsadsadsad",
+ "prop396": "dsadsadsadsad",
+ "prop395": "Fashiosadsadn",
+ "prop389": "sadsadsdsadas"
+ },
+ "prop393": "sassa-6572-4e94-8c45-9b76adfe567b.BELEEVDNNWHPCFG5",
+ "prop392": [
+ {
+ "prop391": "sadsadsdsadas",
+ "prop390": "sadsadsdsadas"
+ }
+ ],
+ "prop389": "BELEEVDNNWHPCFG5",
+ "prop388": false,
+ "prop387": false,
+ "prop386": false,
+ "prop385": [
+ {
+ "prop285": [
+ "nsddasc17ce07",
+ "nbsds05674208",
+ "nadsda1b76202",
+ "nadada7dbb31",
+ "ndadada7a6be08",
+ "nbadadf4e4dd06"
+ ],
+ "prop284": [
+ {
+ "prop283": false,
+ "prop282": true,
+ "prop281": {
+ "prop280": "AASS",
+ "prop279": 65
+ },
+ "prop278": true,
+ "prop277": 1519064999000,
+ "prop276": "REGULAR"
+ }
+ ],
+ "prop275": "AFDA SDADck",
+ "prop274": "LSTBELEEVDNNWHPCFG5XTR99D",
+ "prop273": "current",
+ "prop272": 599,
+ "prop271": 6,
+ "prop270": 177,
+ "prop269": 112,
+ "prop268": true,
+ "prop267": [
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AAA15c17ce07"
+ },
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AA05674208"
+ },
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AAAAA1b76202"
+ },
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AAAAA67dbb31"
+ },
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AAAAA7a6be08"
+ },
+ {
+ "type": "sadsadsdsadas",
+ "prop255": "AAAAf4e4dd06"
+ }
+ ],
+ "prop254": "AAAADSDSWQDFDFSD44",
+ "prop253": false
+ },
+ {
+ "prop285": [
+ "nSADSA15c17ce07",
+ "nSDSA105674208",
+ "nSDS0081b76202",
+ "nSDSD67dbb31",
+ "nSAFS1d7a6be08",
+ "nbSFDFSAf4e4dd06"
+ ],
+ "prop284": [
+ {
+ "prop283": false,
+ "prop282": true,
+ "prop281": {
+ "prop280": "WHAT",
+ "prop279": 58
+ },
+ "prop278": true,
+ "prop277": 1519151399000,
+ "prop276": "REGULAR"
+ }
+ ],
+ "prop275": "SDADAck",
+ "prop274": "LSTBELEEVDNNWHPCFG5SOSPHT",
+ "prop273": "current",
+ "prop272": 599,
+ "prop271": 6,
+ "prop270": 198,
+ "prop269": 140,
+ "prop268": true,
+ "prop267": [
+ {
+ "type": "DFSFDSFSDFDSFSDFDFDFSDFD",
+ "prop255": "nb:mp:015c17ce07"
+ },
+ {
+ "type": "DSFDSFSDFSDFSDFSDFSDF",
+ "prop255": "nb:mp:0105674208"
+ },
+ {
+ "type": "SFDSFDSFDSFDSFDFSDF",
+ "prop255": "nb:mp:0081b76202"
+ },
+ {
+ "type": "SFDSFSDFFDSFDFSDFDSFFDSDF",
+ "prop255": "nb:mp:00f67dbb31"
+ },
+ {
+ "type": "DSFSDFDFDSFSDFDSFDFSDFSDF",
+ "prop255": "nb:mp:01d7a6be08"
+ },
+ {
+ "type": "DGDGDSGDSGSGSDGSDGSDGSDGSDGSSD",
+ "prop255": "nb:mp:01f4e4dd06"
+ }
+ ],
+ "prop254": "aaaaca4d3acf4b76",
+ "prop253": false
+ },
+ {
+ "prop285": [
+ "dsdsadsadasdsad",
+ "sadsdsadasdsa8",
+ "nsdadsad0081b76202",
+ "sdddsadadsdd",
+ "nasdsadsa08",
+ "nsaddsadsae4dd06"
+ ],
+ "prop284": [
+ {
+ "prop283": false,
+ "prop282": true,
+ "prop281": {
+ "prop280": "ohh",
+ "prop279": 58
+ },
+ "prop278": true,
+ "prop277": 1519064999000,
+ "prop276": "REGULAR"
+ }
+ ],
+ "prop275": "OUT",
+ "prop274": "LSTBELEEVDNNWHPCFG5NHDRPJ",
+ "prop273": "PAST",
+ "prop272": 599,
+ "prop271": 6,
+ "prop270": 207,
+ "prop269": 149,
+ "prop268": true,
+ "prop267": [
+ {
+ "type": "SAFSAFSAFASFASFDASFSDFASD",
+ "prop255": "EDSFdsad05674208"
+ },
+ {
+ "type": "SFAFSAFSADFSAFSAFSAFSSAFSA",
+ "prop255": "nbdsad05674208"
+ },
+ {
+ "type": "SFDSFSFDSFDSFDSFDSFDSFDSAF",
+ "prop255": "nbasdsa1b76202"
+ },
+ {
+ "type": "SFDSFSFSAFSAFSAFSAFSAFSA",
+ "prop255": "nbsa0f67dbb31"
+ },
+ {
+ "type": "DSFDSFDSFDSFSADFDSAFEWRFEWFDSAFSD",
+ "prop255": "nbdasd7a6be08"
+ },
+ {
+ "type": "DFDSFDSFDSFDSDSFDSFSDFDS",
+ "prop255": "nbssads4dd06"
+ }
+ ],
+ "prop254": "a98dbbb8c75e4fe9",
+ "prop253": false
+ },
+ {
+ "prop285": [
+ "SADSASA015c17ce07",
+ "nASDSA0105674208",
+ "nbSADASD0081b76202",
+ "nSADAS0f67dbb31",
+ "nbSADSAd7a6be08",
+ "nbD45545F1f4e4dd06"
+ ],
+ "prop284": [
+ {
+ "prop283": false,
+ "prop282": true,
+ "prop281": {
+ "prop280": "MYPRICE",
+ "prop279": 58
+ },
+ "prop278": true,
+ "prop277": 1519064999000,
+ "prop276": "IRREGULAR"
+ }
+ ],
+ "prop275": "WHAT?",
+ "prop274": "LSTBELEEVDNNWHPCFG5DU2RER",
+ "prop273": "PAST",
+ "prop272": 599,
+ "prop271": 6,
+ "prop270": 278,
+ "prop269": 220,
+ "prop268": true,
+ "prop267": [
+ {
+ "type": "DDSFDSFSDDSADFSFDS",
+ "prop255": "nb:mp:015c17ce07"
+ },
+ {
+ "type": "DFDSFDSFDSFDSFDS",
+ "prop255": "nb:mp:0105674208"
+ },
+ {
+ "type": "DSFDSFDSFDSFDSFDSFDS",
+ "prop255": "DSADASDA0081b76202"
+ },
+ {
+ "type": "DSFDSFDSFDSFSAFFDFDASFASD",
+ "prop255": "DSADSAD00f67dbb31"
+ },
+ {
+ "type": "ADSFDSFDSFDSFDSFDSFDSFDS",
+ "prop255": "nbSDSAmpSASADSADSA01d7a6be08"
+ },
+ {
+ "type": "DSFDSFDSFDSFDSFDSFDS",
+ "prop255": "SADSADSA"
+ }
+ ],
+ "prop254": "10d23E223E324E32e58d43a4648ea",
+ "prop253": false
+ }
+ ],
+ "prop252": "SDSFSD",
+ "prop251": 4,
+ "prop250": false,
+ "prop249": [],
+ "prop248": false,
+ "prop247": false,
+ "prop246": {
+ "prop245": 6,
+ "prop244": 5,
+ "prop243": 3,
+ "prop242": 35
+ },
+ "prop241": "JUNK",
+ "prop240": "NICEVERYNICE",
+ "prop239": {
+ "prop238": false
+ },
+ "prop237": "NICEVERYNICE"
+ },
+ "valid": true
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/draft4/enumObject.json b/src/test/resources/draft4/enumObject.json
new file mode 100644
index 0000000..b7b9929
--- /dev/null
+++ b/src/test/resources/draft4/enumObject.json
@@ -0,0 +1,80 @@
+[
+ {
+ "description": "simple enum object validation",
+ "schema": {
+ "properties": {
+ "foo": {
+ "type": "object",
+ "enum": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "bar": {
+ "$ref": "#/properties/foo"
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "one of the enum is valid",
+ "data": {
+ "bar": 1
+ },
+ "valid": true
+ },
+ {
+ "description": "one of the enum is valid",
+ "isTypeLoose": true,
+ "data": {
+ "bar": "1"
+ },
+ "valid": true
+ },
+ {
+ "description": "something else is invalid",
+ "isTypeLoose": false,
+ "data": {
+ "bar": "1"
+ },
+ "valid": false
+ },
+ {
+ "description": "something else is invalid",
+ "data": {
+ "bar": 4
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "fake enum object validation",
+ "schema": {
+ "type": "object",
+ "enum": [
+ 1,
+ 2,
+ {
+ "key": "value"
+ }
+ ]
+ },
+ "tests": [
+ {
+ "description": "one of the enum is invalid",
+ "data": 1,
+ "valid": false
+ },
+ {
+ "description": "one of the enum is valid",
+ "isTypeLoose": true,
+ "data": {
+ "key": "value"
+ },
+ "valid": true
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/draft4/extra/classpath/schema.json b/src/test/resources/draft4/extra/classpath/schema.json
new file mode 100644
index 0000000..6b783b0
--- /dev/null
+++ b/src/test/resources/draft4/extra/classpath/schema.json
@@ -0,0 +1,31 @@
+[
+ {
+ "description": "Sub schema in classpath",
+ "schema": {
+ "id": "classpath:/draft4/extra/classpath/sub-schema.json",
+ "type": "object",
+ "properties": {
+ "features": {
+ "$ref": "#/features"
+ }
+ },
+ "additionalProperties": false
+ },
+ "tests": [
+ {
+ "description": "an integer is an integer",
+ "data": {
+ "features": 4
+ },
+ "valid": true
+ },
+ {
+ "description": "a number is not an integer",
+ "data": {
+ "features": 4.0
+ },
+ "valid": false
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/classpath/sub-schema.json b/src/test/resources/draft4/extra/classpath/sub-schema.json
new file mode 100644
index 0000000..e545248
--- /dev/null
+++ b/src/test/resources/draft4/extra/classpath/sub-schema.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "features",
+ "features": {
+ "title": "integer feature",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99999
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/cyclic/Element.json b/src/test/resources/draft4/extra/cyclic/Element.json
new file mode 100644
index 0000000..0361c5f
--- /dev/null
+++ b/src/test/resources/draft4/extra/cyclic/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/extra/cyclic/Extension.json b/src/test/resources/draft4/extra/cyclic/Extension.json
new file mode 100644
index 0000000..c1ffb6b
--- /dev/null
+++ b/src/test/resources/draft4/extra/cyclic/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/extra/cyclic/Master.json b/src/test/resources/draft4/extra/cyclic/Master.json
new file mode 100644
index 0000000..01bc4ad
--- /dev/null
+++ b/src/test/resources/draft4/extra/cyclic/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/extra/folder/folderInteger.json b/src/test/resources/draft4/extra/folder/folderInteger.json
new file mode 100644
index 0000000..b03d6ea
--- /dev/null
+++ b/src/test/resources/draft4/extra/folder/folderInteger.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/product/product-all-errors-data.json b/src/test/resources/draft4/extra/product/product-all-errors-data.json
new file mode 100644
index 0000000..f12ec8c
--- /dev/null
+++ b/src/test/resources/draft4/extra/product/product-all-errors-data.json
@@ -0,0 +1,5 @@
+{
+ "id": "it must be integer",
+ "name": "too short",
+ "price": "it should be number"
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/product/product-no-errors-data.json b/src/test/resources/draft4/extra/product/product-no-errors-data.json
new file mode 100644
index 0000000..fd9728f
--- /dev/null
+++ b/src/test/resources/draft4/extra/product/product-no-errors-data.json
@@ -0,0 +1,5 @@
+{
+ "id": 1,
+ "name": "A valid product name",
+ "price": 99.99
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/product/product-one-error-data.json b/src/test/resources/draft4/extra/product/product-one-error-data.json
new file mode 100644
index 0000000..2bc07d6
--- /dev/null
+++ b/src/test/resources/draft4/extra/product/product-one-error-data.json
@@ -0,0 +1,5 @@
+{
+ "id": 786,
+ "name": "too short",
+ "price": 99.99
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/product/product-two-errors-data.json b/src/test/resources/draft4/extra/product/product-two-errors-data.json
new file mode 100644
index 0000000..1320168
--- /dev/null
+++ b/src/test/resources/draft4/extra/product/product-two-errors-data.json
@@ -0,0 +1,5 @@
+{
+ "id": 99.99,
+ "name": "A valid product name",
+ "price": 99.99
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/product/product.schema.json b/src/test/resources/draft4/extra/product/product.schema.json
new file mode 100644
index 0000000..07058e6
--- /dev/null
+++ b/src/test/resources/draft4/extra/product/product.schema.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "A product from Acme's catalog",
+ "title": "Product",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "description": "The unique identifier for a product"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 200,
+ "minLength": 10,
+ "description": "Name of the product"
+ },
+ "price": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "price"
+ ]
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/property.json b/src/test/resources/draft4/extra/property.json
new file mode 100644
index 0000000..c1a5127
--- /dev/null
+++ b/src/test/resources/draft4/extra/property.json
@@ -0,0 +1,37 @@
+[
+ {
+ "description": "id as schema",
+ "schema": {
+ "id": "http://localhost:1234/id_schema/schema/features.json",
+ "title": "property object",
+ "type": "object",
+ "properties": {
+ "featuresInteger": {
+ "$ref": "#/featuresInteger"
+ },
+ "featuresBoolean": {
+ "$ref": "#/featuresBoolean"
+ }
+ },
+ "additionalProperties": false
+ },
+ "tests": [
+ {
+ "description": "an integer is an integer",
+ "data": {
+ "featuresInteger": 4,
+ "featuresBoolean": true
+ },
+ "valid": true
+ },
+ {
+ "description": "a number is not an integer",
+ "data": {
+ "featuresInteger": 4.0,
+ "featuresBoolean": true
+ },
+ "valid": false
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/union_type.json b/src/test/resources/draft4/extra/union_type.json
new file mode 100644
index 0000000..1ced48b
--- /dev/null
+++ b/src/test/resources/draft4/extra/union_type.json
@@ -0,0 +1,95 @@
+[
+ {
+ "description": "test case with union types",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "vars": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": [
+ "string",
+ "number"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "required": [
+ "vars"
+ ]
+ },
+ "tests": [
+ {
+ "description": "an item from valid union types",
+ "data": {
+ "vars": [
+ 232
+ ]
+ },
+ "valid": true
+ },
+ {
+ "description": "an item from valid union types",
+ "data": {
+ "vars": [
+ "test"
+ ]
+ },
+ "valid": true
+ },
+ {
+ "description": "an item other than valid union types",
+ "data": {
+ "vars": [
+ true
+ ]
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "test case with union types",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "vars": {
+ "type": [
+ "string",
+ "number"
+ ]
+ }
+ },
+ "required": [
+ "vars"
+ ]
+ },
+ "tests": [
+ {
+ "description": "an item other than valid union types",
+ "data": {
+ "vars": true
+ },
+ "valid": false
+ },
+ {
+ "description": "an item from valid union types",
+ "data": {
+ "vars": 232
+ },
+ "valid": true
+ },
+ {
+ "description": "an item from valid union types",
+ "data": {
+ "vars": "test"
+ },
+ "valid": true
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/draft4/extra/uri_mapping/example-schema.json b/src/test/resources/draft4/extra/uri_mapping/example-schema.json
new file mode 100644
index 0000000..82eab72
--- /dev/null
+++ b/src/test/resources/draft4/extra/uri_mapping/example-schema.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#"
+}
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
new file mode 100644
index 0000000..59ab161
--- /dev/null
+++ b/src/test/resources/draft4/extra/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:/draft4/extra/uri_mapping/example-schema.json"
+ },
+ {
+ "publicURL": "https://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
new file mode 100644
index 0000000..34e8dfe
--- /dev/null
+++ b/src/test/resources/draft4/extra/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:/draft4/extra/uri_mapping/example-schema.json"
+ }
+]
diff --git a/src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json b/src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json
new file mode 100644
index 0000000..9d8cae3
--- /dev/null
+++ b/src/test/resources/draft4/extra/uri_mapping/schema-with-ref.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "json-object-with-schema",
+ "type": "array",
+ "properties": {
+ "schema": {
+ "$ref": "http://example.com/invalid/schema/url"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/extra/uri_mapping/uri-mapping.json b/src/test/resources/draft4/extra/uri_mapping/uri-mapping.json
new file mode 100644
index 0000000..d452842
--- /dev/null
+++ b/src/test/resources/draft4/extra/uri_mapping/uri-mapping.json
@@ -0,0 +1,10 @@
+[
+ {
+ "publicURL": "http://json-schema.org/draft-04/schema#",
+ "localURL": "resource:/draftv4.schema.json"
+ },
+ {
+ "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"
+ }
+]
diff --git a/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json b/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json
new file mode 100644
index 0000000..3677228
--- /dev/null
+++ b/src/test/resources/draft4/extra/uri_mapping/uri-mapping.schema.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "json-schema-validator-uri-mapping",
+ "type": "array",
+ "description": "Data portion of message from JMRI to client for type \"node\"",
+ "items": {
+ "type": "object",
+ "uniqueItems": true,
+ "properties": {
+ "publicURL": {
+ "type": "string",
+ "description": "Public, presumably internet-accessible, URL for schema"
+ },
+ "localURL": {
+ "type": "string",
+ "description": "Local URL for schema that will be used when a schema references the public URL"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "publicURL",
+ "localURL"
+ ]
+ }
+}
diff --git a/src/test/resources/draft4/extra/uuid.json b/src/test/resources/draft4/extra/uuid.json
new file mode 100644
index 0000000..59db2ee
--- /dev/null
+++ b/src/test/resources/draft4/extra/uuid.json
@@ -0,0 +1,30 @@
+[
+ {
+ "description": "uuid format validation",
+ "schema": {
+ "format": "uuid"
+ },
+ "tests": [
+ {
+ "description": "invalid uuid",
+ "data": "df984f5c-59bd-48e3sac87-b03d574b37e9",
+ "valid": false
+ },
+ {
+ "description": "valid uuid string",
+ "data": "df984f5c-59bd-48e3-ac87-b03d574b37e9",
+ "valid": true
+ },
+ {
+ "description": "too short, it is invalid",
+ "data": "5fc03087d-d265-11e7-b8c6-83e29cd24f",
+ "valid": false
+ },
+ {
+ "description": "two long, length is greater than 36",
+ "data": "5fc03087d-d265-11e7-b8c6-83e29cd24f42333",
+ "valid": false
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/draft4/idRef.json b/src/test/resources/draft4/idRef.json
new file mode 100644
index 0000000..7bc0361
--- /dev/null
+++ b/src/test/resources/draft4/idRef.json
@@ -0,0 +1,53 @@
+[
+ {
+ "description": "refer to subschema by a unique name ($id)",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "cars": {
+ "type": "array",
+ "items": {
+ "$ref": "#car"
+ }
+ }
+ },
+ "definitions": {
+ "car": {
+ "id": "#car",
+ "type": "object",
+ "properties": {
+ "model": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "model"
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "invalid when element referenced by id is invalid",
+ "data": {
+ "cars": [
+ {
+ }
+ ]
+ },
+ "valid": false
+ },
+ {
+ "description": "valid when element referenced by id is valid",
+ "data": {
+ "cars": [
+ {
+ "model": "BMW"
+ }
+ ]
+ },
+ "valid": true
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/draft4/integer.json b/src/test/resources/draft4/integer.json
new file mode 100644
index 0000000..7ee25af
--- /dev/null
+++ b/src/test/resources/draft4/integer.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/issue258/Element.json b/src/test/resources/draft4/issue258/Element.json
new file mode 100644
index 0000000..0361c5f
--- /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 0000000..c1ffb6b
--- /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 0000000..01bc4ad
--- /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 0000000..70e18fe
--- /dev/null
+++ b/src/test/resources/draft4/issue425.json
@@ -0,0 +1,52 @@
+[
+ {
+ "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": false,
+ "validationMessages": [
+ "$.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/refRemoteSchema.json b/src/test/resources/draft4/refRemoteSchema.json
new file mode 100644
index 0000000..1c277cf
--- /dev/null
+++ b/src/test/resources/draft4/refRemoteSchema.json
@@ -0,0 +1,3 @@
+{
+ "$ref": "subSchemas.json#/integer"
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/relativeRefRemote.json b/src/test/resources/draft4/relativeRefRemote.json
new file mode 100644
index 0000000..00df5ae
--- /dev/null
+++ b/src/test/resources/draft4/relativeRefRemote.json
@@ -0,0 +1,123 @@
+[
+ {
+ "description": "remote ref",
+ "schema": {
+ "$ref": "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": "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": "refRemoteSchema.json"
+ },
+ "tests": [
+ {
+ "description": "ref within ref valid",
+ "data": 1,
+ "valid": true
+ },
+ {
+ "description": "ref within ref invalid",
+ "data": "a",
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "change resolution scope",
+ "schema": {
+ "items": {
+ "id": "extra/folder/",
+ "items": {
+ "$ref": "folderInteger.json"
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "changed scope ref valid",
+ "data": [
+ [
+ 1
+ ]
+ ],
+ "valid": true
+ },
+ {
+ "description": "changed scope ref invalid",
+ "data": [
+ [
+ "a"
+ ]
+ ],
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "sub directory reference with ref",
+ "schema": {
+ "$ref": "self_ref/../subSchemas.json#/integer"
+ },
+ "tests": [
+ {
+ "description": "remote fragment valid",
+ "data": 1,
+ "valid": true
+ },
+ {
+ "description": "remote fragment invalid",
+ "data": "a",
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "domain reference with ref",
+ "schema": {
+ "$ref": "/draft4/integer.json"
+ },
+ "tests": [
+ {
+ "description": "remote ref valid",
+ "data": 1,
+ "valid": true
+ },
+ {
+ "description": "remote ref invalid",
+ "data": "a",
+ "valid": false
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/draft4/schemaTag.json b/src/test/resources/draft4/schemaTag.json
new file mode 100644
index 0000000..1077e82
--- /dev/null
+++ b/src/test/resources/draft4/schemaTag.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#"
+} \ No newline at end of file
diff --git a/src/test/resources/draft4/subSchemas.json b/src/test/resources/draft4/subSchemas.json
new file mode 100644
index 0000000..5111fb6
--- /dev/null
+++ b/src/test/resources/draft4/subSchemas.json
@@ -0,0 +1,13 @@
+{
+ "integer": {
+ "type": "integer"
+ },
+ "refToInteger": {
+ "$ref": "#/integer"
+ },
+ "definitions": {
+ "a": {
+ "type": "integer"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/draft6/idRef.json b/src/test/resources/draft6/idRef.json
new file mode 100644
index 0000000..37a3174
--- /dev/null
+++ b/src/test/resources/draft6/idRef.json
@@ -0,0 +1,53 @@
+[
+ {
+ "description": "refer to subschema by a unique name ($id)",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "cars": {
+ "type": "array",
+ "items": {
+ "$ref": "#car"
+ }
+ }
+ },
+ "definitions": {
+ "car": {
+ "$id": "#car",
+ "type": "object",
+ "properties": {
+ "model": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "model"
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "invalid when element referenced by id is invalid",
+ "data": {
+ "cars": [
+ {
+ }
+ ]
+ },
+ "valid": false
+ },
+ {
+ "description": "valid when element referenced by id is valid",
+ "data": {
+ "cars": [
+ {
+ "model": "BMW"
+ }
+ ]
+ },
+ "valid": true
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/draft6/schemaTag.json b/src/test/resources/draft6/schemaTag.json
new file mode 100644
index 0000000..76c0c23
--- /dev/null
+++ b/src/test/resources/draft6/schemaTag.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#"
+} \ No newline at end of file
diff --git a/src/test/resources/draft7/idRef.json b/src/test/resources/draft7/idRef.json
new file mode 100644
index 0000000..37a3174
--- /dev/null
+++ b/src/test/resources/draft7/idRef.json
@@ -0,0 +1,53 @@
+[
+ {
+ "description": "refer to subschema by a unique name ($id)",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "cars": {
+ "type": "array",
+ "items": {
+ "$ref": "#car"
+ }
+ }
+ },
+ "definitions": {
+ "car": {
+ "$id": "#car",
+ "type": "object",
+ "properties": {
+ "model": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "model"
+ ]
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "invalid when element referenced by id is invalid",
+ "data": {
+ "cars": [
+ {
+ }
+ ]
+ },
+ "valid": false
+ },
+ {
+ "description": "valid when element referenced by id is valid",
+ "data": {
+ "cars": [
+ {
+ "model": "BMW"
+ }
+ ]
+ },
+ "valid": true
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/draft7/issue386.json b/src/test/resources/draft7/issue386.json
new file mode 100644
index 0000000..c1281ad
--- /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 0000000..8dbbfa6
--- /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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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 0000000..b18463d
--- /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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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 0000000..f7a9de4
--- /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,
+ "validationMessages": [
+ "$.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 0000000..2cf9b57
--- /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 0000000..2eb430c
--- /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 0000000..4ee9e29
--- /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,
+ "validationMessages": [
+ "$.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 0000000..b220968
--- /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,
+ "validationMessages": [
+ "$.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/schemaTag.json b/src/test/resources/draft7/schemaTag.json
new file mode 100644
index 0000000..f00c697
--- /dev/null
+++ b/src/test/resources/draft7/schemaTag.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#"
+} \ No newline at end of file
diff --git a/src/test/resources/draft7/urn/account.schema.json b/src/test/resources/draft7/urn/account.schema.json
new file mode 100644
index 0000000..9a726e3
--- /dev/null
+++ b/src/test/resources/draft7/urn/account.schema.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "account",
+ "type": "object",
+ "properties": {
+ "first-name": {
+ "type": "string"
+ },
+ "age": {
+ "type": "integer"
+ }
+ }
+}
diff --git a/src/test/resources/draft7/urn/issue665.json b/src/test/resources/draft7/urn/issue665.json
new file mode 100644
index 0000000..57a3bf8
--- /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 0000000..2929686
--- /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 0000000..026f879
--- /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/draft7/urn/test.json b/src/test/resources/draft7/urn/test.json
new file mode 100644
index 0000000..450460f
--- /dev/null
+++ b/src/test/resources/draft7/urn/test.json
@@ -0,0 +1,6 @@
+{
+ "account": {
+ "first-name": "Test",
+ "age": 18
+ }
+}
diff --git a/src/test/resources/draft7/urn/urn.schema.json b/src/test/resources/draft7/urn/urn.schema.json
new file mode 100644
index 0000000..33c8828
--- /dev/null
+++ b/src/test/resources/draft7/urn/urn.schema.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "schema-with-urn-ref",
+ "type": "object",
+ "properties": {
+ "account": {
+ "$ref": "account"
+ }
+ }
+}
diff --git a/src/test/resources/issue686/translations.properties b/src/test/resources/issue686/translations.properties
new file mode 100644
index 0000000..4dfc094
--- /dev/null
+++ b/src/test/resources/issue686/translations.properties
@@ -0,0 +1 @@
+type = {0}: {1} found, {2} 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 0000000..f9df244
--- /dev/null
+++ b/src/test/resources/issue686/translations_de.properties
@@ -0,0 +1 @@
+type = {0}: {1} found, {2} 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 0000000..d627e45
--- /dev/null
+++ b/src/test/resources/issue686/translations_fr.properties
@@ -0,0 +1 @@
+type = {0}: {1} found, {2} expected (TEST) (FR)
diff --git a/src/test/resources/issue784/schema.json b/src/test/resources/issue784/schema.json
new file mode 100644
index 0000000..38d5a2d
--- /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/662/emptyObject.json b/src/test/resources/issues/662/emptyObject.json
new file mode 100644
index 0000000..0967ef4
--- /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 0000000..2245430
--- /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 0000000..3155e0f
--- /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 0000000..ce72d61
--- /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 0000000..a9794e6
--- /dev/null
+++ b/src/test/resources/jsv-messages-override.properties
@@ -0,0 +1 @@
+allOf = {0}: overridden message {1}
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..d61b04f
--- /dev/null
+++ b/src/test/resources/logback-test.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="ERROR">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
diff --git a/src/test/resources/multipleOfScale.json b/src/test/resources/multipleOfScale.json
new file mode 100644
index 0000000..2647e66
--- /dev/null
+++ b/src/test/resources/multipleOfScale.json
@@ -0,0 +1,30 @@
+[
+ {
+ "description": "by deep scale",
+ "schema": {
+ "multipleOf": 0.25
+ },
+ "tests": [
+ {
+ "description": "8.75 is multiple of 0.25",
+ "data": 8.75,
+ "valid": true
+ },
+ {
+ "description": "8.750000000001 is not multiple of 0.25",
+ "data": 8.750000000001,
+ "valid": false
+ },
+ {
+ "description": "8.7500000000001 is not multiple of 0.25",
+ "data": 8.7500000000001,
+ "valid": false
+ },
+ {
+ "description": "8.75000000000001 is not multiple of 0.25",
+ "data": 8.75000000000001,
+ "valid": false
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/oas/v31/dialect/base b/src/test/resources/oas/v31/dialect/base
new file mode 100644
index 0000000..eae8386
--- /dev/null
+++ b/src/test/resources/oas/v31/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/v31/meta/base b/src/test/resources/oas/v31/meta/base
new file mode 100644
index 0000000..a7a59f1
--- /dev/null
+++ b/src/test/resources/oas/v31/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/v31/schema-base/2022-10-07 b/src/test/resources/oas/v31/schema-base/2022-10-07
new file mode 100644
index 0000000..752e98b
--- /dev/null
+++ b/src/test/resources/oas/v31/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/v31/schema/2022-10-07 b/src/test/resources/oas/v31/schema/2022-10-07
new file mode 100644
index 0000000..468bc7e
--- /dev/null
+++ b/src/test/resources/oas/v31/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/discriminator.json b/src/test/resources/openapi3/discriminator.json
new file mode 100644
index 0000000..6d55ece
--- /dev/null
+++ b/src/test/resources/openapi3/discriminator.json
@@ -0,0 +1,592 @@
+[
+ {
+ "description": "discriminator with anyOf/allOf",
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ },
+ {
+ "$ref": "#/components/schemas/KidsBedRoom"
+ },
+ {
+ "$ref": "#/components/schemas/Kitchen"
+ },
+ {
+ "$ref": "#/components/schemas/GuestRoom"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type",
+ "mapping": {
+ "bed": "#/components/schemas/BedRoom"
+ }
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ],
+ "discriminator": {
+ "mapping": {
+ "guest": "#/components/schemas/GuestRoom"
+ }
+ }
+ },
+ "KidsBedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "isTidy": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "isTidy"
+ ]
+ }
+ ]
+ },
+ "GuestRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "guest": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "guest"
+ ]
+ }
+ ]
+ },
+ "Kitchen": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "hasMicrowaveOven": {
+ "type": "boolean"
+ },
+ "equipment": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Pot"
+ },
+ {
+ "$ref": "#/components/schemas/Blender"
+ }
+ ]
+ }
+ }
+ },
+ "required": [
+ "hasMicrowaveOven"
+ ]
+ }
+ ]
+ },
+ "KitchenEquipment": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type"
+ }
+ },
+ "Pot": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/KitchenEquipment"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "capacity": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "capacity"
+ ]
+ }
+ ]
+ },
+ "Blender": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/KitchenEquipment"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "maxSpeed": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "maxSpeed"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "mapping to base type",
+ "data": {
+ "@type": "Room"
+ },
+ "valid": true
+ },
+ {
+ "description": "mapped to Bedroom",
+ "data": {
+ "@type": "bed",
+ "numberOfBeds": 2
+ },
+ "valid": true
+ },
+ {
+ "description": "mapped to Kitchen",
+ "data": {
+ "@type": "Kitchen",
+ "hasMicrowaveOven": true,
+ "equipment": [
+ {
+ "@type": "Blender",
+ "maxSpeed": 1500
+ },
+ {
+ "@type": "Pot",
+ "capacity": 5
+ }
+ ]
+ },
+ "valid": true
+ },
+ {
+ "description": "mapped to KidsBedRoom",
+ "data": {
+ "@type": "KidsBedRoom",
+ "numberOfBeds": 1,
+ "isTidy": false
+ },
+ "valid": true
+ },
+ {
+ "description": "mapped to Bedroom with missing number of beds",
+ "data": {
+ "@type": "bed"
+ },
+ "valid": false
+ },
+ {
+ "description": "mapped to KidsBedroom with missing number of beds",
+ "data": {
+ "@type": "KidsBedRoom",
+ "isTidy": true
+ },
+ "valid": false
+ },
+ {
+ "description": "mapped to KidsBedroom with missing tidiness",
+ "data": {
+ "@type": "KidsBedRoom",
+ "numberOfBeds": 1
+ },
+ "valid": false
+ },
+ {
+ "description": "mapped to GuestRoom with correct @type (mapping override on BedRoom)",
+ "data": {
+ "@type": "guest",
+ "numberOfBeds": 9,
+ "guest": "Steve"
+ },
+ "valid": true
+ },
+ {
+ "description": "mapped to GuestRoom with incorrect @type (mapping override on BedRoom)",
+ "data": {
+ "@type": "GuestRoom",
+ "guest": "Steve"
+ },
+ "valid": false
+ },
+ {
+ "description": "mapped to invalid Room",
+ "data": {
+ "@type": "Bathroom"
+ },
+ "valid": false
+ },
+ {
+ "description": "discriminator missing - falling back to default validation with failure as @type is mandatory",
+ "data": {
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "absent discriminator value as discriminator marked as non-mandatory (which is against OAS3 recommendations)",
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "$ref": "#/components/schemas/BedRoom"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "intOrStringType": {
+ "type": "integer"
+ }
+ },
+ "discriminator": {
+ "propertyName": "@type",
+ "mapping": {
+ "bed": "#/components/schemas/BedRoom"
+ }
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "intOrStringType": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "discriminator missing - falling back to default anyOf behavior",
+ "data": {
+ "intOrStringType": 4
+ },
+ "valid": true
+ },
+ {
+ "description": "discriminator missing - with constructed schema conflict",
+ "data": {
+ "intOrStringType": "cannot match due to allOf"
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Issue #398 - $ref pointing to base type with discriminator and w/o anyOf",
+ "schema": {
+ "$ref": "#/components/schemas/Room",
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ },
+ "nextRoom": {
+ "anyOf": [
+ {"$ref":"#/components/schemas/Room" },
+ {"$ref":"#/components/schemas/BedRoom" }
+ ]
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type"
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "$ref without anyOf and therefore no discriminator context",
+ "data": {
+ "@type": "Room"
+ },
+ "valid": true
+ },
+ {
+ "description": "schema with discriminator and recursion with invalid BedRoom",
+ "data": {
+ "@type": "can be ignored - discriminator not in use on root schema",
+ "numberOfBeds": 42,
+ "nextRoom": {
+ "@type": "BedRoom",
+ "floor": 1
+ }
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Issue #398 - $ref pointing to extended type with discriminator and w/o anyOf",
+ "schema": {
+ "$ref": "#/components/schemas/BedRoom",
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ },
+ "nextRoom": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Room"},
+ {"$ref": "#/components/schemas/BedRoom"}
+ ]
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type"
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ },
+ "nextRoom": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Room"},
+ {"$ref": "#/components/schemas/BedRoom"}
+ ]
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "$ref without anyOf and therefore no discriminator context",
+ "data": {
+ "@type": "can be ignored - discriminator not in use on root schema",
+ "numberOfBeds": 42,
+ "nextRoom": {
+ "@type": "Room",
+ "floor": 3
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "schema with discriminator and recursion with valid BedRoom",
+ "data": {
+ "@type": "can be ignored - discriminator not in use on root schema",
+ "numberOfBeds": 42,
+ "nextRoom": {
+ "@type": "BedRoom",
+ "floor": 1,
+ "numberOfBeds": 12345
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "schema with discriminator and recursion with invalid BedRoom",
+ "data": {
+ "@type": "can be ignored - discriminator not in use on root schema",
+ "numberOfBeds": 42,
+ "nextRoom": {
+ "@type": "BedRoom",
+ "floor": 1
+ }
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Issue #398 - invalid discriminator specification has to lead to failed validation",
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Room"},
+ {"$ref": "#/components/schemas/BedRoom"}
+ ],
+ "components": {
+ "schemas": {
+ "Room": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string"
+ },
+ "floor": {
+ "type": "integer"
+ },
+ "nextRoom": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Room"},
+ {"$ref": "#/components/schemas/BedRoom"}
+ ]
+ }
+ },
+ "required": [
+ "@type"
+ ],
+ "discriminator": {
+ "propertyName": "@type"
+ }
+ },
+ "BedRoom": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Room"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "numberOfBeds": {
+ "type": "integer"
+ },
+ "nextRoom": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Room"},
+ {"$ref": "#/components/schemas/BedRoom"}
+ ]
+ }
+ },
+ "required": [
+ "numberOfBeds"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "$ref without anyOf and therefore no discriminator context",
+ "data": {
+ "@type": "illegal discriminator property value"
+ },
+ "valid": false
+ }
+ ]
+ }
+]
diff --git a/src/test/resources/prefixItemsException/prefixItemsException.json b/src/test/resources/prefixItemsException/prefixItemsException.json
new file mode 100644
index 0000000..cc9c1f0
--- /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 0000000..6941769
--- /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/folder/folderInteger.json b/src/test/resources/remotes/folder/folderInteger.json
new file mode 100644
index 0000000..782f200
--- /dev/null
+++ b/src/test/resources/remotes/folder/folderInteger.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+}
diff --git a/src/test/resources/remotes/id_schema/property.json b/src/test/resources/remotes/id_schema/property.json
new file mode 100644
index 0000000..a1069a6
--- /dev/null
+++ b/src/test/resources/remotes/id_schema/property.json
@@ -0,0 +1,35 @@
+[
+ {
+ "description": "id as schema",
+ "schema": {
+ "id": "http://localhost:1234/id_schema/schema/features.json",
+ "title": "property object",
+ "type": "object",
+ "properties": {
+ "featuresInteger": {
+ "$ref": "#/featuresInteger"
+ },
+ "featuresBoolean": {
+ "$ref": "#/featuresBoolean"
+ }
+ },
+ "additionalProperties": false
+ },
+ "tests": [
+ {
+ "data": {
+ "featuresInteger": 4,
+ "featuresBoolean": true
+ },
+ "valid": true
+ },
+ {
+ "data": {
+ "featuresInteger": 4.0,
+ "featuresBoolean": true
+ },
+ "valid": false
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/remotes/id_schema/schema/features.json b/src/test/resources/remotes/id_schema/schema/features.json
new file mode 100644
index 0000000..c3b12b8
--- /dev/null
+++ b/src/test/resources/remotes/id_schema/schema/features.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "features fields",
+ "featuresBoolean": {
+ "title": "boolean feature",
+ "type": "boolean"
+ },
+ "featuresInteger": {
+ "title": "integer feature",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99999
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/remotes/self_ref/selfRef.json b/src/test/resources/remotes/self_ref/selfRef.json
new file mode 100644
index 0000000..10b45dd
--- /dev/null
+++ b/src/test/resources/remotes/self_ref/selfRef.json
@@ -0,0 +1,4 @@
+{
+ "id": "http://localhost:1234/self_ref/selfRef.json",
+ "description": "Schema with ID set to its own URL"
+}
diff --git a/src/test/resources/schema/OverwritingCustomMessageBug.json b/src/test/resources/schema/OverwritingCustomMessageBug.json
new file mode 100644
index 0000000..6ead21b
--- /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": "{0}: Must be a string with the a shape foofoofoofoo... with at least one foo"
+ }
+ },
+ "Nope": {
+ "type": "string"
+ },
+ "bars": {
+ "type": "string",
+ "pattern": "(bar)+",
+ "message": {
+ "pattern": "{0}: 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/contains/issue769/max-contains-v7.json b/src/test/resources/schema/contains/issue769/max-contains-v7.json
new file mode 100644
index 0000000..7a31642
--- /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 0000000..bda4eb4
--- /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 0000000..e935425
--- /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 0000000..f8a4971
--- /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 0000000..9af1c53
--- /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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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 0000000..1c3e906
--- /dev/null
+++ b/src/test/resources/schema/customMessageTests/custom-message-tests.json
@@ -0,0 +1,196 @@
+[
+ {
+ "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",
+ "data": {
+ "foo": [1, 2, 3],
+ "bar": 123
+ },
+ "valid": false,
+ "validationMessages": [
+ "Custom Message: Invalid type provided"
+ ]
+ },
+ {
+ "description": "Custom error message for keyword failing at nested property level - type keyword",
+ "data": {
+ "foo": [1, 2, "text"],
+ "bar": "Bar 1"
+ },
+ "valid": false,
+ "validationMessages": [
+ "Custom Message: Only numbers are supported in 'foo'"
+ ]
+ },
+ {
+ "description": "Custom error message for keyword failing at nested property level - maxItems keyword",
+ "data": {
+ "foo": [1, 2, 3, 4],
+ "bar": "Bar 1"
+ },
+ "valid": false,
+ "validationMessages": [
+ "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",
+ "data": {
+ "foo": [1, 2, "text"],
+ "bar": 123
+ },
+ "valid": false,
+ "validationMessages": [
+ "Custom Message: Invalid type provided",
+ "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",
+ "data": {
+ "foo": [1, 2, "text", 3],
+ "bar": 123
+ },
+ "valid": false,
+ "validationMessages": [
+ "Custom Message: Invalid type provided",
+ "Custom Message: Only numbers are supported in '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" : "{0}: ''foo'' is required",
+ "bar" : "{0}: ''bar'' is required"
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "bar is required",
+ "data": {
+ "foo": 1
+ },
+ "valid": false,
+ "validationMessages": [
+ "$: 'bar' is required"
+ ]
+ },
+ {
+ "description": "foo is required",
+ "data": {
+ "bar": "bar"
+ },
+ "valid": false,
+ "validationMessages": [
+ "$: 'foo' is required"
+ ]
+ },
+ {
+ "description": "both foo and bar are required",
+ "data": {
+ },
+ "valid": false,
+ "validationMessages": [
+ "$: '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": "{0}: Item should not be empty"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "should not be empty",
+ "data": {
+ "requestedItems": [
+ {
+ "item": ""
+ }
+ ]
+ },
+ "valid": false,
+ "validationMessages": [
+ "$.requestedItems[0].item: Item should not be empty"
+ ]
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/test/resources/schema/dateTimeArray.json b/src/test/resources/schema/dateTimeArray.json
new file mode 100644
index 0000000..68bab5b
--- /dev/null
+++ b/src/test/resources/schema/dateTimeArray.json
@@ -0,0 +1,10 @@
+{
+ "$id": "https://example.com/dateTimeTest.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "date-time"
+ }
+}
+
diff --git a/src/test/resources/schema/example-main.json b/src/test/resources/schema/example-main.json
new file mode 100644
index 0000000..05a8436
--- /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 0000000..39dfc0b
--- /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 0000000..d159f7b
--- /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/issue295-v7.json b/src/test/resources/schema/issue295-v7.json
new file mode 100644
index 0000000..449a0e6
--- /dev/null
+++ b/src/test/resources/schema/issue295-v7.json
@@ -0,0 +1,24 @@
+{
+ "$id": "https://example.com/properties.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "oneOf": [
+ {
+ "required": [
+ "requiredprop"
+ ],
+ "properties": {
+ "requiredprop": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "properties": {
+ "forbiddenprop": {
+ "type": "null"
+ }
+ }
+ }
+ ]
+}
+
diff --git a/src/test/resources/schema/issue313-2019-09.json b/src/test/resources/schema/issue313-2019-09.json
new file mode 100644
index 0000000..bd1d421
--- /dev/null
+++ b/src/test/resources/schema/issue313-2019-09.json
@@ -0,0 +1,41 @@
+{
+ "$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/test/resources/schema/issue313-v7.json b/src/test/resources/schema/issue313-v7.json
new file mode 100644
index 0000000..26bbc2f
--- /dev/null
+++ b/src/test/resources/schema/issue313-v7.json
@@ -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/test/resources/schema/issue314-v7.json b/src/test/resources/schema/issue314-v7.json
new file mode 100644
index 0000000..c0d605c
--- /dev/null
+++ b/src/test/resources/schema/issue314-v7.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#",
+ "type": "object"
+}
diff --git a/src/test/resources/schema/issue327-v7.json b/src/test/resources/schema/issue327-v7.json
new file mode 100644
index 0000000..65478b1
--- /dev/null
+++ b/src/test/resources/schema/issue327-v7.json
@@ -0,0 +1,21 @@
+{
+ "$id": "https://example.com/person.schema.json",
+ "$schema": "http://json-schema.org/draft-07/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
+ }
+ }
+}
diff --git a/src/test/resources/schema/issue342-v7.json b/src/test/resources/schema/issue342-v7.json
new file mode 100644
index 0000000..31bfbe1
--- /dev/null
+++ b/src/test/resources/schema/issue342-v7.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "propertyNames": {
+ "enum": [
+ "a",
+ "b",
+ "c"
+ ]
+ }
+}
diff --git a/src/test/resources/schema/issue347-v7.json b/src/test/resources/schema/issue347-v7.json
new file mode 100644
index 0000000..2ae91c0
--- /dev/null
+++ b/src/test/resources/schema/issue347-v7.json
@@ -0,0 +1,19 @@
+{
+ "$id": "test",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "Test description",
+ "properties": {
+ "test": {
+ "description": "Sample property",
+ "examples": [
+ "hello",
+ "world"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "test"
+ ],
+ "type": "object"
+}
diff --git a/src/test/resources/schema/issue366_schema.json b/src/test/resources/schema/issue366_schema.json
new file mode 100644
index 0000000..b397596
--- /dev/null
+++ b/src/test/resources/schema/issue366_schema.json
@@ -0,0 +1,11 @@
+{
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "minimum": 2
+ }
+ ]
+}
+
diff --git a/src/test/resources/schema/issue383-v7.json b/src/test/resources/schema/issue383-v7.json
new file mode 100644
index 0000000..aa28c75
--- /dev/null
+++ b/src/test/resources/schema/issue383-v7.json
@@ -0,0 +1,85 @@
+{
+ "type": "object",
+ "properties": {
+ "validation": {
+ "$ref": "#/definitions/predicateOrNull"
+ }
+ },
+ "definitions": {
+ "fieldRef": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "set": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ]
+ },
+ "patternPredicate": {
+ "type": "object",
+ "oneOf": [
+ {
+ "properties": {
+ "notEmpty": {
+ "$ref": "#/definitions/fieldRef"
+ }
+ },
+ "required": [
+ "notEmpty"
+ ]
+ },
+ {
+ "properties": {
+ "notBlank": {
+ "$ref": "#/definitions/fieldRef"
+ }
+ },
+ "required": [
+ "notBlank"
+ ]
+ }
+ ]
+ },
+ "allPredicate": {
+ "type": "object",
+ "properties": {
+ "all": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/predicate"
+ }
+ }
+ },
+ "required": [
+ "all"
+ ]
+ },
+ "predicate": {
+ "type": "object",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/patternPredicate"
+ },
+ {
+ "$ref": "#/definitions/allPredicate"
+ }
+ ]
+ },
+ "predicateOrNull": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/predicate"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+}
diff --git a/src/test/resources/schema/issue396-v7.json b/src/test/resources/schema/issue396-v7.json
new file mode 100644
index 0000000..cfb7233
--- /dev/null
+++ b/src/test/resources/schema/issue396-v7.json
@@ -0,0 +1,40 @@
+{
+ "type": "object",
+ "propertyNames": {
+ "anyOf": [
+ {
+ "minLength": 10
+ },
+ {
+ "$ref": "#/definitions/props"
+ },
+ {
+ "oneOf": [
+ {
+ "enum": [
+ "z",
+ "a",
+ "b"
+ ]
+ },
+ {
+ "$ref": "#/definitions/xyz"
+ }
+ ]
+ }
+ ]
+ },
+ "definitions": {
+ "props": {
+ "minLength": 2,
+ "pattern": "[A-Z]{2,}"
+ },
+ "xyz": {
+ "enum": [
+ "x",
+ "y",
+ "z"
+ ]
+ }
+ }
+}
diff --git a/src/test/resources/schema/issue404-v7.json b/src/test/resources/schema/issue404-v7.json
new file mode 100644
index 0000000..8e6c181
--- /dev/null
+++ b/src/test/resources/schema/issue404-v7.json
@@ -0,0 +1,14 @@
+{
+ "$id": "https://example.com/address.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "object",
+ "enum": [1, 2, 3]
+ },
+ "bar": {
+ "$ref": "#/properties/foo"
+ }
+ }
+}
diff --git a/src/test/resources/schema/issue426-v7.json b/src/test/resources/schema/issue426-v7.json
new file mode 100644
index 0000000..9b01e2b
--- /dev/null
+++ b/src/test/resources/schema/issue426-v7.json
@@ -0,0 +1,20 @@
+{
+ "$id": "https://example.com/person.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Person",
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "description": "The person's first name."
+ },
+ "foo": {
+ "type": "array",
+ "maxItems": 3
+ }
+ },
+ "message": {
+ "maxItems" : "MaxItem must be 3 only",
+ "type" : "Invalid type"
+ }
+}
diff --git a/src/test/resources/schema/issue451-v7.json b/src/test/resources/schema/issue451-v7.json
new file mode 100644
index 0000000..de37523
--- /dev/null
+++ b/src/test/resources/schema/issue451-v7.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://example.com/issue-451.json",
+ "title": "AllOf structuring payload",
+ "description": "Test description",
+ "type": "object",
+ "properties": {
+ "allOfAttr" : {
+ "allOf" : [
+ {"$ref": "#/definitions/definition1"},
+ {"$ref": "#/definitions/definition2"}
+ ]
+ },
+ "anyOfAttr" : {
+ "anyOf" : [
+ {"$ref": "#/definitions/definition1"},
+ {"$ref": "#/definitions/definition2"}
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "definitions": {
+ "definition1" : {
+ "type": "object",
+ "properties": {
+ "a" : {"type": "string"},
+ "c" : {"type": "integer"}
+ },
+ "additionalProperties": false
+ },
+ "definition2" : {
+ "type": "object",
+ "properties": {
+ "x" : {"type": "number"},
+ "y" : {"type": "number"}
+ },
+ "required": ["x", "y"],
+ "additionalProperties": false
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/schema/issue456-v7.json b/src/test/resources/schema/issue456-v7.json
new file mode 100644
index 0000000..46fbe08
--- /dev/null
+++ b/src/test/resources/schema/issue456-v7.json
@@ -0,0 +1,44 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://example.com/issue-456.json",
+ "title": "OneOf validates first child only",
+ "description": "Test description",
+ "type": "object",
+ "properties": {
+ "id": "string",
+ "details": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "__typename": {
+ "const": "T2"
+ }
+ },
+ "required": [
+ "name",
+ "__typename"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "__typename": {
+ "const": "T3"
+ }
+ },
+ "required": [
+ "description",
+ "__typename"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/test/resources/schema/issue467.json b/src/test/resources/schema/issue467.json
new file mode 100644
index 0000000..257ea16
--- /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/issue471-2019-09.json b/src/test/resources/schema/issue471-2019-09.json
new file mode 100644
index 0000000..369363c
--- /dev/null
+++ b/src/test/resources/schema/issue471-2019-09.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/schema",
+ "type": "object",
+ "properties": {
+ "pictures":{
+ "type":"array",
+ "maxItems":2,
+ "items":{
+ "type":"string"
+ }
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 10
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/schema/issue493.json b/src/test/resources/schema/issue493.json
new file mode 100644
index 0000000..d24a0b1
--- /dev/null
+++ b/src/test/resources/schema/issue493.json
@@ -0,0 +1,72 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "parameters": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": [
+ {
+ "type": "object",
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "const": "param-required"
+ },
+ "value": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/test-ref1"
+ },
+ {
+ "$ref": "#/$defs/test-ref2"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "additionalItems": {
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "const": "param-optional"
+ },
+ "value": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/test-ref1"
+ },
+ {
+ "$ref": "#/$defs/test-ref2"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "minItems": 1,
+ "maxItems": 2
+ }
+ },
+ "$defs": {
+ "test-ref1": {
+ "type": "string",
+ "pattern": "^\\{\\{.+\\}\\}$"
+ },
+ "test-ref2": {
+ "type": "integer"
+ }
+ }
+} \ No newline at end of file
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 0000000..c88c294
--- /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 0000000..934c606
--- /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/issue518-v7.json b/src/test/resources/schema/issue518-v7.json
new file mode 100644
index 0000000..c0d605c
--- /dev/null
+++ b/src/test/resources/schema/issue518-v7.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#",
+ "type": "object"
+}
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 0000000..7d984cf
--- /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 0000000..f026907
--- /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 0000000..69696bf
--- /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 0000000..df4091e
--- /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 0000000..a6a2c52
--- /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 0000000..d73ddd1
--- /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 0000000..d73ddd1
--- /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 0000000..61ef6e4
--- /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 0000000..e69f514
--- /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 0000000..5e24803
--- /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 0000000..9404473
--- /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 0000000..ab4137c
--- /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 0000000..63dd7db
--- /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 0000000..ef7d4f3
--- /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 0000000..483c6d0
--- /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 0000000..d159f7b
--- /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/notAllowedValidation/notAllowedJson.json b/src/test/resources/schema/notAllowedValidation/notAllowedJson.json
new file mode 100644
index 0000000..e97b014
--- /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/v31/petstore.json b/src/test/resources/schema/oas/v31/petstore.json
new file mode 100644
index 0000000..adb86a6
--- /dev/null
+++ b/src/test/resources/schema/oas/v31/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/output-format-schema.json b/src/test/resources/schema/output-format-schema.json
new file mode 100644
index 0000000..10d3957
--- /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 0000000..4b1e3cf
--- /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 0000000..c42fecd
--- /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 0000000..c219fec
--- /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 0000000..3b43a5d
--- /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 0000000..f9eadfc
--- /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 0000000..ea263fc
--- /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,
+ "validationMessages": [
+ "$: 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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$.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,
+ "validationMessages": [
+ "$: 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
new file mode 100644
index 0000000..e26cd83
--- /dev/null
+++ b/src/test/resources/schema/walk-schema-default.json
@@ -0,0 +1,101 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Schema with default values ",
+
+ "definitions": {
+ "RandomObject": {
+ "type": "object",
+ "properties": {
+ "stringValue_missing": {
+ "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": [
+ "stringValue_missing"
+ ]
+ }
+ },
+
+ "type": "object",
+ "properties": {
+ "outer": {
+ "type": "object",
+ "properties": {
+ "mixedObject": {
+ "type": "object",
+ "properties": {
+ "intValue_present": {
+ "description": "the test data supplies a value for this attribute so the default is ignored",
+ "type": "integer",
+ "default": 5
+ },
+ "intValue_missing": {
+ "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it",
+ "type": "integer",
+ "default": 15
+ },
+ "intValue_missing_notRequired": {
+ "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it",
+ "type": "integer",
+ "default": 25
+ },
+ "intValue_null": {
+ "description": "the test data supplies the value null for this attribute so the default should be applied if the ApplyDefaultsStrategy requires it",
+ "type": "integer",
+ "default": 35
+ },
+ "intValue_missingButError": {
+ "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it, but the default is wrong so there should be an error",
+ "type": "integer",
+ "default": "forty-five"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "intValue_present",
+ "intValue_missing",
+ "intValue_null",
+ "intValue_missingButError"
+ ]
+ },
+
+ "goodArray": {
+ "type": "array",
+ "items": {
+ "description": "if an item in the array is null, then default value should be applied if the ApplyDefaultsStrategy requires it",
+ "type": "string",
+ "default": "five"
+ }
+ },
+
+ "badArray": {
+ "type": "array",
+ "items": {
+ "description": "if an item in the array is null, then default value should be applied if the ApplyDefaultsStrategy requires it, but the default is wrong so there should be an error",
+ "type": "string",
+ "default": 5
+ }
+ },
+
+ "reference": {
+ "$ref": "#/definitions/RandomObject"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["mixedObject", "goodArray", "badArray", "reference"]
+ }
+ },
+ "additionalProperties": false,
+ "required": ["outer"]
+}
diff --git a/src/test/resources/schema/walk-schema.json b/src/test/resources/schema/walk-schema.json
new file mode 100644
index 0000000..48ab3ca
--- /dev/null
+++ b/src/test/resources/schema/walk-schema.json
@@ -0,0 +1,75 @@
+{
+ "definitions": {
+ "phone-number": {
+ "country-code": {
+ "title": "country code",
+ "type": "string"
+ },
+ "number": {
+ "title": "number",
+ "type": "number"
+ }
+ },
+ "address": {
+ "type": "object",
+ "title": "Address",
+ "properties": {
+ "street_address": {
+ "title": "StreetAddress",
+ "type": "string"
+ },
+ "city": {
+ "title": "City",
+ "type": "string"
+ },
+ "state": {
+ "title": "State",
+ "type": "string"
+ },
+ "phone_number": {
+ "title": "PhoneNumber",
+ "type": "object",
+ "$ref": "#/definitions/phone-number"
+ }
+ },
+ "required": [
+ "city",
+ "state"
+ ]
+ }
+ },
+ "$schema": "https://github.com/networknt/json-schema-validator/tests/schemas/example01",
+ "title": "Sample test schema ",
+ "description": "Sample schema definition",
+ "type": " object",
+ "properties": {
+ "property1": {
+ "title": "Property1",
+ "type": "string",
+ "custom-keyword": [
+
+ ]
+ },
+ "property2": {
+ "title": "Property2",
+ "type ": "string",
+ "custom-keyword": [
+
+ ]
+ },
+ "property3": {
+ "title": "Property3",
+ "$ref": "#/definitions/address",
+ "properties": {
+ "property3.1": {
+ "title": "Property3.1",
+ "type ": "string"
+ }
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "property1"
+ ]
+} \ No newline at end of file
diff --git a/src/test/resources/selfRef.json b/src/test/resources/selfRef.json
new file mode 100644
index 0000000..385c25d
--- /dev/null
+++ b/src/test/resources/selfRef.json
@@ -0,0 +1,31 @@
+{
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "tree": {
+ "$ref": "#/definitions/tree"
+ }
+ },
+ "definitions": {
+ "tree": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ },
+ "branches": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tree"
+ },
+ "minItems": 1
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/resources/test-messages.properties b/src/test/resources/test-messages.properties
new file mode 100644
index 0000000..6e4a5b6
--- /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 0000000..2d92388
--- /dev/null
+++ b/src/test/resources/test-messages_fr.properties
@@ -0,0 +1 @@
+atmostOne = french
diff --git a/src/test/suite/.editorconfig b/src/test/suite/.editorconfig
new file mode 100644
index 0000000..6db6a5b
--- /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 0000000..15f4a2d
--- /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 0000000..a826069
--- /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 0000000..68bc17f
--- /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 0000000..8dab09e
--- /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 0000000..c28adba
--- /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 0000000..36dda74
--- /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/bin/jsonschema_suite b/src/test/suite/bin/jsonschema_suite
new file mode 100755
index 0000000..9fee8d7
--- /dev/null
+++ b/src/test/suite/bin/jsonschema_suite
@@ -0,0 +1,734 @@
+#! /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 = None
+ VALIDATORS = {}
+else:
+ 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 jsonschema.exceptions.RefResolutionError:
+ 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.exceptions.RefResolutionError:
+ # python-jsonschema/jsonschema#884
+ pass
+ 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("/<path:path>")
+ 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/output-test-schema.json b/src/test/suite/output-test-schema.json
new file mode 100644
index 0000000..02c51ef
--- /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 0000000..d209bdb
--- /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.
+<!-- This general.json file may be added to the structure tests later, but I haven't gotten to them yet, so I don't know. -->
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 0000000..27082ed
--- /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 0000000..d387d93
--- /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 0000000..e17f1f5
--- /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 0000000..26286fa
--- /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 0000000..d8cda15
--- /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 0000000..9194170
--- /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 0000000..62db1a8
--- /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 0000000..cff77a7
--- /dev/null
+++ b/src/test/suite/output-tests/draft2019-09/content/type.json
@@ -0,0 +1,63 @@
+[
+ {
+ "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"]
+ }
+ }
+ },
+ {
+ "description": "correct type yields an output unit",
+ "data": "a string",
+ "output": {
+ "basic": {
+ "$id": "https://json-schema.org/tests/content/draft2019-09/type/0/tests/1/basic",
+ "$ref": "/draft/2019-09/output/schema",
+ "properties": {
+ "annotations": {
+ "contains": {
+ "properties": {
+ "valid": {"const": true},
+ "keywordLocation": {"const": "/type"},
+ "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2019-09/type/0#/type"},
+ "instanceLocation": {"const": ""},
+ "annotation": false,
+ "error": false
+ },
+ "required": ["keywordLocation", "instanceLocation"]
+ }
+ }
+ },
+ "required": ["annotations"]
+ }
+ }
+ }
+ ]
+ }
+]
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 0000000..ff523ee
--- /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 0000000..c329c91
--- /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 0000000..1f2b370
--- /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 0000000..9baf48d
--- /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 0000000..710475b
--- /dev/null
+++ b/src/test/suite/output-tests/draft2020-12/content/type.json
@@ -0,0 +1,63 @@
+[
+ {
+ "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"]
+ }
+ }
+ },
+ {
+ "description": "correct type yields an output unit",
+ "data": "a string",
+ "output": {
+ "basic": {
+ "$id": "https://json-schema.org/tests/content/draft2020-12/type/0/tests/1/basic",
+ "$ref": "/draft/2020-12/output/schema",
+ "properties": {
+ "annotations": {
+ "contains": {
+ "properties": {
+ "valid": {"const": true},
+ "keywordLocation": {"const": "/type"},
+ "absoluteKeywordLocation": {"const": "https://json-schema.org/tests/content/draft2020-12/type/0#/type"},
+ "instanceLocation": {"const": ""},
+ "annotation": false,
+ "error": false
+ },
+ "required": ["keywordLocation", "instanceLocation"]
+ }
+ }
+ },
+ "required": ["annotations"]
+ }
+ }
+ }
+ ]
+ }
+]
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 0000000..1eef288
--- /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 0000000..75da9e2
--- /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/suite/remotes/baseUriChange/folderInteger.json b/src/test/suite/remotes/baseUriChange/folderInteger.json
new file mode 100644
index 0000000..8b50ea3
--- /dev/null
+++ b/src/test/suite/remotes/baseUriChange/folderInteger.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+}
diff --git a/src/test/suite/remotes/baseUriChangeFolder/folderInteger.json b/src/test/suite/remotes/baseUriChangeFolder/folderInteger.json
new file mode 100644
index 0000000..8b50ea3
--- /dev/null
+++ b/src/test/suite/remotes/baseUriChangeFolder/folderInteger.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+}
diff --git a/src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json b/src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json
new file mode 100644
index 0000000..8b50ea3
--- /dev/null
+++ b/src/test/suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json
@@ -0,0 +1,3 @@
+{
+ "type": "integer"
+}
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 0000000..7f88860
--- /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 0000000..388c881
--- /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 0000000..388c881
--- /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 0000000..388c881
--- /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 0000000..c1a09a5
--- /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 0000000..d01aaa1
--- /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 0000000..e787aa3
--- /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 0000000..91c8669
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/format-assertion-false.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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 0000000..a33d143
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/format-assertion-true.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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/id/my_identifier.json b/src/test/suite/remotes/draft-next/id/my_identifier.json
new file mode 100644
index 0000000..1167cb1
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/id/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..388c881
--- /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 0000000..17b4df5
--- /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 0000000..c19c9e8
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/metaschema-no-validation.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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 0000000..e78e531
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/metaschema-optional-vocabulary.json
@@ -0,0 +1,13 @@
+{
+ "$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
+ },
+ "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 0000000..cdb8c0c
--- /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 0000000..50bf77f
--- /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 0000000..7462207
--- /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 0000000..46fafc9
--- /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-defs.json b/src/test/suite/remotes/draft-next/subSchemas-defs.json
new file mode 100644
index 0000000..75b7583
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/subSchemas-defs.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/subSchemas.json b/src/test/suite/remotes/draft-next/subSchemas.json
new file mode 100644
index 0000000..75b7583
--- /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 0000000..cad332a
--- /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/draft-next/unknownKeyword/my_identifier.json b/src/test/suite/remotes/draft-next/unknownKeyword/my_identifier.json
new file mode 100644
index 0000000..1167cb1
--- /dev/null
+++ b/src/test/suite/remotes/draft-next/unknownKeyword/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..bf679e8
--- /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 0000000..bf679e8
--- /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 0000000..bf679e8
--- /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 0000000..0d691d9
--- /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 0000000..4a3499f
--- /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 0000000..c11bf0a
--- /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/id/my_identifier.json b/src/test/suite/remotes/draft2019-09/id/my_identifier.json
new file mode 100644
index 0000000..2a2c062
--- /dev/null
+++ b/src/test/suite/remotes/draft2019-09/id/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..b5ef392
--- /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 0000000..bf679e8
--- /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 0000000..a0fb9fb
--- /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 0000000..494f0ab
--- /dev/null
+++ b/src/test/suite/remotes/draft2019-09/metaschema-no-validation.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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 0000000..968597c
--- /dev/null
+++ b/src/test/suite/remotes/draft2019-09/metaschema-optional-vocabulary.json
@@ -0,0 +1,13 @@
+{
+ "$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
+ },
+ "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 0000000..8892f16
--- /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 0000000..fe10748
--- /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 0000000..242f713
--- /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 0000000..0ad690d
--- /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-defs.json b/src/test/suite/remotes/draft2019-09/subSchemas-defs.json
new file mode 100644
index 0000000..fdfee68
--- /dev/null
+++ b/src/test/suite/remotes/draft2019-09/subSchemas-defs.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/subSchemas.json b/src/test/suite/remotes/draft2019-09/subSchemas.json
new file mode 100644
index 0000000..fdfee68
--- /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 0000000..fce7941
--- /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/draft2019-09/unknownKeyword/my_identifier.json b/src/test/suite/remotes/draft2019-09/unknownKeyword/my_identifier.json
new file mode 100644
index 0000000..2a2c062
--- /dev/null
+++ b/src/test/suite/remotes/draft2019-09/unknownKeyword/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..1f44a63
--- /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 0000000..1f44a63
--- /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 0000000..1f44a63
--- /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 0000000..07cce1d
--- /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 0000000..9c2dca9
--- /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 0000000..65bc0c2
--- /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 0000000..d6dd645
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/format-assertion-false.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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 0000000..bb16d58
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/format-assertion-true.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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/id/my_identifier.json b/src/test/suite/remotes/draft2020-12/id/my_identifier.json
new file mode 100644
index 0000000..3a1a687
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/id/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..1f44a63
--- /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 0000000..6565a1e
--- /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 0000000..85d74b2
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/metaschema-no-validation.json
@@ -0,0 +1,12 @@
+{
+ "$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
+ },
+ "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 0000000..f38ec28
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/metaschema-optional-vocabulary.json
@@ -0,0 +1,13 @@
+{
+ "$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
+ },
+ "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 0000000..67bc33c
--- /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 0000000..29661ff
--- /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 0000000..6607ac5
--- /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 0000000..acd8293
--- /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 0000000..16d30fa
--- /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-defs.json b/src/test/suite/remotes/draft2020-12/subSchemas-defs.json
new file mode 100644
index 0000000..1bb4846
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/subSchemas-defs.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/subSchemas.json b/src/test/suite/remotes/draft2020-12/subSchemas.json
new file mode 100644
index 0000000..1bb4846
--- /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 0000000..b07555f
--- /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/draft2020-12/unknownKeyword/my_identifier.json b/src/test/suite/remotes/draft2020-12/unknownKeyword/my_identifier.json
new file mode 100644
index 0000000..3a1a687
--- /dev/null
+++ b/src/test/suite/remotes/draft2020-12/unknownKeyword/my_identifier.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "string"
+} \ No newline at end of file
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 0000000..05ce071
--- /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/draft7/detached-ref.json b/src/test/suite/remotes/draft7/detached-ref.json
new file mode 100644
index 0000000..27f2ec8
--- /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 0000000..0ea927b
--- /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/extendible-dynamic-ref.json b/src/test/suite/remotes/extendible-dynamic-ref.json
new file mode 100644
index 0000000..d0bcd37
--- /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/id/my_identifier.json b/src/test/suite/remotes/id/my_identifier.json
new file mode 100644
index 0000000..5d2d1e5
--- /dev/null
+++ b/src/test/suite/remotes/id/my_identifier.json
@@ -0,0 +1,3 @@
+{
+ "type": "string"
+} \ No newline at end of file
diff --git a/src/test/suite/remotes/integer.json b/src/test/suite/remotes/integer.json
new file mode 100644
index 0000000..8b50ea3
--- /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 0000000..96b17c3
--- /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/locationIndependentIdentifierDraft4.json b/src/test/suite/remotes/locationIndependentIdentifierDraft4.json
new file mode 100644
index 0000000..eeff1eb
--- /dev/null
+++ b/src/test/suite/remotes/locationIndependentIdentifierDraft4.json
@@ -0,0 +1,11 @@
+{
+ "definitions": {
+ "refToInteger": {
+ "$ref": "#foo"
+ },
+ "A": {
+ "id": "#foo",
+ "type": "integer"
+ }
+ }
+}
diff --git a/src/test/suite/remotes/locationIndependentIdentifierPre2019.json b/src/test/suite/remotes/locationIndependentIdentifierPre2019.json
new file mode 100644
index 0000000..e72815c
--- /dev/null
+++ b/src/test/suite/remotes/locationIndependentIdentifierPre2019.json
@@ -0,0 +1,11 @@
+{
+ "definitions": {
+ "refToInteger": {
+ "$ref": "#foo"
+ },
+ "A": {
+ "$id": "#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 0000000..1dab4a4
--- /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/name.json b/src/test/suite/remotes/name.json
new file mode 100644
index 0000000..fceacb8
--- /dev/null
+++ b/src/test/suite/remotes/name.json
@@ -0,0 +1,15 @@
+{
+ "definitions": {
+ "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 0000000..f46c761
--- /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 0000000..9cd2527
--- /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 0000000..c2d48c0
--- /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-definitions.json b/src/test/suite/remotes/ref-and-definitions.json
new file mode 100644
index 0000000..e0ee802
--- /dev/null
+++ b/src/test/suite/remotes/ref-and-definitions.json
@@ -0,0 +1,11 @@
+{
+ "$id": "http://localhost:1234/ref-and-definitions.json",
+ "definitions": {
+ "inner": {
+ "properties": {
+ "bar": { "type": "string" }
+ }
+ }
+ },
+ "allOf": [ { "$ref": "#/definitions/inner" } ]
+}
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 0000000..85d06c3
--- /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/subSchemas-defs.json b/src/test/suite/remotes/subSchemas-defs.json
new file mode 100644
index 0000000..50b7b6d
--- /dev/null
+++ b/src/test/suite/remotes/subSchemas-defs.json
@@ -0,0 +1,10 @@
+{
+ "$defs": {
+ "integer": {
+ "type": "integer"
+ },
+ "refToInteger": {
+ "$ref": "#/$defs/integer"
+ }
+ }
+}
diff --git a/src/test/suite/remotes/subSchemas.json b/src/test/suite/remotes/subSchemas.json
new file mode 100644
index 0000000..6e9b3de
--- /dev/null
+++ b/src/test/suite/remotes/subSchemas.json
@@ -0,0 +1,10 @@
+{
+ "definitions": {
+ "integer": {
+ "type": "integer"
+ },
+ "refToInteger": {
+ "$ref": "#/definitions/integer"
+ }
+ }
+}
diff --git a/src/test/suite/remotes/tree.json b/src/test/suite/remotes/tree.json
new file mode 100644
index 0000000..a12d98b
--- /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/unknownKeyword/my_identifier.json b/src/test/suite/remotes/unknownKeyword/my_identifier.json
new file mode 100644
index 0000000..5d2d1e5
--- /dev/null
+++ b/src/test/suite/remotes/unknownKeyword/my_identifier.json
@@ -0,0 +1,3 @@
+{
+ "type": "string"
+} \ No newline at end of file
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 0000000..aca2211
--- /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 0000000..8339316
--- /dev/null
+++ b/src/test/suite/test-schema.json
@@ -0,0 +1,61 @@
+{
+ "$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
+ }
+ },
+ "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 0000000..7859fbb
--- /dev/null
+++ b/src/test/suite/tests/draft-next/additionalProperties.json
@@ -0,0 +1,156 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..86745da
--- /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 0000000..a0c4c51
--- /dev/null
+++ b/src/test/suite/tests/draft-next/anchor.json
@@ -0,0 +1,144 @@
+[
+ {
+ "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
+ }
+ ]
+ },
+ {
+ "description": "invalid anchors",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "$ref": "https://json-schema.org/draft/next/schema"
+ },
+ "tests": [
+ {
+ "description": "MUST start with a letter (and not #)",
+ "data": { "$anchor" : "#foo" },
+ "valid": false
+ },
+ {
+ "description": "JSON pointers are not valid",
+ "data": { "$anchor" : "/a/b" },
+ "valid": false
+ },
+ {
+ "description": "invalid with valid beginning",
+ "data": { "$anchor" : "foo#something" },
+ "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 0000000..a97267c
--- /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 0000000..6d40f23
--- /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 0000000..61fbd83
--- /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 0000000..8539a53
--- /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 0000000..37e1f09
--- /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 0000000..689b68e
--- /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 0000000..7bda825
--- /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 0000000..2344724
--- /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 0000000..86079c3
--- /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 0000000..94124ff
--- /dev/null
+++ b/src/test/suite/tests/draft-next/dynamicRef.json
@@ -0,0 +1,646 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..e263f39
--- /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 0000000..6ec4752
--- /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 0000000..9026298
--- /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 0000000..ec6c7f1
--- /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/id.json b/src/test/suite/tests/draft-next/id.json
new file mode 100644
index 0000000..fe74c6b
--- /dev/null
+++ b/src/test/suite/tests/draft-next/id.json
@@ -0,0 +1,211 @@
+[
+ {
+ "description": "Invalid use of fragments in location-independent $id",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "$ref": "https://json-schema.org/draft/next/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name",
+ "data": {
+ "$ref": "#foo",
+ "$defs": {
+ "A": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name and no ref",
+ "data": {
+ "$defs": {
+ "A": { "$id": "#foo" }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path",
+ "data": {
+ "$ref": "#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft-next/bar#foo",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/bar#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft-next/bar#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/bar#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft-next/root",
+ "$ref": "http://localhost:1234/draft-next/nested.json#foo",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft-next/root",
+ "$ref": "http://localhost:1234/draft-next/nested.json#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Valid use of empty fragments in location-independent $id",
+ "comment": "These are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "$ref": "https://json-schema.org/draft/next/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft-next/bar",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/bar#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft-next/root",
+ "$ref": "http://localhost:1234/draft-next/nested.json#/$defs/B",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": true
+ }
+ ]
+ },
+ {
+ "description": "Unnormalized $ids are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "$ref": "https://json-schema.org/draft/next/schema"
+ },
+ "tests": [
+ {
+ "description": "Unnormalized identifier",
+ "data": {
+ "$ref": "http://localhost:1234/draft-next/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment",
+ "data": {
+ "$ref": "http://localhost:1234/draft-next/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft-next/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "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 0000000..70576c4
--- /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 0000000..fa66743
--- /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 0000000..dfb79af
--- /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 0000000..5af6e4c
--- /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 0000000..b215850
--- /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 0000000..c88f604
--- /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 0000000..5eec9da
--- /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 0000000..656ce81
--- /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 0000000..a6ad212
--- /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 0000000..b0e1c72
--- /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 0000000..52c9c9a
--- /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 0000000..434c0f1
--- /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 0000000..2c6f71f
--- /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 0000000..f153454
--- /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 0000000..b225104
--- /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 0000000..e8c0771
--- /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 0000000..1de0b7a
--- /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 0000000..9f4f707
--- /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 0000000..1fe384c
--- /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/ecmascript-regex.json b/src/test/suite/tests/draft-next/optional/ecmascript-regex.json
new file mode 100644
index 0000000..2721145
--- /dev/null
+++ b/src/test/suite/tests/draft-next/optional/ecmascript-regex.json
@@ -0,0 +1,596 @@
+[
+ {
+ "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": "\\a is not an ECMA 262 control escape",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/next/schema",
+ "$ref": "https://json-schema.org/draft/next/schema"
+ },
+ "tests": [
+ {
+ "description": "when used as a pattern",
+ "data": { "pattern": "\\a" },
+ "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 0000000..a4fcca6
--- /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 0000000..ede922a
--- /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 0000000..e25845b
--- /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 0000000..aa55555
--- /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 0000000..d5adca2
--- /dev/null
+++ b/src/test/suite/tests/draft-next/optional/format/duration.json
@@ -0,0 +1,136 @@
+[
+ {
+ "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": "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/email.json b/src/test/suite/tests/draft-next/optional/format/email.json
new file mode 100644
index 0000000..5948ebd
--- /dev/null
+++ b/src/test/suite/tests/draft-next/optional/format/email.json
@@ -0,0 +1,121 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..bfb3063
--- /dev/null
+++ b/src/test/suite/tests/draft-next/optional/format/hostname.json
@@ -0,0 +1,126 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..1f35367
--- /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 0000000..109bf73
--- /dev/null
+++ b/src/test/suite/tests/draft-next/optional/format/idn-hostname.json
@@ -0,0 +1,332 @@
+[
+ {
+ "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": "\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
+ }
+ ]
+ }
+]
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 0000000..2a4bc2b
--- /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 0000000..241df7f
--- /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 0000000..543b99e
--- /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 0000000..48e82a2
--- /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 0000000..f55342f
--- /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 0000000..5987f53
--- /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 0000000..1e28fc2
--- /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 0000000..0a000a4
--- /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 0000000..8d9d254
--- /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 0000000..f57d62d
--- /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 0000000..50908ea
--- /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 0000000..6cea9da
--- /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 0000000..fc26f26
--- /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 0000000..3af875c
--- /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 0000000..c832e09
--- /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 0000000..055ff6b
--- /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 0000000..09c6d0f
--- /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 0000000..c7aca3d
--- /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 0000000..a7f5928
--- /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 0000000..1ba4fe8
--- /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 0000000..9efa2b4
--- /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 0000000..e614017
--- /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 0000000..8417ce2
--- /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 0000000..647fb9f
--- /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 0000000..cc02fd3
--- /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 0000000..86e551c
--- /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 0000000..08f6ef1
--- /dev/null
+++ b/src/test/suite/tests/draft-next/unevaluatedItems.json
@@ -0,0 +1,792 @@
+[
+ {
+ "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": "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 0000000..d0d5350
--- /dev/null
+++ b/src/test/suite/tests/draft-next/unevaluatedProperties.json
@@ -0,0 +1,1607 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..b16dd50
--- /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 0000000..f25b9e1
--- /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 0000000..9a7ae4f
--- /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 0000000..f9f03bb
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/additionalProperties.json
@@ -0,0 +1,156 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..dec267e
--- /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 0000000..eb0a969
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/anchor.json
@@ -0,0 +1,145 @@
+[
+ {
+ "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
+ }
+ ]
+ },
+ {
+ "description": "invalid anchors",
+ "comment": "Section 8.2.3",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$ref": "https://json-schema.org/draft/2019-09/schema"
+ },
+ "tests": [
+ {
+ "description": "MUST start with a letter (and not #)",
+ "data": { "$anchor" : "#foo" },
+ "valid": false
+ },
+ {
+ "description": "JSON pointers are not valid",
+ "data": { "$anchor" : "/a/b" },
+ "valid": false
+ },
+ {
+ "description": "invalid with valid beginning",
+ "data": { "$anchor" : "foo#something" },
+ "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 0000000..8712d11
--- /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 0000000..6d40f23
--- /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 0000000..29700fd
--- /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 0000000..30458bf
--- /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 0000000..2a7a5d8
--- /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 0000000..95eba5a
--- /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 0000000..3f9088c
--- /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 0000000..d573c10
--- /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 0000000..c5b8ea0
--- /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 0000000..1315211
--- /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 0000000..4ec8569
--- /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 0000000..24f4689
--- /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 0000000..2934dce
--- /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/id.json b/src/test/suite/tests/draft2019-09/id.json
new file mode 100644
index 0000000..0ba3138
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/id.json
@@ -0,0 +1,211 @@
+[
+ {
+ "description": "Invalid use of fragments in location-independent $id",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$ref": "https://json-schema.org/draft/2019-09/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name",
+ "data": {
+ "$ref": "#foo",
+ "$defs": {
+ "A": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name and no ref",
+ "data": {
+ "$defs": {
+ "A": { "$id": "#foo" }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path",
+ "data": {
+ "$ref": "#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2019-09/bar#foo",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/bar#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2019-09/bar#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/bar#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2019-09/root",
+ "$ref": "http://localhost:1234/draft2019-09/nested.json#foo",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2019-09/root",
+ "$ref": "http://localhost:1234/draft2019-09/nested.json#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Valid use of empty fragments in location-independent $id",
+ "comment": "These are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$ref": "https://json-schema.org/draft/2019-09/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2019-09/bar",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/bar#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2019-09/root",
+ "$ref": "http://localhost:1234/draft2019-09/nested.json#/$defs/B",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": true
+ }
+ ]
+ },
+ {
+ "description": "Unnormalized $ids are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$ref": "https://json-schema.org/draft/2019-09/schema"
+ },
+ "tests": [
+ {
+ "description": "Unnormalized identifier",
+ "data": {
+ "$ref": "http://localhost:1234/draft2019-09/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment",
+ "data": {
+ "$ref": "http://localhost:1234/draft2019-09/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2019-09/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "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 0000000..510a0e0
--- /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 0000000..eb69414
--- /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 0000000..e24156a
--- /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 0000000..ce4507c
--- /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 0000000..d9ed157
--- /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 0000000..a0cc7d9
--- /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 0000000..5b31474
--- /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 0000000..c1f1dfd
--- /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 0000000..8d3093c
--- /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 0000000..07817cc
--- /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 0000000..1278266
--- /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 0000000..20e01a9
--- /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 0000000..afb5f20
--- /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 0000000..760a434
--- /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 0000000..d90728c
--- /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 0000000..9b7a220
--- /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 0000000..45951d0
--- /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 0000000..8b06467
--- /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 0000000..efd3f87
--- /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 0000000..5bfbd05
--- /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 0000000..be1059c
--- /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 0000000..f5741fa
--- /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 0000000..731001d
--- /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 0000000..805888c
--- /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 0000000..00d5f47
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/optional/format/duration.json
@@ -0,0 +1,136 @@
+[
+ {
+ "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": "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 0000000..e6acc32
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/optional/format/email.json
@@ -0,0 +1,86 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..f3b7181
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/optional/format/hostname.json
@@ -0,0 +1,126 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..baf01d0
--- /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 0000000..072a6b0
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/optional/format/idn-hostname.json
@@ -0,0 +1,332 @@
+[
+ {
+ "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": "\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
+ }
+ ]
+ }
+]
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 0000000..efe4247
--- /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 0000000..0486091
--- /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 0000000..6914210
--- /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 0000000..ad4c79e
--- /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 0000000..39f1cc9
--- /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 0000000..da32401
--- /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 0000000..ba97d2a
--- /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 0000000..dadaae6
--- /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 0000000..c89f730
--- /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 0000000..2c49da6
--- /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 0000000..f4aee10
--- /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 0000000..ad67840
--- /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 0000000..dc6fb7e
--- /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 0000000..4daa8f5
--- /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 0000000..676e6b5
--- /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 0000000..ef25000
--- /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 0000000..e9a75dd
--- /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 0000000..f98e87c
--- /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 0000000..cfb8774
--- /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 0000000..354bb48
--- /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 0000000..a53429c
--- /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 0000000..b7fecbf
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/propertyNames.json
@@ -0,0 +1,115 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..22b47e7
--- /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 0000000..ea56990
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/ref.json
@@ -0,0 +1,1067 @@
+[
+ {
+ "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 f-component",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$comment": "RFC 8141 §2.3.3, but we don't allow fragments",
+ "$ref": "https://json-schema.org/draft/2019-09/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/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
+ }
+ ]
+ }
+]
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 0000000..072894c
--- /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 0000000..bca98a9
--- /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 0000000..92c6be8
--- /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 0000000..8e2ee4b
--- /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 0000000..71c36df
--- /dev/null
+++ b/src/test/suite/tests/draft2019-09/unevaluatedProperties.json
@@ -0,0 +1,1571 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..314b4b9
--- /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 0000000..98482b2
--- /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 0000000..29e69c1
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/additionalProperties.json
@@ -0,0 +1,156 @@
+[
+ {
+ "description":
+ "additionalProperties being false does not allow other properties",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": {
+ "type": "null"
+ }
+ },
+ "tests": [
+ {
+ "description": "allows null values",
+ "data": {"foo": null},
+ "valid": true
+ }
+ ]
+ }
+]
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 0000000..9e87903
--- /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 0000000..83a7166
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/anchor.json
@@ -0,0 +1,145 @@
+[
+ {
+ "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
+ }
+ ]
+ },
+ {
+ "description": "invalid anchors",
+ "comment": "Section 8.2.2",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "tests": [
+ {
+ "description": "MUST start with a letter (and not #)",
+ "data": { "$anchor" : "#foo" },
+ "valid": false
+ },
+ {
+ "description": "JSON pointers are not valid",
+ "data": { "$anchor" : "/a/b" },
+ "valid": false
+ },
+ {
+ "description": "invalid with valid beginning",
+ "data": { "$anchor" : "foo#something" },
+ "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 0000000..89b192d
--- /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 0000000..6d40f23
--- /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 0000000..50be86a
--- /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 0000000..08a00a7
--- /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 0000000..698f780
--- /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 0000000..ceb3ae2
--- /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 0000000..da2a503
--- /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 0000000..2baa38e
--- /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 0000000..1c5f057
--- /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 0000000..bff26ad
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/dynamicRef.json
@@ -0,0 +1,760 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..c8f35ea
--- /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 0000000..05db233
--- /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 0000000..00af9d7
--- /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 0000000..01adcbd
--- /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/id.json b/src/test/suite/tests/draft2020-12/id.json
new file mode 100644
index 0000000..59265c4
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/id.json
@@ -0,0 +1,211 @@
+[
+ {
+ "description": "Invalid use of fragments in location-independent $id",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name",
+ "data": {
+ "$ref": "#foo",
+ "$defs": {
+ "A": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name and no ref",
+ "data": {
+ "$defs": {
+ "A": { "$id": "#foo" }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path",
+ "data": {
+ "$ref": "#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2020-12/bar#foo",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/bar#foo",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2020-12/bar#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/bar#/a/b",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2020-12/root",
+ "$ref": "http://localhost:1234/draft2020-12/nested.json#foo",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#foo",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ },
+ {
+ "description": "Identifier path with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2020-12/root",
+ "$ref": "http://localhost:1234/draft2020-12/nested.json#/a/b",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#/a/b",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": false
+ }
+ ]
+ },
+ {
+ "description": "Valid use of empty fragments in location-independent $id",
+ "comment": "These are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "tests": [
+ {
+ "description": "Identifier name with absolute URI",
+ "data": {
+ "$ref": "http://localhost:1234/draft2020-12/bar",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/bar#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Identifier name with base URI change in subschema",
+ "data": {
+ "$id": "http://localhost:1234/draft2020-12/root",
+ "$ref": "http://localhost:1234/draft2020-12/nested.json#/$defs/B",
+ "$defs": {
+ "A": {
+ "$id": "nested.json",
+ "$defs": {
+ "B": {
+ "$id": "#",
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "valid": true
+ }
+ ]
+ },
+ {
+ "description": "Unnormalized $ids are allowed but discouraged",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "tests": [
+ {
+ "description": "Unnormalized identifier",
+ "data": {
+ "$ref": "http://localhost:1234/draft2020-12/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/foo/bar/../baz",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment",
+ "data": {
+ "$ref": "http://localhost:1234/draft2020-12/foo/baz",
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "valid": true
+ },
+ {
+ "description": "Unnormalized identifier with empty fragment and no ref",
+ "data": {
+ "$defs": {
+ "A": {
+ "$id": "http://localhost:1234/draft2020-12/foo/bar/../baz#",
+ "type": "integer"
+ }
+ }
+ },
+ "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 0000000..1c35d7e
--- /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 0000000..46f157a
--- /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 0000000..6a3e1cf
--- /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 0000000..8cd3ca7
--- /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 0000000..f6a6b7c
--- /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 0000000..7462726
--- /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 0000000..73ae731
--- /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 0000000..b99a541
--- /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 0000000..ee72d7d
--- /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 0000000..9d6a8b6
--- /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 0000000..5076c5a
--- /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 0000000..a753ad3
--- /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 0000000..dc44052
--- /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 0000000..92d6979
--- /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 0000000..d0f2b6e
--- /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 0000000..416c8e5
--- /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 0000000..6d6713b
--- /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 0000000..d69b29e
--- /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 0000000..5113bd6
--- /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 0000000..47d5bd7
--- /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/ecmascript-regex.json b/src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json
new file mode 100644
index 0000000..41eaafe
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/optional/ecmascript-regex.json
@@ -0,0 +1,598 @@
+[
+ {
+ "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": "\\a is not an ECMA 262 control escape",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "tests": [
+ {
+ "description": "when used as a pattern",
+ "data": { "pattern": "\\a" },
+ "valid": false,
+ "disabled": true,
+ "reason": "TODO: RegexFormat does not support ECMA 262 regular expressions"
+ }
+ ]
+ },
+ {
+ "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 0000000..f5ae8b1
--- /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 0000000..0340037
--- /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 0000000..8783d73
--- /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 0000000..dfb1c80
--- /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 0000000..a3af56e
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/optional/format/duration.json
@@ -0,0 +1,136 @@
+[
+ {
+ "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": "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/email.json b/src/test/suite/tests/draft2020-12/optional/format/email.json
new file mode 100644
index 0000000..ee07589
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/optional/format/email.json
@@ -0,0 +1,121 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..41418dd
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/optional/format/hostname.json
@@ -0,0 +1,126 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..50f3c23
--- /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 0000000..bc7d92f
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/optional/format/idn-hostname.json
@@ -0,0 +1,332 @@
+[
+ {
+ "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": "\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
+ }
+ ]
+ }
+]
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 0000000..86d27bd
--- /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 0000000..b9e570c
--- /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 0000000..0c9483d
--- /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 0000000..311c9ef
--- /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 0000000..71ba9b6
--- /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 0000000..a036c6d
--- /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 0000000..3eaf9ce
--- /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 0000000..8967932
--- /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 0000000..7fc35f5
--- /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 0000000..46f28e6
--- /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 0000000..08aab82
--- /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 0000000..84b5f15
--- /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 0000000..d152643
--- /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 0000000..0b7df4e
--- /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 0000000..676e6b5
--- /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 0000000..d2efb3e
--- /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 0000000..c2b080a
--- /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 0000000..28b0c4c
--- /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 0000000..af0b8d8
--- /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 0000000..81829c7
--- /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 0000000..0adfc06
--- /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 0000000..eb66fa8
--- /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 0000000..7ecfb7e
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/propertyNames.json
@@ -0,0 +1,85 @@
+[
+ {
+ "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 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
+ }
+ ]
+ }
+]
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 0000000..8d15fa4
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/ref.json
@@ -0,0 +1,1067 @@
+[
+ {
+ "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 f-component",
+ "schema": {
+ "$comment": "RFC 8141 §2.3.3, but we don't allow fragments",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$ref": "https://json-schema.org/draft/2020-12/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/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 0000000..047ac74
--- /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 0000000..b7cb99a
--- /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 0000000..2123c40
--- /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 0000000..ee0cb65
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/unevaluatedItems.json
@@ -0,0 +1,799 @@
+[
+ {
+ "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": "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 0000000..b8a2306
--- /dev/null
+++ b/src/test/suite/tests/draft2020-12/unevaluatedProperties.json
@@ -0,0 +1,1568 @@
+[
+ {
+ "description": "unevaluatedProperties true",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/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,
+ "type": "object",
+ "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",
+ "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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/2020-12/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/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
+ }
+ ]
+ }
+]
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 0000000..4ea3bf9
--- /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 0000000..1acb96a
--- /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 0000000..ab44a2e
--- /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 0000000..af7bfc6
--- /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/suite/tests/draft3/default.json b/src/test/suite/tests/draft3/default.json
new file mode 100644
index 0000000..289a9b6
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/dependencies.json b/src/test/suite/tests/draft3/dependencies.json
new file mode 100644
index 0000000..0ffa6bf
--- /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 0000000..a5c9d90
--- /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 0000000..ef7cc14
--- /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 0000000..5a1ab3b
--- /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 0000000..909bce5
--- /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 0000000..a5447c9
--- /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 0000000..090f49a
--- /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 0000000..e8bda22
--- /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/suite/tests/draft3/maxItems.json b/src/test/suite/tests/draft3/maxItems.json
new file mode 100644
index 0000000..3b53a6b
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/maxLength.json b/src/test/suite/tests/draft3/maxLength.json
new file mode 100644
index 0000000..b0a9ea5
--- /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/suite/tests/draft3/maximum.json b/src/test/suite/tests/draft3/maximum.json
new file mode 100644
index 0000000..ccb79c6
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/minItems.json b/src/test/suite/tests/draft3/minItems.json
new file mode 100644
index 0000000..ed51188
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/minLength.json b/src/test/suite/tests/draft3/minLength.json
new file mode 100644
index 0000000..6652c75
--- /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 0000000..d579536
--- /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 0000000..1bc8eb2
--- /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/ecmascript-regex.json b/src/test/suite/tests/draft3/optional/ecmascript-regex.json
new file mode 100644
index 0000000..03fe977
--- /dev/null
+++ b/src/test/suite/tests/draft3/optional/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/color.json b/src/test/suite/tests/draft3/optional/format/color.json
new file mode 100644
index 0000000..0c0b534
--- /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 0000000..1f1e6fb
--- /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 0000000..796bc46
--- /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/email.json b/src/test/suite/tests/draft3/optional/format/email.json
new file mode 100644
index 0000000..059615a
--- /dev/null
+++ b/src/test/suite/tests/draft3/optional/format/email.json
@@ -0,0 +1,53 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..d418f37
--- /dev/null
+++ b/src/test/suite/tests/draft3/optional/format/host-name.json
@@ -0,0 +1,63 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..91cac9f
--- /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 0000000..c3ef379
--- /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 0000000..8a37763
--- /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 0000000..36c823e
--- /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 0000000..f024b62
--- /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/suite/tests/draft3/optional/non-bmp-regex.json b/src/test/suite/tests/draft3/optional/non-bmp-regex.json
new file mode 100644
index 0000000..dd67af2
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/optional/zeroTerminatedFloats.json b/src/test/suite/tests/draft3/optional/zeroTerminatedFloats.json
new file mode 100644
index 0000000..9b50ea2
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/pattern.json b/src/test/suite/tests/draft3/pattern.json
new file mode 100644
index 0000000..92db0f9
--- /dev/null
+++ b/src/test/suite/tests/draft3/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/draft3/patternProperties.json b/src/test/suite/tests/draft3/patternProperties.json
new file mode 100644
index 0000000..b0f2a8e
--- /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 0000000..cd23801
--- /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 0000000..609eaa4
--- /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 0000000..0e4ab53
--- /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/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/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 0000000..aaaf024
--- /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 0000000..8447bc8
--- /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 0000000..c48c6a0
--- /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 0000000..c9e6815
--- /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 0000000..0f8e162
--- /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/suite/tests/draft4/allOf.json b/src/test/suite/tests/draft4/allOf.json
new file mode 100644
index 0000000..fc7dec5
--- /dev/null
+++ b/src/test/suite/tests/draft4/allOf.json
@@ -0,0 +1,261 @@
+[
+ {
+ "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 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/draft4/anyOf.json b/src/test/suite/tests/draft4/anyOf.json
new file mode 100644
index 0000000..09cc3c2
--- /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 0000000..289a9b6
--- /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/suite/tests/draft4/definitions.json b/src/test/suite/tests/draft4/definitions.json
new file mode 100644
index 0000000..482823b
--- /dev/null
+++ b/src/test/suite/tests/draft4/definitions.json
@@ -0,0 +1,26 @@
+[
+ {
+ "description": "validate definition against metaschema",
+ "schema": {"$ref": "http://json-schema.org/draft-04/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/draft4/dependencies.json b/src/test/suite/tests/draft4/dependencies.json
new file mode 100644
index 0000000..9045ddc
--- /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 0000000..ce43acc
--- /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/suite/tests/draft4/format.json b/src/test/suite/tests/draft4/format.json
new file mode 100644
index 0000000..5bd83cc
--- /dev/null
+++ b/src/test/suite/tests/draft4/format.json
@@ -0,0 +1,218 @@
+[
+ {
+ "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": "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/draft4/infinite-loop-detection.json b/src/test/suite/tests/draft4/infinite-loop-detection.json
new file mode 100644
index 0000000..f98c74f
--- /dev/null
+++ b/src/test/suite/tests/draft4/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/draft4/items.json b/src/test/suite/tests/draft4/items.json
new file mode 100644
index 0000000..16ea070
--- /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 0000000..3b53a6b
--- /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 0000000..3387959
--- /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/suite/tests/draft4/maxProperties.json b/src/test/suite/tests/draft4/maxProperties.json
new file mode 100644
index 0000000..aa7209f
--- /dev/null
+++ b/src/test/suite/tests/draft4/maxProperties.json
@@ -0,0 +1,54 @@
+[
+ {
+ "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 = 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/draft4/maximum.json b/src/test/suite/tests/draft4/maximum.json
new file mode 100644
index 0000000..ccb79c6
--- /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 0000000..ed51188
--- /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 0000000..6652c75
--- /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/suite/tests/draft4/minProperties.json b/src/test/suite/tests/draft4/minProperties.json
new file mode 100644
index 0000000..49a0726
--- /dev/null
+++ b/src/test/suite/tests/draft4/minProperties.json
@@ -0,0 +1,38 @@
+[
+ {
+ "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/suite/tests/draft4/minimum.json b/src/test/suite/tests/draft4/minimum.json
new file mode 100644
index 0000000..22d310e
--- /dev/null
+++ b/src/test/suite/tests/draft4/minimum.json
@@ -0,0 +1,114 @@
+[
+ {
+ "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 (explicit false exclusivity)",
+ "schema": {"minimum": 1.1, "exclusiveMinimum": false},
+ "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/draft4/multipleOf.json b/src/test/suite/tests/draft4/multipleOf.json
new file mode 100644
index 0000000..ed2df4a
--- /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 0000000..525219c
--- /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 0000000..fb63b08
--- /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 0000000..1bc8eb2
--- /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 0000000..c431bac
--- /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/suite/tests/draft4/optional/float-overflow.json b/src/test/suite/tests/draft4/optional/float-overflow.json
new file mode 100644
index 0000000..47fd5ba
--- /dev/null
+++ b/src/test/suite/tests/draft4/optional/float-overflow.json
@@ -0,0 +1,13 @@
+[
+ {
+ "description": "all integers are multiples of 0.5, if overflow is handled",
+ "schema": {"type": "number", "multipleOf": 0.5},
+ "tests": [
+ {
+ "description": "valid if optional overflow handling is implemented",
+ "data": 1e308,
+ "valid": true
+ }
+ ]
+ }
+]
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 0000000..0911273
--- /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 0000000..d6761a4
--- /dev/null
+++ b/src/test/suite/tests/draft4/optional/format/email.json
@@ -0,0 +1,83 @@
+[
+ {
+ "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/suite/tests/draft4/optional/format/hostname.json b/src/test/suite/tests/draft4/optional/format/hostname.json
new file mode 100644
index 0000000..a8ecd19
--- /dev/null
+++ b/src/test/suite/tests/draft4/optional/format/hostname.json
@@ -0,0 +1,118 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..9680fe6
--- /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 0000000..94368f2
--- /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/suite/tests/draft4/optional/format/unknown.json b/src/test/suite/tests/draft4/optional/format/unknown.json
new file mode 100644
index 0000000..12339ae
--- /dev/null
+++ b/src/test/suite/tests/draft4/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/draft4/optional/format/uri.json b/src/test/suite/tests/draft4/optional/format/uri.json
new file mode 100644
index 0000000..4b48d40
--- /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/suite/tests/draft4/optional/id.json b/src/test/suite/tests/draft4/optional/id.json
new file mode 100644
index 0000000..1c91d33
--- /dev/null
+++ b/src/test/suite/tests/draft4/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": {
+ "definitions": {
+ "id_in_enum": {
+ "enum": [
+ {
+ "id": "https://localhost:1234/my_identifier.json",
+ "type": "null"
+ }
+ ]
+ },
+ "real_id_in_schema": {
+ "id": "https://localhost:1234/my_identifier.json",
+ "type": "string"
+ },
+ "zzz_id_in_const": {
+ "const": {
+ "id": "https://localhost:1234/my_identifier.json",
+ "type": "null"
+ }
+ }
+ },
+ "anyOf": [
+ { "$ref": "#/definitions/id_in_enum" },
+ { "$ref": "https://localhost:1234/my_identifier.json" }
+ ]
+ },
+ "tests": [
+ {
+ "description": "exact match to enum, and type matches",
+ "data": {
+ "id": "https://localhost:1234/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
+ }
+ ]
+ }
+
+]
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 0000000..dd67af2
--- /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 0000000..9b50ea2
--- /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 0000000..92db0f9
--- /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 0000000..51c8af3
--- /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 0000000..195159e
--- /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 0000000..b53bd2a
--- /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 0000000..64a618b
--- /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/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/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": "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/locationIndependentIdentifierDraft4.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 0000000..6ccfdc2
--- /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/suite/tests/draft4/type.json b/src/test/suite/tests/draft4/type.json
new file mode 100644
index 0000000..df46677
--- /dev/null
+++ b/src/test/suite/tests/draft4/type.json
@@ -0,0 +1,469 @@
+[
+ {
+ "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 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/draft4/uniqueItems.json b/src/test/suite/tests/draft4/uniqueItems.json
new file mode 100644
index 0000000..d2730c6
--- /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 0000000..2c7d155
--- /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 0000000..0f8e162
--- /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 0000000..ec9319e
--- /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 0000000..ab5eb38
--- /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 0000000..6d40f23
--- /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 0000000..1c2cafc
--- /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 0000000..bd93654
--- /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 0000000..289a9b6
--- /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 0000000..d772fde
--- /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 0000000..c0bd809
--- /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 0000000..ce43acc
--- /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 0000000..dc3cd70
--- /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 0000000..b38d7ec
--- /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 0000000..2df2a9f
--- /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 0000000..f98c74f
--- /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 0000000..7ed6781
--- /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 0000000..f0c36ab
--- /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 0000000..be60c54
--- /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 0000000..acec142
--- /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 0000000..6844a39
--- /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 0000000..d3b1872
--- /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 0000000..23c68fe
--- /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 0000000..9f74f78
--- /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 0000000..21ae50e
--- /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 0000000..e606979
--- /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 0000000..b46c4ed
--- /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 0000000..eeb7ae8
--- /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 0000000..94b4a4e
--- /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 0000000..c4886aa
--- /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 0000000..52ff982
--- /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 0000000..0911273
--- /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 0000000..d6761a4
--- /dev/null
+++ b/src/test/suite/tests/draft6/optional/format/email.json
@@ -0,0 +1,83 @@
+[
+ {
+ "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/suite/tests/draft6/optional/format/hostname.json b/src/test/suite/tests/draft6/optional/format/hostname.json
new file mode 100644
index 0000000..a8ecd19
--- /dev/null
+++ b/src/test/suite/tests/draft6/optional/format/hostname.json
@@ -0,0 +1,118 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..9680fe6
--- /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 0000000..94368f2
--- /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 0000000..a0346b5
--- /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 0000000..12339ae
--- /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 0000000..7cdf228
--- /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 0000000..df355c5
--- /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 0000000..4b48d40
--- /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 0000000..03d30fc
--- /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 0000000..dd67af2
--- /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 0000000..1f58d97
--- /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 0000000..92db0f9
--- /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 0000000..c276e64
--- /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 0000000..5b971ca
--- /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 0000000..f0788e6
--- /dev/null
+++ b/src/test/suite/tests/draft6/propertyNames.json
@@ -0,0 +1,107 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
diff --git a/src/test/suite/tests/draft6/ref.json b/src/test/suite/tests/draft6/ref.json
new file mode 100644
index 0000000..379322c
--- /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 0000000..28459c4
--- /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/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/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": "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": "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/locationIndependentIdentifierPre2019.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 0000000..8d8087a
--- /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 0000000..8304647
--- /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 0000000..d2730c6
--- /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 0000000..2c7d155
--- /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 0000000..0f8e162
--- /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 0000000..ec9319e
--- /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 0000000..ab5eb38
--- /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 0000000..6d40f23
--- /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 0000000..1c2cafc
--- /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 0000000..2b1a515
--- /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 0000000..289a9b6
--- /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 0000000..afe396e
--- /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 0000000..c0bd809
--- /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 0000000..ce43acc
--- /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 0000000..dc3cd70
--- /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 0000000..b38d7ec
--- /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 0000000..e2447d6
--- /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 0000000..284e919
--- /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 0000000..f98c74f
--- /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 0000000..7ed6781
--- /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 0000000..f0c36ab
--- /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 0000000..be60c54
--- /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 0000000..acec142
--- /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 0000000..6844a39
--- /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 0000000..d3b1872
--- /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 0000000..23c68fe
--- /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 0000000..9f74f78
--- /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 0000000..21ae50e
--- /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 0000000..e606979
--- /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 0000000..b46c4ed
--- /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 0000000..eeb7ae8
--- /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 0000000..94b4a4e
--- /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 0000000..3f5a743
--- /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 0000000..8ff5373
--- /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 0000000..c4886aa
--- /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 0000000..52ff982
--- /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 0000000..0911273
--- /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 0000000..d723124
--- /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 0000000..d6761a4
--- /dev/null
+++ b/src/test/suite/tests/draft7/optional/format/email.json
@@ -0,0 +1,83 @@
+[
+ {
+ "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/suite/tests/draft7/optional/format/hostname.json b/src/test/suite/tests/draft7/optional/format/hostname.json
new file mode 100644
index 0000000..a8ecd19
--- /dev/null
+++ b/src/test/suite/tests/draft7/optional/format/hostname.json
@@ -0,0 +1,118 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
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 0000000..6e21374
--- /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 0000000..dc47f7b
--- /dev/null
+++ b/src/test/suite/tests/draft7/optional/format/idn-hostname.json
@@ -0,0 +1,324 @@
+[
+ {
+ "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": "\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
+ }
+ ]
+ }
+]
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 0000000..9680fe6
--- /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 0000000..94368f2
--- /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 0000000..c6b4c22
--- /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 0000000..a0d12ae
--- /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 0000000..a0346b5
--- /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 0000000..3449177
--- /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 0000000..e50e505
--- /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 0000000..014ecd8
--- /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 0000000..12339ae
--- /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 0000000..7cdf228
--- /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 0000000..df355c5
--- /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 0000000..4b48d40
--- /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 0000000..6be81b8
--- /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 0000000..dd67af2
--- /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 0000000..1f58d97
--- /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 0000000..92db0f9
--- /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 0000000..c276e64
--- /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 0000000..5b971ca
--- /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 0000000..f0788e6
--- /dev/null
+++ b/src/test/suite/tests/draft7/propertyNames.json
@@ -0,0 +1,107 @@
+[
+ {
+ "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
+ }
+ ]
+ }
+]
diff --git a/src/test/suite/tests/draft7/ref.json b/src/test/suite/tests/draft7/ref.json
new file mode 100644
index 0000000..82e1e16
--- /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 0000000..22185d6
--- /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/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/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": "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": "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/locationIndependentIdentifierPre2019.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 0000000..8d8087a
--- /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 0000000..8304647
--- /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 0000000..d2730c6
--- /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/tox.ini b/src/test/suite/tox.ini
new file mode 100644
index 0000000..dcc0dce
--- /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