1
1
package io.github.optimumcode.json.schema
2
2
3
3
import io.github.optimumcode.json.pointer.JsonPointer
4
+ import io.github.optimumcode.json.pointer.plus
5
+ import io.github.optimumcode.json.pointer.relative
6
+ import io.github.optimumcode.json.schema.ValidationOutput.BasicError
4
7
import io.github.optimumcode.json.schema.ValidationOutput.Detailed
5
8
import io.github.optimumcode.json.schema.ValidationOutput.Verbose
9
+ import kotlin.jvm.JvmStatic
6
10
7
11
internal typealias OutputErrorTransformer <T > = OutputCollector <T >.(ValidationError ) -> ValidationError ?
8
12
@@ -12,6 +16,20 @@ public sealed class OutputCollector<T> private constructor(
12
16
parent : OutputCollector <T >? = null ,
13
17
transformer : OutputErrorTransformer <T > = NO_TRANSFORMATION ,
14
18
) : ErrorCollector {
19
+ public companion object {
20
+ @JvmStatic
21
+ public fun flag (): Flag = Flag ()
22
+
23
+ @JvmStatic
24
+ public fun basic (): Basic = Basic ()
25
+
26
+ @JvmStatic
27
+ public fun detailed (): Detailed = Detailed ()
28
+
29
+ @JvmStatic
30
+ public fun verbose (): Verbose = Verbose ()
31
+ }
32
+
15
33
public abstract val output: T
16
34
private val transformerFunc: OutputErrorTransformer <T > =
17
35
parent?.let { p ->
@@ -32,7 +50,11 @@ public sealed class OutputCollector<T> private constructor(
32
50
33
51
internal abstract fun updateLocation (path : JsonPointer ): OutputCollector <T >
34
52
35
- internal abstract fun updateKeywordLocation (path : JsonPointer ): OutputCollector <T >
53
+ internal abstract fun updateKeywordLocation (
54
+ path : JsonPointer ,
55
+ absoluteLocation : AbsoluteLocation ? = null,
56
+ canCollapse : Boolean = true,
57
+ ): OutputCollector <T >
36
58
37
59
internal abstract fun withErrorTransformer (transformer : OutputErrorTransformer <T >): OutputCollector <T >
38
60
@@ -55,7 +77,11 @@ public sealed class OutputCollector<T> private constructor(
55
77
56
78
override fun updateLocation (path : JsonPointer ): OutputCollector <Nothing > = this
57
79
58
- override fun updateKeywordLocation (path : JsonPointer ): OutputCollector <Nothing > = this
80
+ override fun updateKeywordLocation (
81
+ path : JsonPointer ,
82
+ absoluteLocation : AbsoluteLocation ? ,
83
+ canCollapse : Boolean ,
84
+ ): OutputCollector <Nothing > = this
59
85
60
86
override fun withErrorTransformer (transformer : OutputErrorTransformer <Nothing >): OutputCollector <Nothing > = this
61
87
@@ -81,26 +107,33 @@ public sealed class OutputCollector<T> private constructor(
81
107
override fun updateLocation (path : JsonPointer ): OutputCollector <Nothing > =
82
108
DelegateOutputCollector (errorCollector, this )
83
109
84
- override fun updateKeywordLocation (path : JsonPointer ): OutputCollector <Nothing > =
85
- DelegateOutputCollector (errorCollector, this )
110
+ override fun updateKeywordLocation (
111
+ path : JsonPointer ,
112
+ absoluteLocation : AbsoluteLocation ? ,
113
+ canCollapse : Boolean ,
114
+ ): OutputCollector <Nothing > = DelegateOutputCollector (errorCollector, this )
86
115
87
116
override fun withErrorTransformer (transformer : OutputErrorTransformer <Nothing >): OutputCollector <Nothing > {
88
117
return DelegateOutputCollector (errorCollector, parent, transformer)
89
118
}
90
119
91
120
override fun reportErrors () {
121
+ if (reportedErrors.isEmpty()) {
122
+ return
123
+ }
92
124
parent?.also { it.reportedErrors.addAll(reportedErrors) }
93
125
? : reportedErrors.forEach(errorCollector::onError)
94
126
}
95
127
96
128
override fun childCollector (): OutputCollector <Nothing > = DelegateOutputCollector (errorCollector, this )
97
129
}
98
130
99
- public class Flag private constructor(
131
+ public class Flag internal constructor(
100
132
private val parent : Flag ? = null ,
101
133
transformer : OutputErrorTransformer <ValidationOutput .Flag > = NO_TRANSFORMATION ,
102
134
) : OutputCollector<ValidationOutput.Flag>(parent, transformer) {
103
135
private var valid: Boolean = true
136
+ private var hasErrors: Boolean = false
104
137
override val output: ValidationOutput .Flag
105
138
get() =
106
139
if (valid) {
@@ -109,72 +142,152 @@ public sealed class OutputCollector<T> private constructor(
109
142
ValidationOutput .Flag .INVALID
110
143
}
111
144
112
- override fun updateKeywordLocation (path : JsonPointer ): Flag = childCollector()
145
+ override fun updateKeywordLocation (
146
+ path : JsonPointer ,
147
+ absoluteLocation : AbsoluteLocation ? ,
148
+ canCollapse : Boolean ,
149
+ ): Flag = childCollector()
113
150
114
151
override fun updateLocation (path : JsonPointer ): Flag = childCollector()
115
152
116
153
override fun withErrorTransformer (transformer : OutputErrorTransformer <ValidationOutput .Flag >): Flag =
117
154
Flag (parent, transformer)
118
155
119
156
override fun reportErrors () {
157
+ valid = valid && ! hasErrors
120
158
parent?.also {
121
159
it.valid = it.valid && valid
122
160
}
123
161
}
124
162
125
163
override fun onError (error : ValidationError ) {
126
164
transformError(error) ? : return
127
- if (! valid ) {
165
+ if (hasErrors ) {
128
166
return
129
167
}
130
- valid = false
168
+ hasErrors = true
131
169
}
132
170
133
171
override fun childCollector (): Flag = Flag (this )
134
172
}
135
173
136
- public class Detailed private constructor(
174
+ public class Basic internal constructor(
175
+ private val parent : Basic ? = null ,
176
+ transformer : OutputErrorTransformer <ValidationOutput .Basic > = NO_TRANSFORMATION ,
177
+ ) : OutputCollector<ValidationOutput.Basic>(parent, transformer) {
178
+ private val errors = mutableListOf<BasicError >()
179
+
180
+ override fun onError (error : ValidationError ) {
181
+ val err = transformError(error) ? : return
182
+ errors + =
183
+ BasicError (
184
+ keywordLocation = err.schemaPath,
185
+ instanceLocation = err.objectPath,
186
+ absoluteKeywordLocation = err.absoluteLocation,
187
+ error = err.message,
188
+ )
189
+ }
190
+
191
+ override val output: ValidationOutput .Basic
192
+ get() =
193
+ ValidationOutput .Basic (
194
+ valid = errors.isEmpty(),
195
+ errors = errors.toSet(),
196
+ )
197
+
198
+ override fun updateLocation (path : JsonPointer ): OutputCollector <ValidationOutput .Basic > = childCollector()
199
+
200
+ override fun updateKeywordLocation (
201
+ path : JsonPointer ,
202
+ absoluteLocation : AbsoluteLocation ? ,
203
+ canCollapse : Boolean ,
204
+ ): OutputCollector <ValidationOutput .Basic > = childCollector()
205
+
206
+ override fun withErrorTransformer (
207
+ transformer : OutputErrorTransformer <ValidationOutput .Basic >,
208
+ ): OutputCollector <ValidationOutput .Basic > = Basic (parent, transformer)
209
+
210
+ override fun childCollector (): OutputCollector <ValidationOutput .Basic > = Basic (this )
211
+
212
+ override fun reportErrors () {
213
+ parent?.errors?.addAll(errors)
214
+ }
215
+ }
216
+
217
+ public class Detailed internal constructor(
137
218
private val location : JsonPointer = JsonPointer .ROOT ,
138
219
private val keywordLocation : JsonPointer = JsonPointer .ROOT ,
139
220
private val parent : Detailed ? = null ,
221
+ private val absoluteLocation : AbsoluteLocation ? = null ,
222
+ private val collapse : Boolean = true ,
140
223
transformer : OutputErrorTransformer <ValidationOutput .Detailed > = NO_TRANSFORMATION ,
141
224
) : OutputCollector<ValidationOutput.Detailed>(parent, transformer) {
142
225
private val errors: MutableList <ValidationOutput .Detailed > = mutableListOf ()
143
226
144
227
override val output: ValidationOutput .Detailed
145
- get() =
146
- if (errors.size == 1 ) {
147
- errors.single()
228
+ get() {
229
+ val valid = errors.none { ! it.valid }
230
+ if (valid) {
231
+ return Detailed (
232
+ valid = true ,
233
+ keywordLocation = keywordLocation,
234
+ instanceLocation = location,
235
+ absoluteKeywordLocation = absoluteLocation,
236
+ errors = emptySet(),
237
+ )
238
+ }
239
+ val failed = errors.filterTo(hashSetOf()) { it.error != null || it.errors.isNotEmpty() }
240
+ return if (failed.size == 1 && collapse) {
241
+ failed.single()
148
242
} else {
149
243
Detailed (
150
- valid = errors.any { ! it.valid } ,
244
+ valid = false ,
151
245
keywordLocation = keywordLocation,
152
- absoluteKeywordLocation = null ,
246
+ absoluteKeywordLocation = absoluteLocation ,
153
247
instanceLocation = location,
154
- errors = errors.toList() ,
248
+ errors = failed ,
155
249
)
156
250
}
251
+ }
157
252
158
253
override fun updateLocation (path : JsonPointer ): Detailed =
159
254
Detailed (
160
255
location = path,
161
256
keywordLocation = keywordLocation,
257
+ absoluteLocation = absoluteLocation,
162
258
parent = this ,
163
259
)
164
260
165
- override fun updateKeywordLocation (path : JsonPointer ): Detailed =
166
- Detailed (
261
+ override fun updateKeywordLocation (
262
+ path : JsonPointer ,
263
+ absoluteLocation : AbsoluteLocation ? ,
264
+ canCollapse : Boolean ,
265
+ ): Detailed {
266
+ val newKeywordLocation =
267
+ if (this .absoluteLocation == null ) {
268
+ path
269
+ } else {
270
+ this .keywordLocation + this .absoluteLocation.path.relative(path)
271
+ }
272
+ if (keywordLocation == newKeywordLocation) {
273
+ return this
274
+ }
275
+ return Detailed (
167
276
location = location,
168
- keywordLocation = path,
277
+ keywordLocation = newKeywordLocation,
278
+ absoluteLocation = absoluteLocation ? : this .absoluteLocation?.copy(path = path),
169
279
parent = this ,
280
+ collapse = absoluteLocation == null && canCollapse,
170
281
)
282
+ }
171
283
172
284
override fun childCollector (): OutputCollector <ValidationOutput .Detailed > =
173
- Detailed (location, keywordLocation, this )
285
+ Detailed (location, keywordLocation, this , absoluteLocation )
174
286
175
287
override fun withErrorTransformer (
176
288
transformer : OutputErrorTransformer <ValidationOutput .Detailed >,
177
- ): OutputCollector <ValidationOutput .Detailed > = Detailed (location, keywordLocation, parent, transformer)
289
+ ): OutputCollector <ValidationOutput .Detailed > =
290
+ Detailed (location, keywordLocation, parent, absoluteLocation, collapse, transformer = transformer)
178
291
179
292
override fun reportErrors () {
180
293
parent?.errors?.add(output)
@@ -194,45 +307,78 @@ public sealed class OutputCollector<T> private constructor(
194
307
}
195
308
}
196
309
197
- public class Verbose private constructor(
310
+ public class Verbose internal constructor(
198
311
private val location : JsonPointer = JsonPointer .ROOT ,
199
312
private val keywordLocation : JsonPointer = JsonPointer .ROOT ,
200
313
private val parent : Verbose ? = null ,
314
+ private val absoluteLocation : AbsoluteLocation ? = null ,
201
315
transformer : OutputErrorTransformer <ValidationOutput .Verbose > = NO_TRANSFORMATION ,
202
316
) : OutputCollector<ValidationOutput.Verbose>(parent, transformer) {
203
317
private val errors: MutableList <ValidationOutput .Verbose > = mutableListOf ()
204
318
205
319
override val output: ValidationOutput .Verbose
206
- get() =
207
- Verbose (
208
- valid = errors.any { ! it.valid },
320
+ get() {
321
+ if (errors.size == 1 ) {
322
+ // when this is a leaf we should return the reported error
323
+ // instead of creating a new node
324
+ val childError = errors.single()
325
+ if (
326
+ childError.errors.isEmpty() &&
327
+ childError.let {
328
+ it.keywordLocation == keywordLocation && it.instanceLocation == it.instanceLocation
329
+ }
330
+ ) {
331
+ return childError
332
+ }
333
+ }
334
+ return Verbose (
335
+ valid = errors.none { ! it.valid },
209
336
keywordLocation = keywordLocation,
210
- absoluteKeywordLocation = null ,
337
+ absoluteKeywordLocation = absoluteLocation ,
211
338
instanceLocation = location,
212
- errors = errors.toList (),
339
+ errors = errors.toSet (),
213
340
)
341
+ }
214
342
215
- override fun updateLocation (path : JsonPointer ): Verbose =
216
- Verbose (
343
+ override fun updateLocation (path : JsonPointer ): Verbose {
344
+ return Verbose (
217
345
location = path,
218
346
keywordLocation = keywordLocation,
347
+ absoluteLocation = absoluteLocation,
219
348
parent = this ,
220
349
)
350
+ }
221
351
222
- override fun updateKeywordLocation (path : JsonPointer ): Verbose =
223
- Verbose (
352
+ override fun updateKeywordLocation (
353
+ path : JsonPointer ,
354
+ absoluteLocation : AbsoluteLocation ? ,
355
+ canCollapse : Boolean ,
356
+ ): Verbose {
357
+ val newKeywordLocation =
358
+ if (this .absoluteLocation == null ) {
359
+ path
360
+ } else {
361
+ this .keywordLocation + this .absoluteLocation.path.relative(path)
362
+ }
363
+ if (keywordLocation == newKeywordLocation) {
364
+ return this
365
+ }
366
+ return Verbose (
224
367
location = location,
225
- keywordLocation = path,
368
+ keywordLocation = newKeywordLocation,
369
+ absoluteLocation = absoluteLocation ? : this .absoluteLocation?.copy(path = path),
226
370
parent = this ,
227
371
)
372
+ }
228
373
229
374
override fun childCollector (): OutputCollector <ValidationOutput .Verbose > {
230
- return Verbose (location, keywordLocation, this )
375
+ return Verbose (location, keywordLocation, this , absoluteLocation )
231
376
}
232
377
233
378
override fun withErrorTransformer (
234
379
transformer : OutputErrorTransformer <ValidationOutput .Verbose >,
235
- ): OutputCollector <ValidationOutput .Verbose > = Verbose (location, keywordLocation, parent, transformer)
380
+ ): OutputCollector <ValidationOutput .Verbose > =
381
+ Verbose (location, keywordLocation, parent, absoluteLocation, transformer)
236
382
237
383
override fun reportErrors () {
238
384
parent?.errors?.add(output)
0 commit comments