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 a32c0218..4019e038 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 @@ -26,7 +26,7 @@ private const val SCHEMA_PROPERTY: String = "\$schema" internal class SchemaLoader : JsonSchemaLoader { private val references: MutableMap = linkedMapOf() - private val usedRefs: MutableSet = linkedSetOf() + private val usedRefs: MutableSet = linkedSetOf() override fun register( schema: JsonElement, @@ -67,11 +67,12 @@ internal class SchemaLoader : JsonSchemaLoader { draft: SchemaType?, ): JsonSchema { val assertion: JsonSchemaAssertion = loadSchemaData(schemaElement, draft, references, usedRefs) + validateReferences(references, usedRefs) return createSchema( LoadResult( assertion, references.toMutableMap(), - usedRefs.toMutableSet(), + usedRefs.mapTo(hashSetOf()) { it.refId }, ), ) } @@ -107,9 +108,10 @@ internal object IsolatedLoader : JsonSchemaLoader { draft: SchemaType?, ): JsonSchema { val references: MutableMap = linkedMapOf() - val usedRefs: MutableSet = hashSetOf() + val usedRefs: MutableSet = hashSetOf() val assertion: JsonSchemaAssertion = loadSchemaData(schemaElement, draft, references, usedRefs) - return createSchema(LoadResult(assertion, references, usedRefs)) + validateReferences(references, usedRefs) + return createSchema(LoadResult(assertion, references, usedRefs.mapTo(hashSetOf()) { it.refId })) } } @@ -117,7 +119,7 @@ private fun loadSchemaData( schemaDefinition: JsonElement, defaultType: SchemaType?, references: MutableMap, - usedRefs: MutableSet, + usedRefs: MutableSet, externalUri: Uri? = null, ): JsonSchemaAssertion { val schemaType = extractSchemaType(schemaDefinition, defaultType) @@ -138,12 +140,18 @@ private fun loadSchemaData( } val schemaAssertion = loadSchema(schemaDefinition, context) references.putAll(isolatedReferences) - context.usedRef.mapTo(usedRefs) { it.refId } + usedRefs.addAll(context.usedRef) + return schemaAssertion +} + +private fun validateReferences( + references: Map, + usedRefs: Set, +) { ReferenceValidator.validateReferences( references.mapValues { it.value.run { PointerWithBaseId(this.baseId, schemaPath) } }, - context.usedRef, + usedRefs, ) - return schemaAssertion } private fun createSchema(result: LoadResult): JsonSchema { diff --git a/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaLoaderTest.kt b/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaLoaderTest.kt index 79526546..cf81ba62 100644 --- a/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaLoaderTest.kt +++ b/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaLoaderTest.kt @@ -5,6 +5,8 @@ import io.github.optimumcode.json.schema.ErrorCollector import io.github.optimumcode.json.schema.JsonSchemaLoader import io.github.optimumcode.json.schema.ValidationError import io.kotest.assertions.assertSoftly +import io.kotest.assertions.throwables.shouldNotThrowAnyUnit +import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactly @@ -161,5 +163,53 @@ class JsonSchemaLoaderTest : FunSpec() { } } } + + test("does not report missing refs when schema is registered") { + shouldNotThrowAnyUnit { + JsonSchemaLoader.create() + .register( + """ + { + "id": "https://test.com/a", + "properties": { + "anotherName": { + "${KEY}ref": "https://test.com/b#/properties/name" + } + } + } + """.trimIndent(), + ) + } + } + + test("reports missing refs when schema is created from definition") { + shouldThrow { + JsonSchemaLoader.create() + .register( + """ + { + "${KEY}id": "https://test.com/a", + "properties": { + "anotherName": { + "${KEY}ref": "https://test.com/b#/properties/name" + } + } + } + """.trimIndent(), + ).fromDefinition( + """ + { + "${KEY}id": "https://test.com/c", + "properties": { + "subName": { + "${KEY}ref": "https://test.com/a#/properties/anotherName" + } + } + } + """.trimIndent(), + ) + }.message shouldBe "cannot resolve references: " + + "{\"https://test.com/b#/properties/name\": [\"/properties/anotherName\"]}" + } } } \ No newline at end of file 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 6d26ba1b..4c7aeb06 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 @@ -133,7 +133,7 @@ private fun FunSpec.executeFromDirectory( JsonSchemaLoader.create() .apply { SchemaType.entries.forEach(::registerWellKnown) - for ((uri, schema) in remoteSchemas.entries.reversed()) { + for ((uri, schema) in remoteSchemas) { if (uri.contains("draft4", ignoreCase = true)) { // skip draft4 schemas continue