Skip to content

Commit 98a7ba7

Browse files
authored
Split items and additionalItems assertions (#28)
Resolves #20 Change remaining pair of assertions
1 parent f7def15 commit 98a7ba7

File tree

3 files changed

+87
-45
lines changed

3 files changed

+87
-45
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.github.optimumcode.json.pointer.relative
99
import io.github.optimumcode.json.schema.JsonSchema
1010
import io.github.optimumcode.json.schema.internal.ReferenceValidator.ReferenceLocation
1111
import io.github.optimumcode.json.schema.internal.factories.FactoryGroup
12+
import io.github.optimumcode.json.schema.internal.factories.array.AdditionalItemsAssertionFactory
1213
import io.github.optimumcode.json.schema.internal.factories.array.ContainsAssertionFactory
1314
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory
1415
import io.github.optimumcode.json.schema.internal.factories.array.MaxItemsAssertionFactory
@@ -58,7 +59,10 @@ private val factories: List<AssertionFactory> = listOf(
5859
MaxLengthAssertionFactory,
5960
MinLengthAssertionFactory,
6061
PatternAssertionFactory,
61-
ItemsAssertionFactory,
62+
FactoryGroup(
63+
ItemsAssertionFactory,
64+
AdditionalItemsAssertionFactory,
65+
),
6266
MaxItemsAssertionFactory,
6367
MinItemsAssertionFactory,
6468
UniqueItemsAssertionFactory,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.github.optimumcode.json.schema.internal.factories.array
2+
3+
import io.github.optimumcode.json.schema.ErrorCollector
4+
import io.github.optimumcode.json.schema.internal.AssertionContext
5+
import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion
6+
import io.github.optimumcode.json.schema.internal.LoadingContext
7+
import io.github.optimumcode.json.schema.internal.factories.AbstractAssertionFactory
8+
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory.Result
9+
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory.Result.All
10+
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory.Result.Index
11+
import kotlinx.serialization.json.JsonArray
12+
import kotlinx.serialization.json.JsonElement
13+
14+
internal object AdditionalItemsAssertionFactory : AbstractAssertionFactory("additionalItems") {
15+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
16+
require(context.isJsonSchema(element)) { "$property must be a valid JSON schema" }
17+
val assertion = context.schemaFrom(element)
18+
return AdditionalItemsAssertion(assertion)
19+
}
20+
}
21+
22+
private class AdditionalItemsAssertion(
23+
private val assertion: JsonSchemaAssertion,
24+
) : JsonSchemaAssertion {
25+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
26+
if (element !is JsonArray) {
27+
return true
28+
}
29+
val lastProcessedIndex: Int = when (val result: Result? = context.annotated(ItemsAssertionFactory.ANNOTATION)) {
30+
All -> element.size
31+
is Index -> result.value
32+
null -> return true // items assertion is not used so this one should be ignored
33+
}
34+
if (lastProcessedIndex == element.size) {
35+
// we have nothing to process here
36+
return true
37+
}
38+
var valid = true
39+
for ((index, el) in element.withIndex()) {
40+
if (index <= lastProcessedIndex) {
41+
continue
42+
}
43+
val res = assertion.validate(
44+
el,
45+
context.at(index),
46+
errorCollector,
47+
)
48+
valid = valid && res
49+
}
50+
51+
return valid
52+
}
53+
}
Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,42 @@
11
package io.github.optimumcode.json.schema.internal.factories.array
22

33
import io.github.optimumcode.json.schema.ErrorCollector
4+
import io.github.optimumcode.json.schema.internal.AnnotationKey
45
import io.github.optimumcode.json.schema.internal.AssertionContext
5-
import io.github.optimumcode.json.schema.internal.AssertionFactory
66
import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion
77
import io.github.optimumcode.json.schema.internal.LoadingContext
8+
import io.github.optimumcode.json.schema.internal.factories.AbstractAssertionFactory
9+
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory.Result
810
import kotlinx.serialization.json.JsonArray
911
import kotlinx.serialization.json.JsonElement
10-
import kotlinx.serialization.json.JsonObject
1112

