Skip to content

Commit 512285a

Browse files
author
Oleg
committed
Add anyOf assertion
1 parent 3bdf9d2 commit 512285a

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

src/commonMain/kotlin/smirnov/oleg/json/schema/internal/SchemaLoader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import smirnov.oleg.json.schema.internal.factories.array.MaxItemsAssertionFactor
1414
import smirnov.oleg.json.schema.internal.factories.array.MinItemsAssertionFactory
1515
import smirnov.oleg.json.schema.internal.factories.array.UniqueItemsAssertionFactory
1616
import smirnov.oleg.json.schema.internal.factories.condition.AllOfAssertionFactory
17+
import smirnov.oleg.json.schema.internal.factories.condition.AnyOfAssertionFactory
1718
import smirnov.oleg.json.schema.internal.factories.condition.IfThenElseAssertionFactory
1819
import smirnov.oleg.json.schema.internal.factories.general.ConstAssertionFactory
1920
import smirnov.oleg.json.schema.internal.factories.general.EnumAssertionFactory
@@ -58,6 +59,7 @@ private val factories: List<AssertionFactory> = listOf(
5859
DependenciesAssertionFactory,
5960
IfThenElseAssertionFactory,
6061
AllOfAssertionFactory,
62+
AnyOfAssertionFactory,
6163
)
6264

6365
class SchemaLoader {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package smirnov.oleg.json.schema.internal.factories.condition
2+
3+
import kotlinx.serialization.json.JsonArray
4+
import kotlinx.serialization.json.JsonElement
5+
import smirnov.oleg.json.schema.ErrorCollector
6+
import smirnov.oleg.json.schema.ValidationError
7+
import smirnov.oleg.json.schema.internal.AssertionContext
8+
import smirnov.oleg.json.schema.internal.JsonSchemaAssertion
9+
import smirnov.oleg.json.schema.internal.LoadingContext
10+
import smirnov.oleg.json.schema.internal.factories.AbstractAssertionFactory
11+
12+
@Suppress("unused")
13+
internal object AnyOfAssertionFactory : AbstractAssertionFactory("anyOf") {
14+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
15+
require(element is JsonArray) { "$property must be an array" }
16+
require(element.isNotEmpty()) { "$property must have at least one element" }
17+
require(element.all(context::isJsonSchema)) { "each element in $property must be a valid JSON schema" }
18+
19+
val assertions: List<JsonSchemaAssertion> = element.mapIndexed { index, item ->
20+
context.at(index).schemaFrom(item)
21+
}
22+
23+
return AnyOfAssertion(assertions)
24+
}
25+
}
26+
27+
private class AnyOfAssertion(
28+
private val assertions: List<JsonSchemaAssertion>,
29+
) : JsonSchemaAssertion {
30+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
31+
var valid = false
32+
val tempHandler = mutableListOf<ValidationError>()
33+
assertions.forEach {
34+
val res = it.validate(element, context, tempHandler::add)
35+
valid = valid or res
36+
}
37+
if (!valid) {
38+
tempHandler.forEach(errorCollector::onError)
39+
}
40+
return valid
41+
}
42+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package smirnov.oleg.json.schema.assertions.condition
2+
3+
import io.kotest.assertions.asClue
4+
import io.kotest.core.spec.style.FunSpec
5+
import io.kotest.matchers.collections.shouldContainExactly
6+
import io.kotest.matchers.collections.shouldHaveSize
7+
import io.kotest.matchers.shouldBe
8+
import kotlinx.serialization.json.JsonPrimitive
9+
import kotlinx.serialization.json.buildJsonObject
10+
import smirnov.oleg.json.pointer.JsonPointer
11+
import smirnov.oleg.json.pointer.JsonPointer.Companion
12+
import smirnov.oleg.json.schema.JsonSchema
13+
import smirnov.oleg.json.schema.KEY
14+
import smirnov.oleg.json.schema.ValidationError
15+
16+
@Suppress("unused")
17+
class JsonSchemaAnyOfValidationTest : FunSpec() {
18+
init {
19+
JsonSchema.fromDescription(
20+
"""
21+
{
22+
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
23+
"anyOf": [
24+
{
25+
"type": "string"
26+
},
27+
{
28+
"type": "number"
29+
},
30+
{
31+
"type": "boolean"
32+
}
33+
]
34+
}
35+
""".trimIndent()
36+
).also { schema ->
37+
listOf(
38+
JsonPrimitive("str"),
39+
JsonPrimitive(42),
40+
JsonPrimitive(true),
41+
).forEach {
42+
test("element $it passes validation") {
43+
val errors = mutableListOf<ValidationError>()
44+
val valid = schema.validate(it, errors::add)
45+
46+
valid shouldBe true
47+
errors shouldHaveSize 0
48+
}
49+
}
50+
51+
test("element does not match any assertion") {
52+
val jsonObject = buildJsonObject {
53+
put("test", JsonPrimitive(42))
54+
}
55+
56+
val errors = mutableListOf<ValidationError>()
57+
val valid = schema.validate(jsonObject, errors::add)
58+
59+
jsonObject.asClue {
60+
valid shouldBe false
61+
errors.shouldContainExactly(
62+
ValidationError(
63+
schemaPath = JsonPointer("/anyOf/0/type"),
64+
objectPath = JsonPointer.ROOT,
65+
message = "element is not a string",
66+
),
67+
ValidationError(
68+
schemaPath = JsonPointer("/anyOf/1/type"),
69+
objectPath = JsonPointer.ROOT,
70+
message = "element is not a number",
71+
),
72+
ValidationError(
73+
schemaPath = JsonPointer("/anyOf/2/type"),
74+
objectPath = JsonPointer.ROOT,
75+
message = "element is not a boolean",
76+
),
77+
)
78+
}
79+
}
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)