From b2f1e9a91a802ee19b6a6ab506f6d7f041df7c6a Mon Sep 17 00:00:00 2001 From: Oleg Smirnov Date: Wed, 19 Jun 2024 11:10:00 +0400 Subject: [PATCH 1/2] Add support for draft 4 --- .../json/schema/JsonSchemaLoader.kt | 3 + .../optimumcode/json/schema/SchemaType.kt | 2 + .../json/schema/internal/SchemaLoader.kt | 12 +- .../config/Draft4SchemaLoaderConfig.kt | 137 +++++++++++++++ .../number/Draft4MaximumAssertionFactory.kt | 57 +++++++ .../number/Draft4MinimumAssertionFactory.kt | 57 +++++++ .../json/schema/internal/wellknown/Draft4.kt | 159 ++++++++++++++++++ .../extension/JsonSchemaExtensionTest.kt | 4 +- .../schema/suite/AbstractSchemaTestSuite.kt | 7 +- .../json/schema/suite/draft4/TestSuite.kt | 17 ++ 10 files changed, 450 insertions(+), 5 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/config/Draft4SchemaLoaderConfig.kt create mode 100644 src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MaximumAssertionFactory.kt create mode 100644 src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MinimumAssertionFactory.kt create mode 100644 src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/wellknown/Draft4.kt create mode 100644 test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/draft4/TestSuite.kt diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt index d2751fcb..06005cf3 100644 --- a/src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt @@ -3,12 +3,14 @@ package io.github.optimumcode.json.schema import com.eygraber.uri.Uri import io.github.optimumcode.json.schema.SchemaType.DRAFT_2019_09 import io.github.optimumcode.json.schema.SchemaType.DRAFT_2020_12 +import io.github.optimumcode.json.schema.SchemaType.DRAFT_4 import io.github.optimumcode.json.schema.SchemaType.DRAFT_6 import io.github.optimumcode.json.schema.SchemaType.DRAFT_7 import io.github.optimumcode.json.schema.extension.ExternalAssertionFactory import io.github.optimumcode.json.schema.internal.SchemaLoader import io.github.optimumcode.json.schema.internal.wellknown.Draft201909 import io.github.optimumcode.json.schema.internal.wellknown.Draft202012 +import io.github.optimumcode.json.schema.internal.wellknown.Draft4 import io.github.optimumcode.json.schema.internal.wellknown.Draft6 import io.github.optimumcode.json.schema.internal.wellknown.Draft7 import kotlinx.serialization.json.JsonElement @@ -19,6 +21,7 @@ public interface JsonSchemaLoader { public fun registerWellKnown(draft: SchemaType): JsonSchemaLoader = apply { when (draft) { + DRAFT_4 -> Draft4.entries.forEach { register(it.content) } DRAFT_6 -> Draft6.entries.forEach { register(it.content) } DRAFT_7 -> Draft7.entries.forEach { register(it.content) } DRAFT_2019_09 -> Draft201909.entries.forEach { register(it.content) } diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/SchemaType.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/SchemaType.kt index 67b6cc88..c3bfa75b 100644 --- a/src/commonMain/kotlin/io/github/optimumcode/json/schema/SchemaType.kt +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/SchemaType.kt @@ -4,6 +4,7 @@ import com.eygraber.uri.Uri import io.github.optimumcode.json.schema.internal.SchemaLoaderConfig import io.github.optimumcode.json.schema.internal.config.Draft201909SchemaLoaderConfig import io.github.optimumcode.json.schema.internal.config.Draft202012SchemaLoaderConfig +import io.github.optimumcode.json.schema.internal.config.Draft4SchemaLoaderConfig import io.github.optimumcode.json.schema.internal.config.Draft6SchemaLoaderConfig import io.github.optimumcode.json.schema.internal.config.Draft7SchemaLoaderConfig import kotlin.jvm.JvmStatic @@ -12,6 +13,7 @@ public enum class SchemaType( internal val schemaId: Uri, internal val config: SchemaLoaderConfig, ) { + DRAFT_4(Uri.parse("http://json-schema.org/draft-04/schema"), Draft4SchemaLoaderConfig), DRAFT_6(Uri.parse("http://json-schema.org/draft-06/schema"), Draft6SchemaLoaderConfig), DRAFT_7(Uri.parse("http://json-schema.org/draft-07/schema"), Draft7SchemaLoaderConfig), DRAFT_2019_09(Uri.parse("https://json-schema.org/draft/2019-09/schema"), Draft201909SchemaLoaderConfig), diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt index 11b7ccbc..68390a61 100644 --- a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt @@ -157,15 +157,25 @@ internal class SchemaLoader : JsonSchemaLoader { ) private fun addExtensionFactory(extensionFactory: ExternalAssertionFactory) { + val matchedDrafts = mutableMapOf>() for (schemaType in SchemaType.entries) { val match = schemaType.config.allFactories.find { it.property.equals(extensionFactory.keywordName, ignoreCase = true) } if (match == null) { continue } + matchedDrafts + .getOrPut( + match.property, + ::ArrayList, + ).add(schemaType) + } + if (matchedDrafts.isNotEmpty()) { error( "external factory with keyword '${extensionFactory.keywordName}' " + - "overlaps with '${match.property}' keyword from $schemaType", + "overlaps with ${matchedDrafts.entries.joinToString { (property, drafts) -> + "'$property' keyword in $drafts draft(s)" + }}", ) } val duplicate = extensionFactories.keys.find { it.equals(extensionFactory.keywordName, ignoreCase = true) } diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/config/Draft4SchemaLoaderConfig.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/config/Draft4SchemaLoaderConfig.kt new file mode 100644 index 00000000..ade3232a --- /dev/null +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/config/Draft4SchemaLoaderConfig.kt @@ -0,0 +1,137 @@ +package io.github.optimumcode.json.schema.internal.config + +import io.github.optimumcode.json.schema.FormatBehavior +import io.github.optimumcode.json.schema.SchemaOption +import io.github.optimumcode.json.schema.internal.AssertionFactory +import io.github.optimumcode.json.schema.internal.KeyWord +import io.github.optimumcode.json.schema.internal.KeyWord.ANCHOR +import io.github.optimumcode.json.schema.internal.KeyWord.COMPATIBILITY_DEFINITIONS +import io.github.optimumcode.json.schema.internal.KeyWord.DEFINITIONS +import io.github.optimumcode.json.schema.internal.KeyWord.DYNAMIC_ANCHOR +import io.github.optimumcode.json.schema.internal.KeyWord.ID +import io.github.optimumcode.json.schema.internal.KeyWordResolver +import io.github.optimumcode.json.schema.internal.ReferenceFactory +import io.github.optimumcode.json.schema.internal.ReferenceFactory.RefHolder +import io.github.optimumcode.json.schema.internal.SchemaLoaderConfig +import io.github.optimumcode.json.schema.internal.SchemaLoaderContext +import io.github.optimumcode.json.schema.internal.config.Draft4KeyWordResolver.REF_PROPERTY +import io.github.optimumcode.json.schema.internal.factories.array.AdditionalItemsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.array.ContainsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.array.MaxItemsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.array.MinItemsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.array.UniqueItemsAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.condition.AllOfAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.condition.AnyOfAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.condition.NotAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.condition.OneOfAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.general.ConstAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.general.EnumAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.general.FormatAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.general.TypeAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.number.Draft4MaximumAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.number.Draft4MinimumAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.number.MinimumAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.number.MultipleOfAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.AdditionalPropertiesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.DependenciesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.MaxPropertiesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.MinPropertiesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.PatternPropertiesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.PropertiesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.PropertyNamesAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.`object`.RequiredAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.string.MaxLengthAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.string.MinLengthAssertionFactory +import io.github.optimumcode.json.schema.internal.factories.string.PatternAssertionFactory +import io.github.optimumcode.json.schema.internal.util.getStringRequired +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject + +internal object Draft4SchemaLoaderConfig : SchemaLoaderConfig { + private val factories: List = + listOf( + TypeAssertionFactory, + EnumAssertionFactory, + ConstAssertionFactory, + MultipleOfAssertionFactory, + Draft4MaximumAssertionFactory, + Draft4MinimumAssertionFactory, + MinimumAssertionFactory, + MaxLengthAssertionFactory, + MinLengthAssertionFactory, + PatternAssertionFactory, + ItemsAssertionFactory, + AdditionalItemsAssertionFactory, + MaxItemsAssertionFactory, + MinItemsAssertionFactory, + UniqueItemsAssertionFactory, + ContainsAssertionFactory, + MaxPropertiesAssertionFactory, + MinPropertiesAssertionFactory, + RequiredAssertionFactory, + PropertiesAssertionFactory, + PatternPropertiesAssertionFactory, + AdditionalPropertiesAssertionFactory, + PropertyNamesAssertionFactory, + DependenciesAssertionFactory, + AllOfAssertionFactory, + AnyOfAssertionFactory, + OneOfAssertionFactory, + NotAssertionFactory, + ) + + override val defaultVocabulary: SchemaLoaderConfig.Vocabulary = SchemaLoaderConfig.Vocabulary() + override val allFactories: List + get() = factories + + override fun createVocabulary(schemaDefinition: JsonElement): SchemaLoaderConfig.Vocabulary? = null + + override fun factories( + schemaDefinition: JsonElement, + vocabulary: SchemaLoaderConfig.Vocabulary, + options: SchemaLoaderConfig.Options, + ): List = + factories + + when (options[SchemaOption.FORMAT_BEHAVIOR_OPTION]) { + null, FormatBehavior.ANNOTATION_AND_ASSERTION -> FormatAssertionFactory.AnnotationAndAssertion + FormatBehavior.ANNOTATION_ONLY -> FormatAssertionFactory.AnnotationOnly + } + + override val keywordResolver: KeyWordResolver + get() = Draft4KeyWordResolver + override val referenceFactory: ReferenceFactory + get() = Draft4ReferenceFactory +} + +private object Draft4KeyWordResolver : KeyWordResolver { + private const val DEFINITIONS_PROPERTY: String = "definitions" + private const val ID_PROPERTY: String = "id" + const val REF_PROPERTY: String = "\$ref" + + override fun resolve(keyword: KeyWord): String? = + when (keyword) { + ID -> ID_PROPERTY + DEFINITIONS -> DEFINITIONS_PROPERTY + ANCHOR, COMPATIBILITY_DEFINITIONS, DYNAMIC_ANCHOR -> null + } +} + +private object Draft4ReferenceFactory : ReferenceFactory { + override fun extractRef( + schemaDefinition: JsonObject, + context: SchemaLoaderContext, + ): RefHolder? = + if (REF_PROPERTY in schemaDefinition) { + RefHolder.Simple(REF_PROPERTY, schemaDefinition.getStringRequired(REF_PROPERTY).let(context::ref)) + } else { + null + } + + override val allowOverriding: Boolean + get() = false + override val resolveRefPriorId: Boolean + get() = false + + override fun recursiveResolutionEnabled(schemaDefinition: JsonObject): Boolean = true +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MaximumAssertionFactory.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MaximumAssertionFactory.kt new file mode 100644 index 00000000..79be0619 --- /dev/null +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MaximumAssertionFactory.kt @@ -0,0 +1,57 @@ +package io.github.optimumcode.json.schema.internal.factories.number + +import io.github.optimumcode.json.schema.internal.AssertionFactory +import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion +import io.github.optimumcode.json.schema.internal.LoadingContext +import io.github.optimumcode.json.schema.internal.factories.number.util.NumberComparisonAssertion +import io.github.optimumcode.json.schema.internal.factories.number.util.compareTo +import io.github.optimumcode.json.schema.internal.factories.number.util.number +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull + +@Suppress("unused") +internal object Draft4MaximumAssertionFactory : AssertionFactory { + private const val EXCLUSIVE_MAXIMUM_PROPERTY = "exclusiveMaximum" + + override val property: String + get() = "maximum" + + override fun isApplicable(element: JsonElement): Boolean = element is JsonObject && element.contains(property) + + override fun create( + element: JsonElement, + context: LoadingContext, + ): JsonSchemaAssertion { + require(element is JsonObject) { "cannot extract $property property from ${element::class.simpleName}" } + val typeElement = requireNotNull(element[property]) { "no property $property found in element $element" } + val exclusive: Boolean = + element[EXCLUSIVE_MAXIMUM_PROPERTY]?.let { + require(it is JsonPrimitive) { "$EXCLUSIVE_MAXIMUM_PROPERTY must be a boolean" } + requireNotNull(it.booleanOrNull) { "$EXCLUSIVE_MAXIMUM_PROPERTY must be a valid boolean" } + } ?: false + return createFromProperty(typeElement, context.at(property), exclusive) + } + + private fun createFromProperty( + element: JsonElement, + context: LoadingContext, + exclusive: Boolean, + ): JsonSchemaAssertion { + require(element is JsonPrimitive) { "$property must be a number" } + val maximumValue: Number = + requireNotNull(element.number) { "$property must be a valid number" } + return NumberComparisonAssertion( + context.schemaPath, + maximumValue, + element.content, + errorMessage = if (exclusive) "must be less" else "must be less or equal to", + if (exclusive) { + { a, b -> a < b } + } else { + { a, b -> a <= b } + }, + ) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MinimumAssertionFactory.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MinimumAssertionFactory.kt new file mode 100644 index 00000000..bb37e10b --- /dev/null +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/number/Draft4MinimumAssertionFactory.kt @@ -0,0 +1,57 @@ +package io.github.optimumcode.json.schema.internal.factories.number + +import io.github.optimumcode.json.schema.internal.AssertionFactory +import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion +import io.github.optimumcode.json.schema.internal.LoadingContext +import io.github.optimumcode.json.schema.internal.factories.number.util.NumberComparisonAssertion +import io.github.optimumcode.json.schema.internal.factories.number.util.compareTo +import io.github.optimumcode.json.schema.internal.factories.number.util.number +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull + +@Suppress("unused") +internal object Draft4MinimumAssertionFactory : AssertionFactory { + private const val EXCLUSIVE_MINIMUM_PROPERTY = "exclusiveMinimum" + + override val property: String + get() = "minimum" + + override fun isApplicable(element: JsonElement): Boolean = element is JsonObject && element.contains(property) + + override fun create( + element: JsonElement, + context: LoadingContext, + ): JsonSchemaAssertion { + require(element is JsonObject) { "cannot extract $property property from ${element::class.simpleName}" } + val typeElement = requireNotNull(element[property]) { "no property $property found in element $element" } + val exclusive: Boolean = + element[EXCLUSIVE_MINIMUM_PROPERTY]?.let { + require(it is JsonPrimitive) { "$EXCLUSIVE_MINIMUM_PROPERTY must be a boolean" } + requireNotNull(it.booleanOrNull) { "$EXCLUSIVE_MINIMUM_PROPERTY must be a valid boolean" } + } ?: false + return createFromProperty(typeElement, context.at(property), exclusive) + } + + private fun createFromProperty( + element: JsonElement, + context: LoadingContext, + exclusive: Boolean, + ): JsonSchemaAssertion { + require(element is JsonPrimitive) { "$property must be a number" } + val maximumValue: Number = + requireNotNull(element.number) { "$property must be a valid number" } + return NumberComparisonAssertion( + context.schemaPath, + maximumValue, + element.content, + errorMessage = if (exclusive) "must be greater" else "must be greater or equal to", + if (exclusive) { + { a, b -> a > b } + } else { + { a, b -> a >= b } + }, + ) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/wellknown/Draft4.kt b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/wellknown/Draft4.kt new file mode 100644 index 00000000..def15651 --- /dev/null +++ b/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/wellknown/Draft4.kt @@ -0,0 +1,159 @@ +package io.github.optimumcode.json.schema.internal.wellknown + +internal enum class Draft4( + val content: String, +) { + SCHEMA( +""" +{ + "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": {} +} +""", + ), +} \ No newline at end of file diff --git a/src/commonTest/kotlin/io/github/optimumcode/json/schema/extension/JsonSchemaExtensionTest.kt b/src/commonTest/kotlin/io/github/optimumcode/json/schema/extension/JsonSchemaExtensionTest.kt index e4d8e686..93499e9f 100644 --- a/src/commonTest/kotlin/io/github/optimumcode/json/schema/extension/JsonSchemaExtensionTest.kt +++ b/src/commonTest/kotlin/io/github/optimumcode/json/schema/extension/JsonSchemaExtensionTest.kt @@ -25,7 +25,9 @@ class JsonSchemaExtensionTest : FunSpec() { JsonSchemaLoader .create() .withExtensions(DuplicatedAssertionFactory) - }.message shouldBe "external factory with keyword 'type' overlaps with 'type' keyword from DRAFT_6" + }.message shouldBe + "external factory with keyword 'type' overlaps with " + + "'type' keyword in [DRAFT_4, DRAFT_6, DRAFT_7, DRAFT_2019_09, DRAFT_2020_12] draft(s)" } test("reports duplicated extension keywords") { diff --git a/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt b/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt index 8031b85e..874e3135 100644 --- a/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt +++ b/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt @@ -202,9 +202,10 @@ private fun FunSpec.executeFromDirectory( } SchemaType.entries.forEach(::registerWellKnown) for ((uri, schema) in remoteSchemas) { + var remoteSchemaType: SchemaType? = null + // schema for draft 4 does not have $schema inside the definition if (uri.toString().contains("draft4", ignoreCase = true)) { - // skip draft4 schemas - continue + remoteSchemaType = SchemaType.DRAFT_4 } if (schema is JsonObject && schema["\$schema"]?.jsonPrimitive?.content.let { it != null && SchemaType.find(it) == null } @@ -212,7 +213,7 @@ private fun FunSpec.executeFromDirectory( continue } try { - register(schema, uri) + register(schema, uri, remoteSchemaType) } catch (ex: Exception) { throw IllegalStateException("cannot load schema with uri '$uri'", ex) } diff --git a/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/draft4/TestSuite.kt b/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/draft4/TestSuite.kt new file mode 100644 index 00000000..9248c80b --- /dev/null +++ b/test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/draft4/TestSuite.kt @@ -0,0 +1,17 @@ +package io.github.optimumcode.json.schema.suite.draft4 + +import io.github.optimumcode.json.schema.SchemaType.DRAFT_4 +import io.github.optimumcode.json.schema.suite.COMMON_FORMAT_FILTER +import io.github.optimumcode.json.schema.suite.runTestSuites +import io.kotest.core.spec.style.FunSpec + +@Suppress("unused") +internal class TestSuite : FunSpec() { + init { + runTestSuites( + draftName = "draft4", + schemaType = DRAFT_4, + formatFilter = COMMON_FORMAT_FILTER, + ) + } +} \ No newline at end of file From 5b32accbe236f368f8597b66a95c5b2dafa26074 Mon Sep 17 00:00:00 2001 From: Oleg Smirnov Date: Wed, 19 Jun 2024 11:16:48 +0400 Subject: [PATCH 2/2] Update api dump --- api/json-schema-validator.api | 1 + 1 file changed, 1 insertion(+) diff --git a/api/json-schema-validator.api b/api/json-schema-validator.api index ab770f8a..ac63b4e5 100644 --- a/api/json-schema-validator.api +++ b/api/json-schema-validator.api @@ -195,6 +195,7 @@ public final class io/github/optimumcode/json/schema/SchemaType : java/lang/Enum public static final field Companion Lio/github/optimumcode/json/schema/SchemaType$Companion; public static final field DRAFT_2019_09 Lio/github/optimumcode/json/schema/SchemaType; public static final field DRAFT_2020_12 Lio/github/optimumcode/json/schema/SchemaType; + public static final field DRAFT_4 Lio/github/optimumcode/json/schema/SchemaType; public static final field DRAFT_6 Lio/github/optimumcode/json/schema/SchemaType; public static final field DRAFT_7 Lio/github/optimumcode/json/schema/SchemaType; public static final fun find (Ljava/lang/String;)Lio/github/optimumcode/json/schema/SchemaType;