@@ -8,6 +8,7 @@ import kotlinx.serialization.json.booleanOrNull
8
8
import smirnov.oleg.json.pointer.JsonPointer
9
9
import smirnov.oleg.json.pointer.div
10
10
import smirnov.oleg.json.pointer.get
11
+ import smirnov.oleg.json.schema.JsonSchema
11
12
import smirnov.oleg.json.schema.internal.factories.array.ContainsAssertionFactory
12
13
import smirnov.oleg.json.schema.internal.factories.array.ItemsAssertionFactory
13
14
import smirnov.oleg.json.schema.internal.factories.array.MaxItemsAssertionFactory
@@ -64,47 +65,89 @@ private val factories: List<AssertionFactory> = listOf(
64
65
NotAssertionFactory ,
65
66
)
66
67
68
+ private const val DEFINITIONS_PROPERTY : String = " definitions"
69
+ private const val ID_PROPERTY : String = " \$ id"
70
+
71
+ private const val rootReference = ' #'
72
+
67
73
class SchemaLoader {
68
- fun load (schemaDefinition : JsonElement ): JsonSchemaAssertion {
69
- return loadSchema(schemaDefinition)
74
+ fun load (schemaDefinition : JsonElement ): JsonSchema {
75
+ val baseId = extractBaseID(schemaDefinition)
76
+ val context = defaultLoadingContext(baseId)
77
+ loadDefinitions(schemaDefinition, context)
78
+ val schemaAssertion = loadSchema(schemaDefinition, context)
79
+ return JsonSchema (baseId, schemaAssertion, context.references)
70
80
}
81
+
82
+ private fun loadDefinitions (schemaDefinition : JsonElement , context : DefaultLoadingContext ) {
83
+ if (schemaDefinition !is JsonObject ) {
84
+ return
85
+ }
86
+ val definitionsElement = schemaDefinition[DEFINITIONS_PROPERTY ] ? : return
87
+ require(definitionsElement is JsonObject ) { " $DEFINITIONS_PROPERTY must be an object" }
88
+ val definitionsContext = context.at(DEFINITIONS_PROPERTY )
89
+ for ((name, element) in definitionsElement) {
90
+ loadSchema(element, definitionsContext.at(name))
91
+ }
92
+ }
93
+
94
+ private fun extractBaseID (schemaDefinition : JsonElement ): String =
95
+ when (schemaDefinition) {
96
+ is JsonObject -> {
97
+ schemaDefinition[ID_PROPERTY ]?.let {
98
+ require(it is JsonPrimitive && it.isString) { " $ID_PROPERTY must be a string" }
99
+ it.content
100
+ } ? : " "
101
+ }
102
+
103
+ else -> " "
104
+ }.trimEnd(rootReference)
71
105
}
72
106
73
107
private fun loadSchema (
74
108
schemaDefinition : JsonElement ,
75
- context : LoadingContext = defaultLoadingContext()
109
+ context : DefaultLoadingContext ,
76
110
): JsonSchemaAssertion {
77
111
require(context.isJsonSchema(schemaDefinition)) {
78
112
" schema must be either a valid JSON object or boolean"
79
113
}
80
- if (schemaDefinition is JsonPrimitive ) {
81
- return if (schemaDefinition.boolean) {
114
+ return when (schemaDefinition) {
115
+ is JsonPrimitive -> if (schemaDefinition.boolean) {
82
116
TrueSchemaAssertion
83
117
} else {
84
118
FalseSchemaAssertion (path = context.schemaPath)
85
119
}
86
- }
87
- val assertions = factories.filter { it.isApplicable(schemaDefinition) }
88
- .map {
89
- it.create(schemaDefinition, context)
90
- }
91
- return AssertionsCollection (assertions )
120
+
121
+ else -> factories.filter { it.isApplicable(schemaDefinition) }
122
+ .map {
123
+ it.create(schemaDefinition, context)
124
+ }. let (:: AssertionsCollection )
125
+ }. apply (context::register )
92
126
}
93
127
94
128
private data class DefaultLoadingContext (
129
+ private val baseId : String ,
95
130
override val schemaPath : JsonPointer = JsonPointer .ROOT ,
131
+ val references : MutableMap <String , JsonSchemaAssertion > = hashMapOf(),
96
132
) : LoadingContext {
97
- override fun at (property : String ): LoadingContext {
133
+ override fun at (property : String ): DefaultLoadingContext {
98
134
return copy(schemaPath = schemaPath / property)
99
135
}
100
136
101
- override fun at (index : Int ): LoadingContext {
137
+ override fun at (index : Int ): DefaultLoadingContext {
102
138
return copy(schemaPath = schemaPath[index])
103
139
}
104
140
105
141
override fun schemaFrom (element : JsonElement ): JsonSchemaAssertion = loadSchema(element, this )
106
142
override fun isJsonSchema (element : JsonElement ): Boolean = (element is JsonObject
107
143
|| (element is JsonPrimitive && element.booleanOrNull != null ))
144
+
145
+ fun register (assertion : JsonSchemaAssertion ) {
146
+ val referenceId = " $baseId$rootReference$schemaPath "
147
+ references.put(referenceId, assertion)?.apply {
148
+ throw IllegalStateException (" duplicated definition $referenceId " )
149
+ }
150
+ }
108
151
}
109
152
110
- private fun defaultLoadingContext (): DefaultLoadingContext = DefaultLoadingContext ()
153
+ private fun defaultLoadingContext (baseId : String ): DefaultLoadingContext = DefaultLoadingContext (baseId )
0 commit comments