Skip to content

Commit 38cf7bc

Browse files
committed
add URI normalization when building reference URI
1 parent 921581b commit 38cf7bc

File tree

2 files changed

+82
-42
lines changed
  • json-schema-validator/src

2 files changed

+82
-42
lines changed

json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,46 @@ private fun Uri.appendPathToParent(path: String): Uri {
712712
}
713713
}.appendEncodedPath(path)
714714
.build()
715+
.normalizeUri()
715716
}
716717

718+
private fun Uri.normalizeUri(): Uri {
719+
if (pathSegments.none { it in RELATIVE_PATH_SEGMENTS }) {
720+
// Nothing to normalize
721+
return this
722+
}
723+
724+
val newPathSegments = ArrayDeque<String>()
725+
for (segment in pathSegments) {
726+
when (segment) {
727+
SAME_LEVEL_SEGMENT -> { // skip
728+
}
729+
730+
PARENT_LEVEL_SEGMENT ->
731+
if (newPathSegments.isEmpty()) {
732+
error("cannot normalize URI '$this'. Path goes beyond root")
733+
} else {
734+
newPathSegments.removeLast()
735+
}
736+
737+
else -> newPathSegments.addLast(segment)
738+
}
739+
}
740+
741+
return buildUpon()
742+
.encodedPath(null)
743+
.apply {
744+
for (segment in newPathSegments) {
745+
appendEncodedPath(segment)
746+
}
747+
}.build()
748+
}
749+
750+
private const val SAME_LEVEL_SEGMENT = "."
751+
private const val PARENT_LEVEL_SEGMENT = ".."
752+
753+
private val RELATIVE_PATH_SEGMENTS = setOf(SAME_LEVEL_SEGMENT, PARENT_LEVEL_SEGMENT)
754+
717755
private val ANCHOR_REGEX: Regex = "^[A-Za-z][A-Za-z0-9-_:.]*$".toRegex()
718756

719757
private fun Uri.buildRefId(): RefId = RefId(this)

