Skip to content

Commit 4f7cdbf

Browse files
authored
Add partial support for format keyword (#72)
This patch introduces: - API for implementing user's format validator and registering it in the schema - Support for data/time formats Related to #54
1 parent b1bd666 commit 4f7cdbf

32 files changed

+1319
-77
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,20 @@ val valid = schema.validate(elementToValidate, errors::add)
286286
| | not | Supported |
287287
</details>
288288

289+
## Format assertion
290+
291+
The library supports `format` assertion. For now only a few formats are supported:
292+
* date
293+
* time
294+
* date-time
295+
* duration
296+
297+
But there is an API to implement the user's defined format validation.
298+
The [FormatValidator](src/commonMain/kotlin/io/github/optimumcode/json/schema/ValidationError.kt) interface can be user for that.
299+
The custom format validators can be register in [JsonSchemaLoader](src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt).
300+
301+
_**Please note, that the format validation API is marked as experimental and will require `OptIn` declaration in your code.**_
302+
289303
## Custom assertions
290304

291305
You can implement custom assertions and use them. Read more [here](docs/custom_assertions.md).

api/json-schema-validator.api

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public final class io/github/optimumcode/json/schema/AnnotationKey$Companion {
4242
public final fun simple (Ljava/lang/String;Lkotlin/reflect/KClass;)Lio/github/optimumcode/json/schema/AnnotationKey;
4343
}
4444

45+
public final class io/github/optimumcode/json/schema/Annotations {
46+
public static final field FORMAT_ANNOTATION Lio/github/optimumcode/json/schema/AnnotationKey;
47+
}
48+
4549
public abstract interface class io/github/optimumcode/json/schema/ErrorCollector {
4650
public static final field Companion Lio/github/optimumcode/json/schema/ErrorCollector$Companion;
4751
public static final field EMPTY Lio/github/optimumcode/json/schema/ErrorCollector;
@@ -51,6 +55,34 @@ public abstract interface class io/github/optimumcode/json/schema/ErrorCollector
5155
public final class io/github/optimumcode/json/schema/ErrorCollector$Companion {
5256
}
5357

58+
public abstract interface annotation class io/github/optimumcode/json/schema/ExperimentalApi : java/lang/annotation/Annotation {
59+
}
60+
61+
public final class io/github/optimumcode/json/schema/FormatBehavior : java/lang/Enum {
62+
public static final field ANNOTATION_AND_ASSERTION Lio/github/optimumcode/json/schema/FormatBehavior;
63+
public static final field ANNOTATION_ONLY Lio/github/optimumcode/json/schema/FormatBehavior;
64+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
65+
public static fun valueOf (Ljava/lang/String;)Lio/github/optimumcode/json/schema/FormatBehavior;
66+
public static fun values ()[Lio/github/optimumcode/json/schema/FormatBehavior;
67+
}
68+
69+
public abstract class io/github/optimumcode/json/schema/FormatValidationResult {
70+
public synthetic fun <init> (ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
71+
public final fun isValid ()Z
72+
}
73+
74+
public abstract interface class io/github/optimumcode/json/schema/FormatValidator {
75+
public static final field Companion Lio/github/optimumcode/json/schema/FormatValidator$Companion;
76+
public static fun Invalid ()Lio/github/optimumcode/json/schema/FormatValidationResult;
77+
public static fun Valid ()Lio/github/optimumcode/json/schema/FormatValidationResult;
78+
public abstract fun validate (Lkotlinx/serialization/json/JsonElement;)Lio/github/optimumcode/json/schema/FormatValidationResult;
79+
}
80+
81+
public final class io/github/optimumcode/json/schema/FormatValidator$Companion {
82+
public final fun Invalid ()Lio/github/optimumcode/json/schema/FormatValidationResult;
83+
public final fun Valid ()Lio/github/optimumcode/json/schema/FormatValidationResult;
84+
}
85+
5486
public final class io/github/optimumcode/json/schema/JsonSchema {
5587
public static final field Companion Lio/github/optimumcode/json/schema/JsonSchema$Companion;
5688
public static final fun fromDefinition (Ljava/lang/String;)Lio/github/optimumcode/json/schema/JsonSchema;
@@ -83,8 +115,11 @@ public abstract interface class io/github/optimumcode/json/schema/JsonSchemaLoad
83115
public abstract fun register (Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
84116
public abstract fun register (Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lio/github/optimumcode/json/schema/SchemaType;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
85117
public abstract fun registerWellKnown (Lio/github/optimumcode/json/schema/SchemaType;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
118+
public abstract fun withCustomFormat (Ljava/lang/String;Lio/github/optimumcode/json/schema/FormatValidator;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
119+
public abstract fun withCustomFormats (Ljava/util/Map;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
86120
public abstract fun withExtensions (Lio/github/optimumcode/json/schema/extension/ExternalAssertionFactory;[Lio/github/optimumcode/json/schema/extension/ExternalAssertionFactory;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
87121
public abstract fun withExtensions (Ljava/lang/Iterable;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
122+
public abstract fun withSchemaOption (Lio/github/optimumcode/json/schema/SchemaOption;Ljava/lang/Object;)Lio/github/optimumcode/json/schema/JsonSchemaLoader;
88123
}
89124

90125
public final class io/github/optimumcode/json/schema/JsonSchemaLoader$Companion {
@@ -104,6 +139,14 @@ public final class io/github/optimumcode/json/schema/JsonSchemaStream {
104139
public static final fun fromStream (Lio/github/optimumcode/json/schema/JsonSchema$Companion;Ljava/io/InputStream;)Lio/github/optimumcode/json/schema/JsonSchema;
105140
}
106141

142+
public final class io/github/optimumcode/json/schema/SchemaOption {
143+
public static final field Companion Lio/github/optimumcode/json/schema/SchemaOption$Companion;
144+
public static final field FORMAT_BEHAVIOR_OPTION Lio/github/optimumcode/json/schema/SchemaOption;
145+
}
146+
147+
public final class io/github/optimumcode/json/schema/SchemaOption$Companion {
148+
}
149+
107150
public final class io/github/optimumcode/json/schema/SchemaType : java/lang/Enum {
108151
public static final field Companion Lio/github/optimumcode/json/schema/SchemaType$Companion;
109152
public static final field DRAFT_2019_09 Lio/github/optimumcode/json/schema/SchemaType;

build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io.gitlab.arturbosch.detekt.Detekt
2+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
23
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
34
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests
45
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
@@ -26,6 +27,11 @@ apiValidation {
2627

2728
kotlin {
2829
explicitApi()
30+
31+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
32+
compilerOptions {
33+
freeCompilerArgs.add("-opt-in=io.github.optimumcode.json.schema.ExperimentalApi")
34+
}
2935
jvm {
3036
jvmToolchain(11)
3137
withJava()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@file:JvmName("Annotations")
2+
3+
package io.github.optimumcode.json.schema
4+
5+
import io.github.optimumcode.json.schema.internal.factories.general.FormatAssertionFactory
6+
import kotlin.jvm.JvmField
7+
import kotlin.jvm.JvmName
8+
9+
/**
10+
* Key for getting annotation from `format` assertion
11+
*/
12+
@JvmField
13+
public val FORMAT_ANNOTATION: AnnotationKey<String> = FormatAssertionFactory.ANNOTATION
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.optimumcode.json.schema
2+
3+
/**
4+
* Marks declarations that are experimental or published as a 'preview' version.
5+
* The API for those declarations can be changed in future releases
6+
* based on library needs or user's feedback.
7+
* Once the API is final the backward compatibility will be maintained within patch and minor updates.
8+
*/
9+
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
10+
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
11+
public annotation class ExperimentalApi
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.optimumcode.json.schema
2+
3+
import kotlinx.serialization.json.JsonElement
4+
import kotlin.jvm.JvmStatic
5+
6+
/**
7+
* The [FormatValidator] is used to check whether the [JsonElement] matches the expected format.
8+
* If the [JsonElement] is not of the required type (e.g. validator expects string but the [JsonElement] is an object)
9+
* the validator **MUST** return [FormatValidator.Valid] result
10+
*/
11+
@ExperimentalApi
12+
public interface FormatValidator {
13+
/**
14+
* Validates [element] against the expected format
15+
*
16+
* @param element JSON element to validate against the expected format
17+
* @return the result of the validation
18+
*/
19+
public fun validate(element: JsonElement): FormatValidationResult
20+
21+
public companion object {
22+
@Suppress("ktlint:standard:function-naming", "FunctionName")
23+
@JvmStatic
24+
public fun Valid(): FormatValidationResult = FormatValidationResult.Valid
25+
26+
@Suppress("ktlint:standard:function-naming", "FunctionName")
27+
@JvmStatic
28+
public fun Invalid(): FormatValidationResult = FormatValidationResult.Invalid
29+
}
30+
}
31+
32+
@ExperimentalApi
33+
public sealed class FormatValidationResult private constructor(private val valid: Boolean) {
34+
public fun isValid(): Boolean = valid
35+
36+
internal data object Valid : FormatValidationResult(true)
37+
38+
internal data object Invalid : FormatValidationResult(false)
39+
}
40+
41+
public enum class FormatBehavior {
42+
/**
43+
* Only annotation. If the value does not match format the validation will pass
44+
*/
45+
ANNOTATION_ONLY,
46+
47+
/**
48+
* Annotation and assertion. If the value does not match format the validation will fail
49+
*/
50+
ANNOTATION_AND_ASSERTION,
51+
}

src/commonMain/kotlin/io/github/optimumcode/json/schema/JsonSchemaLoader.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import io.github.optimumcode.json.schema.internal.wellknown.Draft7
1111
import kotlinx.serialization.json.JsonElement
1212
import kotlin.jvm.JvmStatic
1313

14+
@Suppress("detekt:TooManyFunctions")
1415
public interface JsonSchemaLoader {
1516
public fun registerWellKnown(draft: SchemaType): JsonSchemaLoader =
1617
apply {
@@ -53,6 +54,20 @@ public interface JsonSchemaLoader {
5354

5455
public fun withExtensions(externalFactories: Iterable<ExternalAssertionFactory>): JsonSchemaLoader
5556

57+
@ExperimentalApi
58+
public fun withCustomFormat(
59+
format: String,
60+
formatValidator: FormatValidator,
61+
): JsonSchemaLoader
62+
63+
@ExperimentalApi
64+
public fun withCustomFormats(formats: Map<String, FormatValidator>): JsonSchemaLoader
65+
66+
public fun <T : Any> withSchemaOption(
67+
option: SchemaOption<T>,
68+
value: T,
69+
): JsonSchemaLoader
70+
5671
public fun fromDefinition(schema: String): JsonSchema = fromDefinition(schema, null)
5772

5873
public fun fromDefinition(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.optimumcode.json.schema
2+
3+
import kotlin.jvm.JvmField
4+
import kotlin.reflect.KClass
5+
6+
public class SchemaOption<T : Any> private constructor(internal val type: KClass<T>) {
7+
public companion object {
8+
@JvmField
9+
public val FORMAT_BEHAVIOR_OPTION: SchemaOption<FormatBehavior> = SchemaOption(FormatBehavior::class)
10+
}
11+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package io.github.optimumcode.json.schema.internal
22

3+
import io.github.optimumcode.json.schema.FormatValidator
34
import io.github.optimumcode.json.schema.extension.ExternalLoadingContext
45
import kotlinx.serialization.json.JsonElement
56

67
internal interface LoadingContext : ExternalLoadingContext {
8+
val customFormatValidators: Map<String, FormatValidator>
9+
710
fun at(property: String): LoadingContext
811

912
fun at(index: Int): LoadingContext

0 commit comments

Comments
 (0)