1213
@Suppress("unused")
13-
internal object ItemsAssertionFactory : AssertionFactory {
14-
private const val itemsProperty: String = "items"
15-
private const val additionalItemsProperty: String = "additionalItems"
16-
17-
override fun isApplicable(element: JsonElement): Boolean {
18-
return element is JsonObject &&
19-
element.run { contains(itemsProperty) }
14+
internal object ItemsAssertionFactory : AbstractAssertionFactory("items") {
15+
sealed class Result {
16+
object All : Result()
17+
class Index(val value: Int) : Result()
2018
}
2119

22-
override fun create(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
23-
require(element is JsonObject) { "cannot extract properties from ${element::class.simpleName}" }
24-
val itemsElement: JsonElement = requireNotNull(element[itemsProperty]) {
25-
"cannot extract $itemsProperty property from element"
26-
}
27-
val itemsContext = context.at(itemsProperty)
28-
val itemsAssertions: List<JsonSchemaAssertion> = if (itemsElement is JsonArray) {
29-
require(itemsElement.isNotEmpty()) { "$itemsProperty must have at least one element" }
30-
require(itemsElement.all(context::isJsonSchema)) {
31-
"all elements in $itemsProperty must be a valid JSON schema"
20+
val ANNOTATION: AnnotationKey<Result> = AnnotationKey.create(property)
21+
22+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
23+
val itemsAssertions: List<JsonSchemaAssertion> = if (element is JsonArray) {
24+
require(element.isNotEmpty()) { "$property must have at least one element" }
25+
require(element.all(context::isJsonSchema)) {
26+
"all elements in $property must be a valid JSON schema"
3227
}
33-
itemsElement.mapIndexed { index, item -> itemsContext.at(index).schemaFrom(item) }
28+
element.mapIndexed { index, item -> context.at(index).schemaFrom(item) }
3429
} else {
35-
require(context.isJsonSchema(itemsElement)) { "$itemsProperty must be a valid JSON schema" }
36-
listOf(itemsContext.schemaFrom(itemsElement))
30+
require(context.isJsonSchema(element)) { "$property must be a valid JSON schema" }
31+
listOf(context.schemaFrom(element))
3732
}
38-
39-
val additionalItemsAssertion: JsonSchemaAssertion? = element[additionalItemsProperty]?.let {
40-
require(context.isJsonSchema(it)) { "$additionalItemsProperty must be a valid JSON schema" }
41-
context.at(additionalItemsProperty).schemaFrom(it)
42-
}
43-
return ElementsAssertion(itemsAssertions, itemsElement !is JsonArray, additionalItemsAssertion)
33+
return ItemsAssertion(itemsAssertions, element !is JsonArray)
4434
}
4535
}
4636

47-
private class ElementsAssertion(
37+
private class ItemsAssertion(
4838
private val items: List<JsonSchemaAssertion>,
4939
private val allElements: Boolean,
50-
private val additionalItems: JsonSchemaAssertion?,
5140
) : JsonSchemaAssertion {
5241
init {
5342
if (allElements) {
@@ -61,36 +50,31 @@ private class ElementsAssertion(
6150
return if (allElements) {
6251
validateEachItem(element, context, errorCollector)
6352
} else {
64-
validateWithAdditionalItems(element, context, errorCollector)
53+
validateElementsAtIndexes(element, context, errorCollector)
6554
}
6655
}
6756

68-
private fun validateWithAdditionalItems(
57+
private fun validateElementsAtIndexes(
6958
element: JsonArray,
7059
context: AssertionContext,
7160
errorCollector: ErrorCollector,
7261
): Boolean {
7362
var valid = true
74-
element.forEachIndexed { index, item ->
75-
val result: Boolean
63+
var lastProcessedIndex = 0
64+
for ((index, item) in element.withIndex()) {
7665
if (index < items.size) {
77-
result = items[index].validate(
66+
val result: Boolean = items[index].validate(
7867
item,
7968
context.at(index),
8069
errorCollector,
8170
)
71+
lastProcessedIndex = index
72+
valid = valid && result
8273
} else {
83-
if (additionalItems == null) {
84-
return valid
85-
}
86-
result = additionalItems.validate(
87-
item,
88-
context.at(index),
89-
errorCollector,
90-
)
74+
break
9175
}
92-
valid = valid && result
9376
}
77+
context.annotate(ItemsAssertionFactory.ANNOTATION, Result.Index(lastProcessedIndex))
9478
return valid
9579
}
9680

@@ -109,6 +93,7 @@ private class ElementsAssertion(
10993
)
11094
valid = valid && result
11195
}
96+
context.annotate(ItemsAssertionFactory.ANNOTATION, Result.All)
11297
return valid
11398
}
11499
}

0 commit comments

Comments
 (0)