Skip to content

Commit 8f277d1

Browse files
authored
Move references validation to the latter stage (#45)
There is no guarantee that all schemes will be registered in the order of usage (schema with dependency after those dependencies). Because of that I have moved the references validation to the latter stage when the actual schema that will be used for validation is created
1 parent 17644f9 commit 8f277d1

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private const val SCHEMA_PROPERTY: String = "\$schema"
2626

2727
internal class SchemaLoader : JsonSchemaLoader {
2828
private val references: MutableMap<RefId, AssertionWithPath> = linkedMapOf()
29-
private val usedRefs: MutableSet<RefId> = linkedSetOf()
29+
private val usedRefs: MutableSet<ReferenceLocation> = linkedSetOf()
3030

3131
override fun register(
3232
schema: JsonElement,
@@ -67,11 +67,12 @@ internal class SchemaLoader : JsonSchemaLoader {
6767
draft: SchemaType?,
6868
): JsonSchema {
6969
val assertion: JsonSchemaAssertion = loadSchemaData(schemaElement, draft, references, usedRefs)
70+
validateReferences(references, usedRefs)
7071
return createSchema(
7172
LoadResult(
7273
assertion,
7374
references.toMutableMap(),
74-
usedRefs.toMutableSet(),
75+
usedRefs.mapTo(hashSetOf()) { it.refId },
7576
),
7677
)
7778
}
@@ -107,17 +108,18 @@ internal object IsolatedLoader : JsonSchemaLoader {
107108
draft: SchemaType?,
108109
): JsonSchema {
109110
val references: MutableMap<RefId, AssertionWithPath> = linkedMapOf()
110-
val usedRefs: MutableSet<RefId> = hashSetOf()
111+
val usedRefs: MutableSet<ReferenceLocation> = hashSetOf()
111112
val assertion: JsonSchemaAssertion = loadSchemaData(schemaElement, draft, references, usedRefs)
112-
return createSchema(LoadResult(assertion, references, usedRefs))
113+
validateReferences(references, usedRefs)
114+
return createSchema(LoadResult(assertion, references, usedRefs.mapTo(hashSetOf()) { it.refId }))
113115
}
114116
}
115117

116118
private fun loadSchemaData(
117119
schemaDefinition: JsonElement,
118120
defaultType: SchemaType?,
119121
references: MutableMap<RefId, AssertionWithPath>,
120-
usedRefs: MutableSet<RefId>,
122+
usedRefs: MutableSet<ReferenceLocation>,
121123
externalUri: Uri? = null,
122124
): JsonSchemaAssertion {
123125
val schemaType = extractSchemaType(schemaDefinition, defaultType)
@@ -138,12 +140,18 @@ private fun loadSchemaData(
138140
}
139141
val schemaAssertion = loadSchema(schemaDefinition, context)
140142
references.putAll(isolatedReferences)
141-
context.usedRef.mapTo(usedRefs) { it.refId }
143+
usedRefs.addAll(context.usedRef)
144+
return schemaAssertion
145+
}
146+
147+
private fun validateReferences(
148+
references: Map<RefId, AssertionWithPath>,
149+
usedRefs: Set<ReferenceLocation>,
150+
) {
142151
ReferenceValidator.validateReferences(
143152
references.mapValues { it.value.run { PointerWithBaseId(this.baseId, schemaPath) } },
144-
context.usedRef,
153+
usedRefs,
145154
)
146-
return schemaAssertion
147155
}
148156

149157
private fun createSchema(result: LoadResult): JsonSchema {

src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaLoaderTest.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import io.github.optimumcode.json.schema.ErrorCollector
55
import io.github.optimumcode.json.schema.JsonSchemaLoader
66
import io.github.optimumcode.json.schema.ValidationError
77
import io.kotest.assertions.assertSoftly
8+
import io.kotest.assertions.throwables.shouldNotThrowAnyUnit
9+
import io.kotest.assertions.throwables.shouldThrow
810
import io.kotest.assertions.withClue
911
import io.kotest.core.spec.style.FunSpec
1012
import io.kotest.matchers.collections.shouldContainExactly
@@ -161,5 +163,53 @@ class JsonSchemaLoaderTest : FunSpec() {
161163
}
162164
}
163165
}
166+
167+
test("does not report missing refs when schema is registered") {
168+
shouldNotThrowAnyUnit {
169+
JsonSchemaLoader.create()
170+
.register(
171+
"""
172+
{
173+
"id": "https://test.com/a",
174+
"properties": {
175+
"anotherName": {
176+
"${KEY}ref": "https://test.com/b#/properties/name"
177+
}
178+
}
179+
}
180+
""".trimIndent(),
181+
)
182+
}
183+
}
184+
185+
test("reports missing refs when schema is created from definition") {
186+
shouldThrow<IllegalArgumentException> {
187+
JsonSchemaLoader.create()
188+
.register(
189+
"""
190+
{
191+
"${KEY}id": "https://test.com/a",
192+
"properties": {
193+
"anotherName": {
194+
"${KEY}ref": "https://test.com/b#/properties/name"
195+
}
196+
}
197+
}
198+
""".trimIndent(),
199+
).fromDefinition(
200+
"""
201+
{
202+
"${KEY}id": "https://test.com/c",
203+
"properties": {
204+
"subName": {
205+
"${KEY}ref": "https://test.com/a#/properties/anotherName"
206+
}
207+
}
208+
}
209+
""".trimIndent(),
210+
)
211+
}.message shouldBe "cannot resolve references: " +
212+
"{\"https://test.com/b#/properties/name\": [\"/properties/anotherName\"]}"
213+
}
164214
}
165215
}

test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private fun FunSpec.executeFromDirectory(
133133
JsonSchemaLoader.create()
134134
.apply {
135135
SchemaType.entries.forEach(::registerWellKnown)
136-
for ((uri, schema) in remoteSchemas.entries.reversed()) {
136+
for ((uri, schema) in remoteSchemas) {
137137
if (uri.contains("draft4", ignoreCase = true)) {
138138
// skip draft4 schemas
139139
continue

0 commit comments

Comments
 (0)