Skip to content

Commit a29b889

Browse files
authored
Each assertion should be loaded independently instead of grouping in one factory (#22)
Resolves #20
1 parent e136ff0 commit a29b889

14 files changed

+371
-181
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ repositories {
3535
mavenCentral()
3636
}
3737

38-
implementation("io.github.optimumcode:json-schema-validator:0.0.1")
38+
implementation("io.github.optimumcode:json-schema-validator:0.0.2")
3939
```
4040

4141
##### Groovy
@@ -45,7 +45,7 @@ repositories {
4545
mavenCentral()
4646
}
4747
48-
implementation 'io.github.optimumcode:json-schema-validator:0.0.1'
48+
implementation 'io.github.optimumcode:json-schema-validator:0.0.2'
4949
```
5050

5151
_Release are published to Sonatype repository. The synchronization with Maven Central takes time._
@@ -78,7 +78,7 @@ repositories {
7878
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots")
7979
}
8080

81-
implementation("io.github.optimumcode:json-schema-validator:0.0.1-SNAPSHOT")
81+
implementation("io.github.optimumcode:json-schema-validator:0.0.3-SNAPSHOT")
8282
```
8383

8484
##### Groovy
@@ -88,7 +88,7 @@ repositories {
8888
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' }
8989
}
9090
91-
implementation 'io.github.optimumcode:json-schema-validator:0.0.1-SNAPSHOT'
91+
implementation 'io.github.optimumcode:json-schema-validator:0.0.3-SNAPSHOT'
9292
```
9393

9494
### Example

changelog_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{
44
"title": "## 🚀 Features",
55
"labels": ["enhancement"],
6+
"exclude_labels": ["internal"],
67
"empty_content": "No new features today 😢"
78
},
89
{

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ kotlin.js.compiler=ir
33
org.gradle.jvmargs=-Xmx1G
44
org.gradle.java.installations.auto-download=false
55

6-
version=0.0.2-SNAPSHOT
6+
version=0.0.3-SNAPSHOT
77
group=io.github.optimumcode

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,70 @@ package io.github.optimumcode.json.schema.internal
33
import io.github.optimumcode.json.pointer.JsonPointer
44
import io.github.optimumcode.json.pointer.div
55
import io.github.optimumcode.json.pointer.get
6+
import kotlin.jvm.JvmStatic
7+
import kotlin.reflect.KClass
8+
import kotlin.reflect.cast
69

710
internal interface AssertionContext {
811
val objectPath: JsonPointer
9-
12+
fun <T : Any> annotate(key: AnnotationKey<T>, value: T)
13+
fun <T : Any> annotated(key: AnnotationKey<T>): T?
1014
fun at(index: Int): AssertionContext
1115
fun at(property: String): AssertionContext
1216
fun resolveRef(refId: RefId): Pair<JsonPointer, JsonSchemaAssertion>
17+
18+
fun resetAnnotations()
19+
}
20+
21+
internal class AnnotationKey<T : Any> private constructor(
22+
private val name: String,
23+
internal val type: KClass<T>,
24+
) {
25+
override fun equals(other: Any?): Boolean {
26+
if (this === other) return true
27+
if (other == null || this::class != other::class) return false
28+
29+
other as AnnotationKey<*>
30+
31+
if (name != other.name) return false
32+
if (type != other.type) return false
33+
34+
return true
35+
}
36+
37+
override fun hashCode(): Int {
38+
var result = name.hashCode()
39+
result = 31 * result + type.hashCode()
40+
return result
41+
}
42+
43+
override fun toString(): String = "$name(${type.simpleName})"
44+
45+
companion object {
46+
@JvmStatic
47+
inline fun <reified T : Any> create(name: String): AnnotationKey<T> = create(name, T::class)
48+
49+
@JvmStatic
50+
fun <T : Any> create(name: String, type: KClass<T>): AnnotationKey<T> = AnnotationKey(name, type)
51+
}
1352
}
1453

1554
internal data class DefaultAssertionContext(
1655
override val objectPath: JsonPointer,
1756
private val references: Map<RefId, AssertionWithPath>,
1857
) : AssertionContext {
58+
private lateinit var _annotations: MutableMap<AnnotationKey<*>, Any>
59+
override fun <T : Any> annotate(key: AnnotationKey<T>, value: T) {
60+
annotations()[key] = value
61+
}
62+
63+
override fun <T : Any> annotated(key: AnnotationKey<T>): T? {
64+
if (!::_annotations.isInitialized) {
65+
return null
66+
}
67+
return _annotations[key]?.let { key.type.cast(it) }
68+
}
69+
1970
override fun at(index: Int): AssertionContext = copy(objectPath = objectPath[index])
2071

2172
override fun at(property: String): AssertionContext {
@@ -26,4 +77,15 @@ internal data class DefaultAssertionContext(
2677
val resolvedRef = requireNotNull(references[refId]) { "$refId is not found" }
2778
return resolvedRef.schemaPath to resolvedRef.assertion
2879
}
80+
81+
override fun resetAnnotations() {
82+
annotations().clear()
83+
}
84+
85+
private fun annotations(): MutableMap<AnnotationKey<*>, Any> {
86+
if (!::_annotations.isInitialized) {
87+
_annotations = hashMapOf()
88+
}
89+
return _annotations
90+
}
2991
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal class AssertionsCollection(
1313
val valid = it.validate(element, context, errorCollector)
1414
result = result and valid
1515
}
16+
context.resetAnnotations()
1617
return result
1718
}
1819
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import io.github.optimumcode.json.pointer.get
88
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
11+
import io.github.optimumcode.json.schema.internal.factories.FactoryGroup
1112
import io.github.optimumcode.json.schema.internal.factories.array.ContainsAssertionFactory
1213
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory
1314
import io.github.optimumcode.json.schema.internal.factories.array.MaxItemsAssertionFactory
1415
import io.github.optimumcode.json.schema.internal.factories.array.MinItemsAssertionFactory
1516
import io.github.optimumcode.json.schema.internal.factories.array.UniqueItemsAssertionFactory
1617
import io.github.optimumcode.json.schema.internal.factories.condition.AllOfAssertionFactory
1718
import io.github.optimumcode.json.schema.internal.factories.condition.AnyOfAssertionFactory
18-
import io.github.optimumcode.json.schema.internal.factories.condition.IfThenElseAssertionFactory
19+
import io.github.optimumcode.json.schema.internal.factories.condition.ElseAssertionFactory
20+
import io.github.optimumcode.json.schema.internal.factories.condition.IfAssertionFactory
1921
import io.github.optimumcode.json.schema.internal.factories.condition.NotAssertionFactory
2022
import io.github.optimumcode.json.schema.internal.factories.condition.OneOfAssertionFactory
23+
import io.github.optimumcode.json.schema.internal.factories.condition.ThenAssertionFactory
2124
import io.github.optimumcode.json.schema.internal.factories.general.ConstAssertionFactory
2225
import io.github.optimumcode.json.schema.internal.factories.general.EnumAssertionFactory
2326
import io.github.optimumcode.json.schema.internal.factories.general.TypeAssertionFactory
@@ -26,9 +29,11 @@ import io.github.optimumcode.json.schema.internal.factories.number.ExclusiveMini
2629
import io.github.optimumcode.json.schema.internal.factories.number.MaximumAssertionFactory
2730
import io.github.optimumcode.json.schema.internal.factories.number.MinimumAssertionFactory
2831
import io.github.optimumcode.json.schema.internal.factories.number.MultipleOfAssertionFactory
32+
import io.github.optimumcode.json.schema.internal.factories.`object`.AdditionalPropertiesAssertionFactory
2933
import io.github.optimumcode.json.schema.internal.factories.`object`.DependenciesAssertionFactory
3034
import io.github.optimumcode.json.schema.internal.factories.`object`.MaxPropertiesAssertionFactory
3135
import io.github.optimumcode.json.schema.internal.factories.`object`.MinPropertiesAssertionFactory
36+
import io.github.optimumcode.json.schema.internal.factories.`object`.PatternPropertiesAssertionFactory
3237
import io.github.optimumcode.json.schema.internal.factories.`object`.PropertiesAssertionFactory
3338
import io.github.optimumcode.json.schema.internal.factories.`object`.PropertyNamesAssertionFactory
3439
import io.github.optimumcode.json.schema.internal.factories.`object`.RequiredAssertionFactory
@@ -61,10 +66,18 @@ private val factories: List<AssertionFactory> = listOf(
6166
MaxPropertiesAssertionFactory,
6267
MinPropertiesAssertionFactory,
6368
RequiredAssertionFactory,
64-
PropertiesAssertionFactory,
69+
FactoryGroup(
70+
PropertiesAssertionFactory,
71+
PatternPropertiesAssertionFactory,
72+
AdditionalPropertiesAssertionFactory,
73+
),
6574
PropertyNamesAssertionFactory,
6675
DependenciesAssertionFactory,
67-
IfThenElseAssertionFactory,
76+
FactoryGroup(
77+
IfAssertionFactory,
78+
ThenAssertionFactory,
79+
ElseAssertionFactory,
80+
),
6881
AllOfAssertionFactory,
6982
AnyOfAssertionFactory,
7083
OneOfAssertionFactory,
@@ -300,6 +313,7 @@ private data class DefaultLoadingContext(
300313
private fun Set<IdWithLocation>.resolvePath(path: String?): Uri {
301314
return last().id.appendPathToParent(requireNotNull(path) { "path is null" })
302315
}
316+
303317
private fun Uri.appendPathToParent(path: String): Uri {
304318
val hasLastEmptySegment = toString().endsWith('/')
305319
return if (hasLastEmptySegment) {
@@ -315,6 +329,7 @@ private fun Uri.appendPathToParent(path: String): Uri {
315329
}.appendEncodedPath(path)
316330
.build()
317331
}
332+
318333
private fun Uri.buildRefId(): RefId = RefId(this)
319334

320335
private fun Builder.buildRefId(): RefId = build().buildRefId()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.github.optimumcode.json.schema.internal.factories
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.AssertionFactory
6+
import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion
7+
import io.github.optimumcode.json.schema.internal.LoadingContext
8+
import kotlinx.serialization.json.JsonElement
9+
10+
/**
11+
* This class allows to create a group assertion that guarantees the order of assertion execution
12+
* (the same order as [group])
13+
*/
14+
internal class AssertionsGroupFactory(
15+
private val group: List<AssertionFactory>,
16+
) : AssertionFactory {
17+
init {
18+
require(group.isNotEmpty()) { "at least one assertion must be in group" }
19+
}
20+
21+
override fun isApplicable(element: JsonElement): Boolean {
22+
return group.any { it.isApplicable(element) }
23+
}
24+
25+
override fun create(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
26+
return GroupAssertion(
27+
assertions = group.asSequence()
28+
.filter { it.isApplicable(element) }
29+
.map { it.create(element, context) }
30+
.toList(),
31+
)
32+
}
33+
}
34+
35+
@Suppress("FunctionName")
36+
internal fun FactoryGroup(vararg factories: AssertionFactory): AssertionFactory =
37+
AssertionsGroupFactory(factories.toList())
38+
39+
private class GroupAssertion(
40+
private val assertions: List<JsonSchemaAssertion>,
41+
) : JsonSchemaAssertion {
42+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
43+
var result = true
44+
assertions.forEach {
45+
val valid = it.validate(element, context, errorCollector)
46+
result = result && valid
47+
}
48+
return result
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.optimumcode.json.schema.internal.factories.condition
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 kotlinx.serialization.json.JsonElement
9+
10+
internal object ElseAssertionFactory : AbstractAssertionFactory("else") {
11+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
12+
require(context.isJsonSchema(element)) { "$property must be a valid JSON schema" }
13+
val elseAssertion = context.schemaFrom(element)
14+
return ElseAssertion(elseAssertion)
15+
}
16+
}
17+
18+
private class ElseAssertion(
19+
private val assertion: JsonSchemaAssertion,
20+
) : JsonSchemaAssertion {
21+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
22+
return if (context.annotated(IfAssertionFactory.ANNOTATION) == false) {
23+
assertion.validate(element, context, errorCollector)
24+
} else {
25+
true
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.github.optimumcode.json.schema.internal.factories.condition
2+
3+
import io.github.optimumcode.json.schema.ErrorCollector
4+
import io.github.optimumcode.json.schema.internal.AnnotationKey
5+
import io.github.optimumcode.json.schema.internal.AssertionContext
6+
import io.github.optimumcode.json.schema.internal.JsonSchemaAssertion
7+
import io.github.optimumcode.json.schema.internal.LoadingContext
8+
import io.github.optimumcode.json.schema.internal.factories.AbstractAssertionFactory
9+
import kotlinx.serialization.json.JsonElement
10+
11+
internal object IfAssertionFactory : AbstractAssertionFactory("if") {
12+
val ANNOTATION: AnnotationKey<Boolean> = AnnotationKey.create(property)
13+
14+
override fun createFromProperty(element: JsonElement, context: LoadingContext): JsonSchemaAssertion {
15+
require(context.isJsonSchema(element)) { "$property must be a valid JSON schema" }
16+
val ifAssertion = context.schemaFrom(element)
17+
return IfAssertion(ifAssertion)
18+
}
19+
}
20+
21+
private class IfAssertion(
22+
private val condition: JsonSchemaAssertion,
23+
) : JsonSchemaAssertion {
24+
override fun validate(element: JsonElement, context: AssertionContext, errorCollector: ErrorCollector): Boolean {
25+
context.annotate(
26+
IfAssertionFactory.ANNOTATION,
27+
condition.validate(element, context, ErrorCollector.EMPTY),
28+
)
29+
return true
30+
}
31+
}

0 commit comments

Comments
 (0)