Skip to content

Commit 5925ce3

Browse files
committed
Add support for draft 6 (#138)
Resolves #118 (cherry picked from commit 68756be)
1 parent e144024 commit 5925ce3

File tree

8 files changed

+336
-6
lines changed

8 files changed

+336
-6
lines changed

Diff for: api/json-schema-validator.api

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ public final class io/github/optimumcode/json/schema/SchemaType : java/lang/Enum
195195
public static final field Companion Lio/github/optimumcode/json/schema/SchemaType$Companion;
196196
public static final field DRAFT_2019_09 Lio/github/optimumcode/json/schema/SchemaType;
197197
public static final field DRAFT_2020_12 Lio/github/optimumcode/json/schema/SchemaType;
198+
public static final field DRAFT_6 Lio/github/optimumcode/json/schema/SchemaType;
198199
public static final field DRAFT_7 Lio/github/optimumcode/json/schema/SchemaType;
199200
public static final fun find (Ljava/lang/String;)Lio/github/optimumcode/json/schema/SchemaType;
200201
public static fun getEntries ()Lkotlin/enums/EnumEntries;

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

+3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package io.github.optimumcode.json.schema
33
import com.eygraber.uri.Uri
44
import io.github.optimumcode.json.schema.SchemaType.DRAFT_2019_09
55
import io.github.optimumcode.json.schema.SchemaType.DRAFT_2020_12
6+
import io.github.optimumcode.json.schema.SchemaType.DRAFT_6
67
import io.github.optimumcode.json.schema.SchemaType.DRAFT_7
78
import io.github.optimumcode.json.schema.extension.ExternalAssertionFactory
89
import io.github.optimumcode.json.schema.internal.SchemaLoader
910
import io.github.optimumcode.json.schema.internal.wellknown.Draft201909
1011
import io.github.optimumcode.json.schema.internal.wellknown.Draft202012
12+
import io.github.optimumcode.json.schema.internal.wellknown.Draft6
1113
import io.github.optimumcode.json.schema.internal.wellknown.Draft7
1214
import kotlinx.serialization.json.JsonElement
1315
import kotlin.jvm.JvmStatic
@@ -17,6 +19,7 @@ public interface JsonSchemaLoader {
1719
public fun registerWellKnown(draft: SchemaType): JsonSchemaLoader =
1820
apply {
1921
when (draft) {
22+
DRAFT_6 -> Draft6.entries.forEach { register(it.content) }
2023
DRAFT_7 -> Draft7.entries.forEach { register(it.content) }
2124
DRAFT_2019_09 -> Draft201909.entries.forEach { register(it.content) }
2225
DRAFT_2020_12 -> Draft202012.entries.forEach { register(it.content) }

Diff for: src/commonMain/kotlin/io/github/optimumcode/json/schema/SchemaType.kt

+2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import com.eygraber.uri.Uri
44
import io.github.optimumcode.json.schema.internal.SchemaLoaderConfig
55
import io.github.optimumcode.json.schema.internal.config.Draft201909SchemaLoaderConfig
66
import io.github.optimumcode.json.schema.internal.config.Draft202012SchemaLoaderConfig
7+
import io.github.optimumcode.json.schema.internal.config.Draft6SchemaLoaderConfig
78
import io.github.optimumcode.json.schema.internal.config.Draft7SchemaLoaderConfig
89
import kotlin.jvm.JvmStatic
910

1011
public enum class SchemaType(
1112
internal val schemaId: Uri,
1213
internal val config: SchemaLoaderConfig,
1314
) {
15+
DRAFT_6(Uri.parse("http://json-schema.org/draft-06/schema"), Draft6SchemaLoaderConfig),
1416
DRAFT_7(Uri.parse("http://json-schema.org/draft-07/schema"), Draft7SchemaLoaderConfig),
1517
DRAFT_2019_09(Uri.parse("https://json-schema.org/draft/2019-09/schema"), Draft201909SchemaLoaderConfig),
1618
DRAFT_2020_12(Uri.parse("https://json-schema.org/draft/2020-12/schema"), Draft202012SchemaLoaderConfig),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.github.optimumcode.json.schema.internal.config
2+
3+
import io.github.optimumcode.json.schema.FormatBehavior
4+
import io.github.optimumcode.json.schema.SchemaOption
5+
import io.github.optimumcode.json.schema.internal.AssertionFactory
6+
import io.github.optimumcode.json.schema.internal.KeyWord
7+
import io.github.optimumcode.json.schema.internal.KeyWord.ANCHOR
8+
import io.github.optimumcode.json.schema.internal.KeyWord.COMPATIBILITY_DEFINITIONS
9+
import io.github.optimumcode.json.schema.internal.KeyWord.DEFINITIONS
10+
import io.github.optimumcode.json.schema.internal.KeyWord.DYNAMIC_ANCHOR
11+
import io.github.optimumcode.json.schema.internal.KeyWord.ID
12+
import io.github.optimumcode.json.schema.internal.KeyWordResolver
13+
import io.github.optimumcode.json.schema.internal.ReferenceFactory
14+
import io.github.optimumcode.json.schema.internal.ReferenceFactory.RefHolder
15+
import io.github.optimumcode.json.schema.internal.SchemaLoaderConfig
16+
import io.github.optimumcode.json.schema.internal.SchemaLoaderContext
17+
import io.github.optimumcode.json.schema.internal.config.Draft6KeyWordResolver.REF_PROPERTY
18+
import io.github.optimumcode.json.schema.internal.factories.array.AdditionalItemsAssertionFactory
19+
import io.github.optimumcode.json.schema.internal.factories.array.ContainsAssertionFactory
20+
import io.github.optimumcode.json.schema.internal.factories.array.ItemsAssertionFactory
21+
import io.github.optimumcode.json.schema.internal.factories.array.MaxItemsAssertionFactory
22+
import io.github.optimumcode.json.schema.internal.factories.array.MinItemsAssertionFactory
23+
import io.github.optimumcode.json.schema.internal.factories.array.UniqueItemsAssertionFactory
24+
import io.github.optimumcode.json.schema.internal.factories.condition.AllOfAssertionFactory
25+
import io.github.optimumcode.json.schema.internal.factories.condition.AnyOfAssertionFactory
26+
import io.github.optimumcode.json.schema.internal.factories.condition.NotAssertionFactory
27+
import io.github.optimumcode.json.schema.internal.factories.condition.OneOfAssertionFactory
28+
import io.github.optimumcode.json.schema.internal.factories.general.ConstAssertionFactory
29+
import io.github.optimumcode.json.schema.internal.factories.general.EnumAssertionFactory
30+
import io.github.optimumcode.json.schema.internal.factories.general.FormatAssertionFactory
31+
import io.github.optimumcode.json.schema.internal.factories.general.TypeAssertionFactory
32+
import io.github.optimumcode.json.schema.internal.factories.number.ExclusiveMaximumAssertionFactory
33+
import io.github.optimumcode.json.schema.internal.factories.number.ExclusiveMinimumAssertionFactory
34+
import io.github.optimumcode.json.schema.internal.factories.number.MaximumAssertionFactory
35+
import io.github.optimumcode.json.schema.internal.factories.number.MinimumAssertionFactory
36+
import io.github.optimumcode.json.schema.internal.factories.number.MultipleOfAssertionFactory
37+
import io.github.optimumcode.json.schema.internal.factories.`object`.AdditionalPropertiesAssertionFactory
38+
import io.github.optimumcode.json.schema.internal.factories.`object`.DependenciesAssertionFactory
39+
import io.github.optimumcode.json.schema.internal.factories.`object`.MaxPropertiesAssertionFactory
40+
import io.github.optimumcode.json.schema.internal.factories.`object`.MinPropertiesAssertionFactory
41+
import io.github.optimumcode.json.schema.internal.factories.`object`.PatternPropertiesAssertionFactory
42+
import io.github.optimumcode.json.schema.internal.factories.`object`.PropertiesAssertionFactory
43+
import io.github.optimumcode.json.schema.internal.factories.`object`.PropertyNamesAssertionFactory
44+
import io.github.optimumcode.json.schema.internal.factories.`object`.RequiredAssertionFactory
45+
import io.github.optimumcode.json.schema.internal.factories.string.MaxLengthAssertionFactory
46+
import io.github.optimumcode.json.schema.internal.factories.string.MinLengthAssertionFactory
47+
import io.github.optimumcode.json.schema.internal.factories.string.PatternAssertionFactory
48+
import io.github.optimumcode.json.schema.internal.util.getStringRequired
49+
import kotlinx.serialization.json.JsonElement
50+
import kotlinx.serialization.json.JsonObject
51+
52+
internal object Draft6SchemaLoaderConfig : SchemaLoaderConfig {
53+
private val factories: List<AssertionFactory> =
54+
listOf(
55+
TypeAssertionFactory,
56+
EnumAssertionFactory,
57+
ConstAssertionFactory,
58+
MultipleOfAssertionFactory,
59+
MaximumAssertionFactory,
60+
ExclusiveMaximumAssertionFactory,
61+
MinimumAssertionFactory,
62+
ExclusiveMinimumAssertionFactory,
63+
MaxLengthAssertionFactory,
64+
MinLengthAssertionFactory,
65+
PatternAssertionFactory,
66+
ItemsAssertionFactory,
67+
AdditionalItemsAssertionFactory,
68+
MaxItemsAssertionFactory,
69+
MinItemsAssertionFactory,
70+
UniqueItemsAssertionFactory,
71+
ContainsAssertionFactory,
72+
MaxPropertiesAssertionFactory,
73+
MinPropertiesAssertionFactory,
74+
RequiredAssertionFactory,
75+
PropertiesAssertionFactory,
76+
PatternPropertiesAssertionFactory,
77+
AdditionalPropertiesAssertionFactory,
78+
PropertyNamesAssertionFactory,
79+
DependenciesAssertionFactory,
80+
AllOfAssertionFactory,
81+
AnyOfAssertionFactory,
82+
OneOfAssertionFactory,
83+
NotAssertionFactory,
84+
)
85+
86+
override val defaultVocabulary: SchemaLoaderConfig.Vocabulary = SchemaLoaderConfig.Vocabulary()
87+
override val allFactories: List<AssertionFactory>
88+
get() = factories
89+
90+
override fun createVocabulary(schemaDefinition: JsonElement): SchemaLoaderConfig.Vocabulary? = null
91+
92+
override fun factories(
93+
schemaDefinition: JsonElement,
94+
vocabulary: SchemaLoaderConfig.Vocabulary,
95+
options: SchemaLoaderConfig.Options,
96+
): List<AssertionFactory> =
97+
factories +
98+
when (options[SchemaOption.FORMAT_BEHAVIOR_OPTION]) {
99+
null, FormatBehavior.ANNOTATION_AND_ASSERTION -> FormatAssertionFactory.AnnotationAndAssertion
100+
FormatBehavior.ANNOTATION_ONLY -> FormatAssertionFactory.AnnotationOnly
101+
}
102+
103+
override val keywordResolver: KeyWordResolver
104+
get() = Draft6KeyWordResolver
105+
override val referenceFactory: ReferenceFactory
106+
get() = Draft6ReferenceFactory
107+
}
108+
109+
private object Draft6KeyWordResolver : KeyWordResolver {
110+
private const val DEFINITIONS_PROPERTY: String = "definitions"
111+
private const val ID_PROPERTY: String = "\$id"
112+
const val REF_PROPERTY: String = "\$ref"
113+
114+
override fun resolve(keyword: KeyWord): String? =
115+
when (keyword) {
116+
ID -> ID_PROPERTY
117+
DEFINITIONS -> DEFINITIONS_PROPERTY
118+
ANCHOR, COMPATIBILITY_DEFINITIONS, DYNAMIC_ANCHOR -> null
119+
}
120+
}
121+
122+
private object Draft6ReferenceFactory : ReferenceFactory {
123+
override fun extractRef(
124+
schemaDefinition: JsonObject,
125+
context: SchemaLoaderContext,
126+
): RefHolder? =
127+
if (REF_PROPERTY in schemaDefinition) {
128+
RefHolder.Simple(REF_PROPERTY, schemaDefinition.getStringRequired(REF_PROPERTY).let(context::ref))
129+
} else {
130+
null
131+
}
132+
133+
override val allowOverriding: Boolean
134+
get() = false
135+
override val resolveRefPriorId: Boolean
136+
get() = false
137+
138+
override fun recursiveResolutionEnabled(schemaDefinition: JsonObject): Boolean = true
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package io.github.optimumcode.json.schema.internal.wellknown
2+
3+
internal enum class Draft6(
4+
val content: String,
5+
) {
6+
SCHEMA(
7+
"""
8+
{
9+
"${"$"}schema": "http://json-schema.org/draft-06/schema#",
10+
"${"$"}id": "http://json-schema.org/draft-06/schema#",
11+
"title": "Core schema meta-schema",
12+
"definitions": {
13+
"schemaArray": {
14+
"type": "array",
15+
"minItems": 1,
16+
"items": { "${"$"}ref": "#" }
17+
},
18+
"nonNegativeInteger": {
19+
"type": "integer",
20+
"minimum": 0
21+
},
22+
"nonNegativeIntegerDefault0": {
23+
"allOf": [
24+
{ "${"$"}ref": "#/definitions/nonNegativeInteger" },
25+
{ "default": 0 }
26+
]
27+
},
28+
"simpleTypes": {
29+
"enum": [
30+
"array",
31+
"boolean",
32+
"integer",
33+
"null",
34+
"number",
35+
"object",
36+
"string"
37+
]
38+
},
39+
"stringArray": {
40+
"type": "array",
41+
"items": { "type": "string" },
42+
"uniqueItems": true,
43+
"default": []
44+
}
45+
},
46+
"type": ["object", "boolean"],
47+
"properties": {
48+
"${"$"}id": {
49+
"type": "string",
50+
"format": "uri-reference"
51+
},
52+
"${"$"}schema": {
53+
"type": "string",
54+
"format": "uri"
55+
},
56+
"${"$"}ref": {
57+
"type": "string",
58+
"format": "uri-reference"
59+
},
60+
"title": {
61+
"type": "string"
62+
},
63+
"description": {
64+
"type": "string"
65+
},
66+
"default": {},
67+
"examples": {
68+
"type": "array",
69+
"items": {}
70+
},
71+
"multipleOf": {
72+
"type": "number",
73+
"exclusiveMinimum": 0
74+
},
75+
"maximum": {
76+
"type": "number"
77+
},
78+
"exclusiveMaximum": {
79+
"type": "number"
80+
},
81+
"minimum": {
82+
"type": "number"
83+
},
84+
"exclusiveMinimum": {
85+
"type": "number"
86+
},
87+
"maxLength": { "${"$"}ref": "#/definitions/nonNegativeInteger" },
88+
"minLength": { "${"$"}ref": "#/definitions/nonNegativeIntegerDefault0" },
89+
"pattern": {
90+
"type": "string",
91+
"format": "regex"
92+
},
93+
"additionalItems": { "${"$"}ref": "#" },
94+
"items": {
95+
"anyOf": [
96+
{ "${"$"}ref": "#" },
97+
{ "${"$"}ref": "#/definitions/schemaArray" }
98+
],
99+
"default": {}
100+
},
101+
"maxItems": { "${"$"}ref": "#/definitions/nonNegativeInteger" },
102+
"minItems": { "${"$"}ref": "#/definitions/nonNegativeIntegerDefault0" },
103+
"uniqueItems": {
104+
"type": "boolean",
105+
"default": false
106+
},
107+
"contains": { "${"$"}ref": "#" },
108+
"maxProperties": { "${"$"}ref": "#/definitions/nonNegativeInteger" },
109+
"minProperties": { "${"$"}ref": "#/definitions/nonNegativeIntegerDefault0" },
110+
"required": { "${"$"}ref": "#/definitions/stringArray" },
111+
"additionalProperties": { "${"$"}ref": "#" },
112+
"definitions": {
113+
"type": "object",
114+
"additionalProperties": { "${"$"}ref": "#" },
115+
"default": {}
116+
},
117+
"properties": {
118+
"type": "object",
119+
"additionalProperties": { "${"$"}ref": "#" },
120+
"default": {}
121+
},
122+
"patternProperties": {
123+
"type": "object",
124+
"additionalProperties": { "${"$"}ref": "#" },
125+
"propertyNames": { "format": "regex" },
126+
"default": {}
127+
},
128+
"dependencies": {
129+
"type": "object",
130+
"additionalProperties": {
131+
"anyOf": [
132+
{ "${"$"}ref": "#" },
133+
{ "${"$"}ref": "#/definitions/stringArray" }
134+
]
135+
}
136+
},
137+
"propertyNames": { "${"$"}ref": "#" },
138+
"const": {},
139+
"enum": {
140+
"type": "array",
141+
"minItems": 1,
142+
"uniqueItems": true
143+
},
144+
"type": {
145+
"anyOf": [
146+
{ "${"$"}ref": "#/definitions/simpleTypes" },
147+
{
148+
"type": "array",
149+
"items": { "${"$"}ref": "#/definitions/simpleTypes" },
150+
"minItems": 1,
151+
"uniqueItems": true
152+
}
153+
]
154+
},
155+
"format": { "type": "string" },
156+
"allOf": { "${"$"}ref": "#/definitions/schemaArray" },
157+
"anyOf": { "${"$"}ref": "#/definitions/schemaArray" },
158+
"oneOf": { "${"$"}ref": "#/definitions/schemaArray" },
159+
"not": { "${"$"}ref": "#" }
160+
},
161+
"default": {}
162+
}
163+
""",
164+
),
165+
}

Diff for: src/commonTest/kotlin/io/github/optimumcode/json/schema/extension/JsonSchemaExtensionTest.kt

+9-5
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ class JsonSchemaExtensionTest : FunSpec() {
2222
init {
2323
test("reports keyword that matches one of the existing keywords") {
2424
shouldThrow<IllegalStateException> {
25-
JsonSchemaLoader.create()
25+
JsonSchemaLoader
26+
.create()
2627
.withExtensions(DuplicatedAssertionFactory)
27-
}.message shouldBe "external factory with keyword 'type' overlaps with 'type' keyword from DRAFT_7"
28+
}.message shouldBe "external factory with keyword 'type' overlaps with 'type' keyword from DRAFT_6"
2829
}
2930

3031
test("reports duplicated extension keywords") {
3132
shouldThrow<IllegalStateException> {
32-
JsonSchemaLoader.create()
33+
JsonSchemaLoader
34+
.create()
3335
.withExtensions(SimpleDateFormatAssertionFactory, SimpleDateFormatAssertionFactory)
3436
}.message shouldBe "duplicated extension factory with keyword 'dateFormat'"
3537
}
@@ -93,7 +95,8 @@ class JsonSchemaExtensionTest : FunSpec() {
9395
test("registers all extensions with varargs") {
9496
val schema =
9597
shouldNotThrowAny {
96-
JsonSchemaLoader.create()
98+
JsonSchemaLoader
99+
.create()
97100
.withExtensions(SimpleTimeFormatAssertionFactory, SimpleDateFormatAssertionFactory)
98101
.fromDefinition(schemaDef)
99102
}
@@ -103,7 +106,8 @@ class JsonSchemaExtensionTest : FunSpec() {
103106
test("registers all extensions with iterable") {
104107
val schema =
105108
shouldNotThrowAny {
106-
JsonSchemaLoader.create()
109+
JsonSchemaLoader
110+
.create()
107111
.withExtensions(listOf(SimpleTimeFormatAssertionFactory, SimpleDateFormatAssertionFactory))
108112
.fromDefinition(schemaDef)
109113
}

0 commit comments

Comments
 (0)