Skip to content

Commit e458073

Browse files
authored
Fix algorithm for dynamic reference resolution (#101)
Resolves #100
1 parent e352695 commit e458073

File tree

6 files changed

+20
-39
lines changed

6 files changed

+20
-39
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal interface AssertionContext : ExternalAssertionContext {
5454

5555
fun pushSchemaPath(
5656
path: JsonPointer,
57-
baseId: Uri,
57+
scopeId: Uri,
5858
)
5959

6060
fun popSchemaPath()
@@ -110,9 +110,9 @@ internal data class DefaultAssertionContext(
110110

111111
override fun pushSchemaPath(
112112
path: JsonPointer,
113-
baseId: Uri,
113+
scopeId: Uri,
114114
) {
115-
referenceResolver.pushSchemaPath(path, baseId)
115+
referenceResolver.pushSchemaPath(path, scopeId)
116116
}
117117

118118
override fun popSchemaPath() {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.github.optimumcode.json.schema.ErrorCollector
66
import kotlinx.serialization.json.JsonElement
77

88
internal class JsonSchemaRoot(
9-
private val baseId: Uri,
9+
private val scopeId: Uri,
1010
private val schemaPath: JsonPointer,
1111
private val assertions: Collection<JsonSchemaAssertion>,
1212
private val canBeReferencedRecursively: Boolean,
@@ -22,7 +22,7 @@ internal class JsonSchemaRoot(
2222
context.resetRecursiveRoot()
2323
}
2424
var result = true
25-
context.pushSchemaPath(schemaPath, baseId)
25+
context.pushSchemaPath(schemaPath, scopeId)
2626
assertions.forEach {
2727
val valid = it.validate(element, context, errorCollector)
2828
result = result and valid

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

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ package io.github.optimumcode.json.schema.internal
22

33
import com.eygraber.uri.Uri
44
import io.github.optimumcode.json.pointer.JsonPointer
5-
import io.github.optimumcode.json.pointer.internal.dropLast
65
import io.github.optimumcode.json.pointer.internal.length
7-
import io.github.optimumcode.json.pointer.startsWith
86

97
internal interface ReferenceResolver {
108
fun ref(refId: RefId): Pair<JsonPointer, JsonSchemaAssertion>
@@ -26,6 +24,7 @@ internal class DefaultReferenceResolver(
2624
if (!originalRef.dynamic) {
2725
return originalRef.schemaPath to originalRef.assertion
2826
}
27+
2928
val fragment = refId.fragment
3029
val possibleDynamicRefs: MutableList<AssertionWithPath> =
3130
references.asSequence()
@@ -35,43 +34,22 @@ internal class DefaultReferenceResolver(
3534
possibleDynamicRefs.sortBy { it.schemaPath.length }
3635

3736
val resolvedDynamicRef =
38-
findMostOuterRef(possibleDynamicRefs)
39-
// Try to select by base id starting from the most outer uri in path to the current location
40-
?: schemaPathsStack.firstNotNullOfOrNull { (_, uri) ->
41-
possibleDynamicRefs.firstOrNull { it.baseId == uri }
42-
}
37+
schemaPathsStack.firstNotNullOfOrNull { (_, scopeId) ->
38+
possibleDynamicRefs.firstOrNull { it.scopeId == scopeId }
39+
}
4340
// If no outer anchor found use the original ref
4441
?: originalRef
4542
return resolvedDynamicRef.schemaPath to resolvedDynamicRef.assertion
4643
}
4744

4845
fun pushSchemaPath(
4946
path: JsonPointer,
50-
baseId: Uri,
47+
scopeId: Uri,
5148
) {
52-
schemaPathsStack.addLast(path to baseId)
49+
schemaPathsStack.addLast(path to scopeId)
5350
}
5451

5552
fun popSchemaPath() {
5653
schemaPathsStack.removeLast()
5754
}
58-
59-
@Suppress("detekt:NestedBlockDepth")
60-
private fun findMostOuterRef(possibleRefs: List<AssertionWithPath>): AssertionWithPath? {
61-
// Try to find the most outer anchor to use
62-
// Check every schema in the current chain
63-
// If not matches - take the most outer by location
64-
for ((schemaPath, baseId) in schemaPathsStack) {
65-
var currPath: JsonPointer = schemaPath
66-
while (currPath != JsonPointer.ROOT) {
67-
for (dynamicRef in possibleRefs) {
68-
if (dynamicRef.schemaPath.startsWith(currPath) && dynamicRef.baseId == baseId) {
69-
return dynamicRef
70-
}
71-
}
72-
currPath = currPath.dropLast() ?: break
73-
}
74-
}
75-
return null
76-
}
7755
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private fun validateReferences(
261261
usedRefs: Set<ReferenceLocation>,
262262
) {
263263
ReferenceValidator.validateReferences(
264-
references.mapValues { it.value.run { PointerWithBaseId(this.baseId, schemaPath) } },
264+
references.mapValues { it.value.run { PointerWithBaseId(this.scopeId, schemaPath) } },
265265
usedRefs,
266266
)
267267
}
@@ -389,7 +389,7 @@ private fun loadSchema(
389389
}
390390
if (refAssertion != null && !referenceFactory.allowOverriding) {
391391
JsonSchemaRoot(
392-
contextWithAdditionalID.baseId,
392+
contextWithAdditionalID.additionalIDs.last().id,
393393
contextWithAdditionalID.schemaPath,
394394
listOf(refAssertion),
395395
contextWithAdditionalID.recursiveResolution,
@@ -450,7 +450,7 @@ private fun loadJsonSchemaRoot(
450450
addAll(assertions)
451451
}
452452
return JsonSchemaRoot(
453-
context.baseId,
453+
context.additionalIDs.last().id,
454454
context.schemaPath,
455455
result,
456456
context.recursiveResolution,
@@ -486,7 +486,7 @@ internal data class AssertionWithPath(
486486
val assertion: JsonSchemaAssertion,
487487
val schemaPath: JsonPointer,
488488
val dynamic: Boolean,
489-
val baseId: Uri,
489+
val scopeId: Uri,
490490
)
491491

492492
private data class DefaultLoadingContext(
@@ -639,7 +639,7 @@ private data class DefaultLoadingContext(
639639
assertion: JsonSchemaAssertion,
640640
dynamic: Boolean,
641641
) {
642-
references.put(referenceId, AssertionWithPath(assertion, schemaPath, dynamic, baseId))?.apply {
642+
references.put(referenceId, AssertionWithPath(assertion, schemaPath, dynamic, additionalIDs.last().id))?.apply {
643643
throw IllegalStateException("duplicated definition $referenceId")
644644
}
645645
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import kotlinx.serialization.builtins.ListSerializer
1818
import kotlinx.serialization.builtins.MapSerializer
1919
import kotlinx.serialization.builtins.serializer
2020
import kotlinx.serialization.json.Json
21+
import kotlinx.serialization.json.JsonArray
2122
import kotlinx.serialization.json.JsonElement
2223
import kotlinx.serialization.json.JsonObject
2324
import kotlinx.serialization.json.jsonPrimitive
@@ -240,6 +241,7 @@ private class TestSuite(
240241
val schema: JsonElement,
241242
val tests: List<SchemaTest>,
242243
val comment: String? = null,
244+
val specification: JsonArray = EMPTY_JSON_ARRAY,
243245
)
244246

245247
@Serializable
@@ -250,6 +252,7 @@ private class SchemaTest(
250252
val comment: String? = null,
251253
)
252254

255+
private val EMPTY_JSON_ARRAY: JsonArray = JsonArray(emptyList())
253256
private val TEST_SUITES_DIR: Path = "schema-test-suite/tests".toPath()
254257
private val TEST_SUITES_DIR_FROM_ROOT: Path = "test-suites".toPath() / TEST_SUITES_DIR
255258
private const val TEST_SUITES_DIR_ENV_VAR: String = "TEST_SUITES_DIR"

0 commit comments

Comments
 (0)