Skip to content

Commit afd1385

Browse files
author
Oleg
committed
Add property names assertion
1 parent ab0e2b0 commit afd1385

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-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
@@ -24,6 +24,7 @@ import smirnov.oleg.json.schema.internal.factories.number.MultipleOfAssertionFac
2424
import smirnov.oleg.json.schema.internal.factories.`object`.MaxPropertiesAssertionFactory
2525
import smirnov.oleg.json.schema.internal.factories.`object`.MinPropertiesAssertionFactory
2626
import smirnov.oleg.json.schema.internal.factories.`object`.PropertiesAssertionFactory
27+
import smirnov.oleg.json.schema.internal.factories.`object`.PropertyNamesAssertionFactory
2728
import smirnov.oleg.json.schema.internal.factories.`object`.RequiredAssertionFactory
2829
import smirnov.oleg.json.schema.internal.factories.string.MaxLengthAssertionFactory
2930
import smirnov.oleg.json.schema.internal.factories.string.MinLengthAssertionFactory
@@ -50,6 +51,7 @@ private val factories: List<AssertionFactory> = listOf(
5051
MinPropertiesAssertionFactory,
5152
RequiredAssertionFactory,
5253
PropertiesAssertionFactory,
54+
PropertyNamesAssertionFactory,
5355
)
5456

5557
class SchemaLoader {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package smirnov.oleg.json.schema.internal.factories.`object`
2+
3+
import kotlinx.serialization.json.JsonElement
4+
import kotlinx.serialization.json.JsonObject
5+
import kotlinx.serialization.json.JsonPrimitive
6+
import smirnov.oleg.json.schema.ErrorCollector
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 PropertyNamesAssertionFactory : AbstractAssertionFactory("propertyNames") {
14+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
15+
require(context.isJsonSchema(element)) { "$property must be a valid JSON schema" }
16+
val propertyNamesAssertion = context.schemaFrom(element)
17+
return PropertyNamesAssertion(propertyNamesAssertion)
18+
}
19+
}
20+
21+
private class PropertyNamesAssertion(
22+
private val namesAssertion: JsonSchemaAssertion,
23+
) : JsonSchemaAssertion {
24+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
25+
if (element !is JsonObject) {
26+
return true
27+
}
28+
var valid = true
29+
element.keys.forEach { property ->
30+
val res = namesAssertion.validate(
31+
JsonPrimitive(property),
32+
context.at(property),
33+
) {
34+
errorCollector.onError(it.copy(message = "property $property: ${it.message}"))
35+
}
36+
valid = valid and res
37+
}
38+
return valid
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package smirnov.oleg.json.schema.assertions.`object`
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.schema.JsonSchema
12+
import smirnov.oleg.json.schema.KEY
13+
import smirnov.oleg.json.schema.ValidationError
14+
15+
@Suppress("unused")
16+
class JsonSchemaPropertyNamesValidationTest : FunSpec() {
17+
init {
18+
JsonSchema.fromDescription(
19+
"""
20+
{
21+
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
22+
"propertyNames": {
23+
"minLength": 3,
24+
"maxLength": 5
25+
}
26+
}
27+
""".trimIndent()
28+
).also { schema ->
29+
test("object properties passes validation") {
30+
val jsonObject = buildJsonObject {
31+
put("abc", JsonPrimitive(42))
32+
put("abcd", JsonPrimitive(42))
33+
put("abcde", JsonPrimitive(42))
34+
}
35+
val errors = mutableListOf<ValidationError>()
36+
val valid = schema.validate(jsonObject, errors::add)
37+
jsonObject.asClue {
38+
valid shouldBe true
39+
errors shouldHaveSize 0
40+
}
41+
}
42+
43+
test("object properties fails validation") {
44+
val jsonObject = buildJsonObject {
45+
put("ab", JsonPrimitive("42"))
46+
put("abc", JsonPrimitive(42))
47+
put("abcd", JsonPrimitive(42.0))
48+
put("abcde", JsonPrimitive(42))
49+
put("abcdef", JsonPrimitive(42))
50+
}
51+
val errors = mutableListOf<ValidationError>()
52+
val valid = schema.validate(jsonObject, errors::add)
53+
jsonObject.asClue {
54+
valid shouldBe false
55+
errors.shouldContainExactly(
56+
ValidationError(
57+
schemaPath = JsonPointer("/propertyNames/minLength"),
58+
objectPath = JsonPointer("/ab"),
59+
message = "property ab: string length (2) must be greater or equal to 3",
60+
),
61+
ValidationError(
62+
schemaPath = JsonPointer("/propertyNames/maxLength"),
63+
objectPath = JsonPointer("/abcdef"),
64+
message = "property abcdef: string length (6) must be less or equal to 5",
65+
)
66+
)
67+
}
68+
}
69+
70+
notAnObjectPasses(schema)
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)