json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaTest.kt

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ class JsonSchemaTest : FunSpec() {
2121
test("loads schema object from string description") {
2222
shouldNotThrowAny {
2323
JsonSchema.fromDefinition(
24-
"""
24+
$$"""
2525
{
26-
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
26+
"$schema": "http://json-schema.org/draft-07/schema#",
2727
"type": "string"
2828
}
2929
""".trimIndent(),
@@ -46,9 +46,9 @@ class JsonSchemaTest : FunSpec() {
4646
test("loads schema with definitions") {
4747
shouldNotThrowAny {
4848
JsonSchema.fromDefinition(
49-
"""
49+
$$"""
5050
{
51-
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
51+
"$schema": "http://json-schema.org/draft-07/schema#",
5252
"definitions": {
5353
"positiveInteger": {
5454
"type": "integer",
@@ -64,11 +64,11 @@ class JsonSchemaTest : FunSpec() {
6464
test("loads schema with self reference") {
6565
shouldNotThrowAny {
6666
JsonSchema.fromDefinition(
67-
"""
67+
$$"""
6868
{
69-
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
69+
"$schema": "http://json-schema.org/draft-07/schema#",
7070
"properties": {
71-
"other": { "${KEY}ref": "#" }
71+
"other": { "$ref": "#" }
7272
}
7373
}
7474
""".trimIndent(),
@@ -79,9 +79,9 @@ class JsonSchemaTest : FunSpec() {
7979
test("reports missing reference") {
8080
shouldThrow<IllegalArgumentException> {
8181
JsonSchema.fromDefinition(
82-
"""
82+
$$"""
8383
{
84-
"${KEY}schema": "http://json-schema.org/draft-07/schema#",
84+
"$schema": "http://json-schema.org/draft-07/schema#",
8585
"definitions": {
8686
"positiveInteger": {
8787
"type": "integer",
@@ -90,7 +90,7 @@ class JsonSchemaTest : FunSpec() {
9090
},
9191
"properties": {
9292
"size": {
93-
"${KEY}ref": "#/definitions/positiveIntege"
93+
"$ref": "#/definitions/positiveIntege"
9494
}
9595
}
9696
}
@@ -115,6 +115,7 @@ class JsonSchemaTest : FunSpec() {
115115
"http://example.com/other.json",
116116
"http://example.com/other.json#",
117117
"http://example.com/root.json#/definitions/B",
118+
"./other.json",
118119
),
119120
"definition X" to
120121
listOf(
@@ -136,7 +137,8 @@ class JsonSchemaTest : FunSpec() {
136137
"http://example.com/root.json#/definitions/C",
137138
),
138139
).forEach { (refDestination, possibleRefs) ->
139-
possibleRefs.asSequence()
140+
possibleRefs
141+
.asSequence()
140142
.flatMapIndexed { index, ref ->
141143
val uri = Uri.parse(ref)
142144
val caseNumber = index + 1
@@ -150,24 +152,24 @@ class JsonSchemaTest : FunSpec() {
150152
withClue(ref) {
151153
shouldNotThrowAny {
152154
JsonSchema.fromDefinition(
153-
"""
155+
$$"""
154156
{
155-
"${KEY}id": "http://example.com/root.json",
157+
"$id": "http://example.com/root.json",
156158
"definitions": {
157-
"A": { "${KEY}id": "#foo" },
159+
"A": { "$id": "#foo" },
158160
"B": {
159-
"${KEY}id": "other.json",
161+
"$id": "other.json",
160162
"definitions": {
161-
"X": { "${KEY}id": "#bar" },
162-
"Y": { "${KEY}id": "t/inner.json" }
163+
"X": { "$id": "#bar" },
164+
"Y": { "$id": "t/inner.json" }
163165
}
164166
},
165167
"C": {
166-
"${KEY}id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"
168+
"$id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"
167169
}
168170
},
169171
"properties": {
170-
"test": { "${KEY}ref": "$ref" }
172+
"test": { "$ref": "$$ref" }
171173
}
172174
}
173175
""".trimIndent(),
@@ -187,9 +189,9 @@ class JsonSchemaTest : FunSpec() {
187189
test("loads schema with supported '$it' \$schema property") {
188190
shouldNotThrowAny {
189191
JsonSchema.fromDefinition(
190-
"""
192+
$$"""
191193
{
192-
"${KEY}schema": "$it",
194+
"$schema": "$$it",
193195
"type": "string"
194196
}
195197
""".trimIndent(),
@@ -205,9 +207,9 @@ class JsonSchemaTest : FunSpec() {
205207
test("reports unsupported '$it' \$schema property") {
206208
shouldThrow<IllegalArgumentException> {
207209
JsonSchema.fromDefinition(
208-
"""
210+
$$"""
209211
{
210-
"${KEY}schema": "$it",
212+
"$schema": "$$it",
211213
"type": "string"
212214
}
213215
""".trimIndent(),
@@ -219,53 +221,53 @@ class JsonSchemaTest : FunSpec() {
219221
test("\$dynamicRef is resolved every time") {
220222
val schema =
221223
JsonSchema.fromDefinition(
222-
"""
224+
$$"""
223225
{
224-
"${KEY}schema": "https://json-schema.org/draft/2020-12/schema",
225-
"${KEY}id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main",
226+
"$schema": "https://json-schema.org/draft/2020-12/schema",
227+
"$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main",
226228
"if": {
227229
"properties": {
228230
"kindOfList": { "const": "numbers" }
229231
},
230232
"required": ["kindOfList"]
231233
},
232-
"then": { "${KEY}ref": "numberList" },
233-
"else": { "${KEY}ref": "stringList" },
234+
"then": { "$ref": "numberList" },
235+
"else": { "$ref": "stringList" },
234236
235-
"${KEY}defs": {
237+
"$defs": {
236238
"genericList": {
237-
"${KEY}id": "genericList",
239+
"$id": "genericList",
238240
"properties": {
239241
"list": {
240-
"items": { "${KEY}dynamicRef": "#itemType" }
242+
"items": { "$dynamicRef": "#itemType" }
241243
}
242244
},
243-
"${KEY}defs": {
245+
"$defs": {
244246
"defaultItemType": {
245-
"${KEY}comment": "Only needed to satisfy bookending requirement",
246-
"${KEY}dynamicAnchor": "itemType"
247+
"$comment": "Only needed to satisfy bookending requirement",
248+
"$dynamicAnchor": "itemType"
247249
}
248250
}
249251
},
250252
"numberList": {
251-
"${KEY}id": "numberList",
252-
"${KEY}defs": {
253+
"$id": "numberList",
254+
"$defs": {
253255
"itemType": {
254-
"${KEY}dynamicAnchor": "itemType",
256+
"$dynamicAnchor": "itemType",
255257
"type": "number"
256258
}
257259
},
258-
"${KEY}ref": "genericList"
260+
"$ref": "genericList"
259261
},
260262
"stringList": {
261-
"${KEY}id": "stringList",
262-
"${KEY}defs": {
263+
"$id": "stringList",
264+
"$defs": {
263265
"itemType": {
264-
"${KEY}dynamicAnchor": "itemType",
266+
"$dynamicAnchor": "itemType",
265267
"type": "string"
266268
}
267269
},
268-
"${KEY}ref": "genericList"
270+
"$ref": "genericList"
269271
}
270272
}
271273
}

0 commit comments

Comments
 (0)