1
+ package io.github.optimumcode.json.schema
2
+
3
+ import io.github.optimumcode.json.pointer.JsonPointer
4
+ import io.github.optimumcode.json.schema.ValidationOutput.Detailed
5
+ import io.github.optimumcode.json.schema.ValidationOutput.Verbose
6
+
7
+ internal typealias OutputErrorTransformer <T > = OutputCollector <T >.(ValidationError ) -> ValidationError ?
8
+
9
+ public sealed class OutputCollector <T > private constructor(
10
+ protected open val parent : OutputCollector <T >? = null ,
11
+ protected val transformer : OutputErrorTransformer <T > = { it },
12
+ ) : ErrorCollector {
13
+ public abstract val output: T
14
+
15
+ internal abstract fun updateLocation (path : JsonPointer ): OutputCollector <T >
16
+
17
+ internal abstract fun updateKeywordLocation (path : JsonPointer ): OutputCollector <T >
18
+
19
+ internal abstract fun withErrorTransformer (transformer : OutputErrorTransformer <T >): OutputCollector <T >
20
+
21
+ internal abstract fun childCollector (): OutputCollector <T >
22
+
23
+ internal open fun reportErrors () = Unit
24
+
25
+ internal inline fun <OUT > use (block : OutputCollector <T >.() -> OUT ): OUT =
26
+ try {
27
+ block(this )
28
+ } finally {
29
+ reportErrors()
30
+ }
31
+
32
+ internal fun transformError (error : ValidationError ): ValidationError ? {
33
+ return transformer(error)?.let {
34
+ parent.let { p ->
35
+ if (p == null ) {
36
+ it
37
+ } else {
38
+ p.transformError(it)
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ internal data object Empty : OutputCollector <Nothing >() {
45
+ override val output: Nothing
46
+ get() = throw UnsupportedOperationException (" no output in empty collector" )
47
+
48
+ override fun updateLocation (path : JsonPointer ): OutputCollector <Nothing > = this
49
+
50
+ override fun updateKeywordLocation (path : JsonPointer ): OutputCollector <Nothing > = this
51
+
52
+ override fun withErrorTransformer (transformer : OutputErrorTransformer <Nothing >): OutputCollector <Nothing > = this
53
+
54
+ override fun childCollector (): OutputCollector <Nothing > = this
55
+
56
+ override fun onError (error : ValidationError ) = Unit
57
+ }
58
+
59
+ internal class DelegateOutputCollector (
60
+ private val errorCollector : ErrorCollector ,
61
+ override val parent : DelegateOutputCollector ? = null ,
62
+ transformer : OutputErrorTransformer <Nothing > = { it },
63
+ ) : OutputCollector<Nothing>(parent, transformer) {
64
+ private val reportedErrors = mutableListOf<ValidationError >()
65
+
66
+ override fun onError (error : ValidationError ) {
67
+ transformError(error)?.also (reportedErrors::add)
68
+ }
69
+
70
+ override val output: Nothing
71
+ get() = throw UnsupportedOperationException (" no output in delegate collector" )
72
+
73
+ override fun updateLocation (path : JsonPointer ): OutputCollector <Nothing > =
74
+ DelegateOutputCollector (errorCollector, this )
75
+
76
+ override fun updateKeywordLocation (path : JsonPointer ): OutputCollector <Nothing > =
77
+ DelegateOutputCollector (errorCollector, this )
78
+
79
+ override fun withErrorTransformer (transformer : OutputErrorTransformer <Nothing >): OutputCollector <Nothing > {
80
+ return DelegateOutputCollector (errorCollector, parent, transformer)
81
+ }
82
+
83
+ override fun reportErrors () {
84
+ parent?.also { it.reportedErrors.addAll(reportedErrors) }
85
+ ? : reportedErrors.forEach(errorCollector::onError)
86
+ }
87
+
88
+ override fun childCollector (): OutputCollector <Nothing > = DelegateOutputCollector (errorCollector, this , transformer)
89
+ }
90
+
91
+ public class Flag private constructor(
92
+ override val parent : Flag ? = null ,
93
+ transformer : OutputErrorTransformer <ValidationOutput .Flag > = { it },
94
+ ) : OutputCollector<ValidationOutput.Flag>(parent, transformer) {
95
+ private var valid: Boolean = true
96
+ override val output: ValidationOutput .Flag
97
+ get() =
98
+ if (valid) {
99
+ ValidationOutput .Flag .VALID
100
+ } else {
101
+ ValidationOutput .Flag .INVALID
102
+ }
103
+
104
+ override fun updateKeywordLocation (path : JsonPointer ): Flag = childCollector()
105
+
106
+ override fun updateLocation (path : JsonPointer ): Flag = childCollector()
107
+
108
+ override fun withErrorTransformer (transformer : OutputErrorTransformer <ValidationOutput .Flag >): Flag =
109
+ Flag (parent, transformer)
110
+
111
+ override fun reportErrors () {
112
+ parent?.also {
113
+ it.valid = it.valid && valid
114
+ }
115
+ }
116
+
117
+ override fun onError (error : ValidationError ) {
118
+ transformError(error) ? : return
119
+ if (! valid) {
120
+ return
121
+ }
122
+ valid = false
123
+ }
124
+
125
+ override fun childCollector (): Flag = Flag (this )
126
+ }
127
+
128
+ public class Detailed private constructor(
129
+ private val location : JsonPointer = JsonPointer .ROOT ,
130
+ private val keywordLocation : JsonPointer = JsonPointer .ROOT ,
131
+ override val parent : Detailed ? = null ,
132
+ transformer : OutputErrorTransformer <ValidationOutput .Detailed > = { it },
133
+ ) : OutputCollector<ValidationOutput.Detailed>(parent, transformer) {
134
+ private val errors: MutableList <ValidationOutput .Detailed > = mutableListOf ()
135
+
136
+ override val output: ValidationOutput .Detailed
137
+ get() =
138
+ if (errors.size == 1 ) {
139
+ errors.single()
140
+ } else {
141
+ Detailed (
142
+ valid = errors.any { ! it.valid },
143
+ keywordLocation = keywordLocation,
144
+ absoluteKeywordLocation = null ,
145
+ instanceLocation = location,
146
+ errors = errors.toList(),
147
+ )
148
+ }
149
+
150
+ override fun updateLocation (path : JsonPointer ): Detailed =
151
+ Detailed (
152
+ location = path,
153
+ keywordLocation = keywordLocation,
154
+ parent = this ,
155
+ )
156
+
157
+ override fun updateKeywordLocation (path : JsonPointer ): Detailed =
158
+ Detailed (
159
+ location = location,
160
+ keywordLocation = path,
161
+ parent = this ,
162
+ )
163
+
164
+ override fun childCollector (): OutputCollector <ValidationOutput .Detailed > =
165
+ Detailed (location, keywordLocation, this )
166
+
167
+ override fun withErrorTransformer (
168
+ transformer : OutputErrorTransformer <ValidationOutput .Detailed >,
169
+ ): OutputCollector <ValidationOutput .Detailed > = Detailed (location, keywordLocation, parent, transformer)
170
+
171
+ override fun reportErrors () {
172
+ parent?.errors?.add(output)
173
+ }
174
+
175
+ override fun onError (error : ValidationError ) {
176
+ val err = transformError(error) ? : return
177
+ errors.add(
178
+ Detailed (
179
+ valid = false ,
180
+ instanceLocation = err.objectPath,
181
+ keywordLocation = err.schemaPath,
182
+ absoluteKeywordLocation = err.absoluteLocation,
183
+ error = err.message,
184
+ ),
185
+ )
186
+ }
187
+ }
188
+
189
+ public class Verbose private constructor(
190
+ private val location : JsonPointer = JsonPointer .ROOT ,
191
+ private val keywordLocation : JsonPointer = JsonPointer .ROOT ,
192
+ override val parent : Verbose ? = null ,
193
+ transformer : OutputErrorTransformer <ValidationOutput .Verbose > = { it },
194
+ ) : OutputCollector<ValidationOutput.Verbose>(parent, transformer) {
195
+ private val errors: MutableList <ValidationOutput .Verbose > = mutableListOf ()
196
+
197
+ override val output: ValidationOutput .Verbose
198
+ get() =
199
+ Verbose (
200
+ valid = errors.any { ! it.valid },
201
+ keywordLocation = keywordLocation,
202
+ absoluteKeywordLocation = null ,
203
+ instanceLocation = location,
204
+ errors = errors.toList(),
205
+ )
206
+
207
+ override fun updateLocation (path : JsonPointer ): Verbose =
208
+ Verbose (
209
+ location = path,
210
+ keywordLocation = keywordLocation,
211
+ parent = this ,
212
+ )
213
+
214
+ override fun updateKeywordLocation (path : JsonPointer ): Verbose =
215
+ Verbose (
216
+ location = location,
217
+ keywordLocation = path,
218
+ parent = this ,
219
+ )
220
+
221
+ override fun childCollector (): OutputCollector <ValidationOutput .Verbose > {
222
+ return Verbose (location, keywordLocation, this )
223
+ }
224
+
225
+ override fun withErrorTransformer (
226
+ transformer : OutputErrorTransformer <ValidationOutput .Verbose >,
227
+ ): OutputCollector <ValidationOutput .Verbose > = Verbose (location, keywordLocation, parent, transformer)
228
+
229
+ override fun reportErrors () {
230
+ parent?.errors?.add(output)
231
+ }
232
+
233
+ override fun onError (error : ValidationError ) {
234
+ val err = transformError(error) ? : return
235
+ errors.add(
236
+ Verbose (
237
+ valid = false ,
238
+ instanceLocation = err.objectPath,
239
+ keywordLocation = err.schemaPath,
240
+ absoluteKeywordLocation = err.absoluteLocation,
241
+ error = err.message,
242
+ ),
243
+ )
244
+ }
245
+ }
246
+ }
0 commit comments