Skip to content

Commit b53d8fd

Browse files
authored
Add support for json-pointer and relative-json-pointer formats (#73)
Related to #54
1 parent 9a78967 commit b53d8fd

File tree

10 files changed

+168
-4
lines changed

10 files changed

+168
-4
lines changed

.github/workflows/check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
with:
5151
gradle-version: wrapper
5252
- name: Build
53-
run: ./gradlew --no-daemon --info ${{ inputs.task }} detektAll ktlintCheck apiCheck koverXmlReport -x :benchmark:benchmark
53+
run: ./gradlew --no-daemon --info ${{ inputs.task }} koverXmlReport -x :benchmark:benchmark
5454
- name: Upload coverage reports to Codecov
5555
if: inputs.upload-code-coverage && github.actor != 'dependabot[bot]'
5656
uses: codecov/codecov-action@v4

.github/workflows/pull_request.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,30 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
jobs:
14+
check-style:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: 'Checkout Repository'
18+
uses: actions/checkout@v4
19+
- uses: actions/setup-java@v4
20+
with:
21+
distribution: temurin
22+
java-version-file: .ci-java-version
23+
- name: Validate Gradle Wrapper
24+
uses: gradle/wrapper-validation-action@v2
25+
- name: Cache konan
26+
uses: actions/cache@v4
27+
with:
28+
path: ~/.konan
29+
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
30+
restore-keys: |
31+
${{ runner.os }}-gradle-
32+
- name: Setup Gradle
33+
uses: gradle/actions/setup-gradle@v3
34+
with:
35+
gradle-version: wrapper
36+
- name: Build
37+
run: ./gradlew --no-daemon --continue detektAll ktlintCheck apiCheck
1438
check-linux:
1539
uses: ./.github/workflows/check.yml
1640
with:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ The library supports `format` assertion. For now only a few formats are supporte
293293
* time
294294
* date-time
295295
* duration
296+
* json-pointer
297+
* relative-json-pointer
296298

297299
But there is an API to implement the user's defined format validation.
298300
The [FormatValidator](src/commonMain/kotlin/io/github/optimumcode/json/schema/ValidationError.kt) interface can be user for that.

src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/factories/general/FormatAssertionFactory.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import io.github.optimumcode.json.schema.internal.factories.AbstractAssertionFac
1616
import io.github.optimumcode.json.schema.internal.formats.DateFormatValidator
1717
import io.github.optimumcode.json.schema.internal.formats.DateTimeFormatValidator
1818
import io.github.optimumcode.json.schema.internal.formats.DurationFormatValidator
19+
import io.github.optimumcode.json.schema.internal.formats.JsonPointerFormatValidator
20+
import io.github.optimumcode.json.schema.internal.formats.RelativeJsonPointerFormatValidator
1921
import io.github.optimumcode.json.schema.internal.formats.TimeFormatValidator
2022
import kotlinx.serialization.json.JsonElement
2123
import kotlinx.serialization.json.JsonPrimitive
@@ -58,6 +60,8 @@ internal sealed class FormatAssertionFactory(
5860
"time" to TimeFormatValidator,
5961
"date-time" to DateTimeFormatValidator,
6062
"duration" to DurationFormatValidator,
63+
"json-pointer" to JsonPointerFormatValidator,
64+
"relative-json-pointer" to RelativeJsonPointerFormatValidator,
6165
)
6266
}
6367
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.optimumcode.json.schema.internal.formats
2+
3+
import io.github.optimumcode.json.pointer.JsonPointer
4+
import io.github.optimumcode.json.schema.FormatValidationResult
5+
import io.github.optimumcode.json.schema.FormatValidator
6+
7+
internal object JsonPointerFormatValidator : AbstractStringFormatValidator() {
8+
override fun validate(value: String): FormatValidationResult {
9+
if (value.isEmpty()) {
10+
return FormatValidator.Valid()
11+
}
12+
if (!value.startsWith(JsonPointer.SEPARATOR)) {
13+
return FormatValidator.Invalid()
14+
}
15+
var escape = false
16+
for (symbol in value) {
17+
if (escape && symbol != JsonPointer.QUOTATION_ESCAPE && symbol != JsonPointer.SEPARATOR_ESCAPE) {
18+
return FormatValidator.Invalid()
19+
}
20+
escape = symbol == JsonPointer.QUOTATION
21+
}
22+
return if (escape) {
23+
// escape character '~' in the end of the segment
24+
FormatValidator.Invalid()
25+
} else {
26+
FormatValidator.Valid()
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.github.optimumcode.json.schema.internal.formats
2+
3+
import io.github.optimumcode.json.schema.FormatValidationResult
4+
import io.github.optimumcode.json.schema.FormatValidator
5+
6+
internal object RelativeJsonPointerFormatValidator : AbstractStringFormatValidator() {
7+
private const val ZERO_CODE: Int = '0'.code
8+
private const val NINE_CODE: Int = '9'.code
9+
private const val REF_SYMBOL = '#'
10+
11+
override fun validate(value: String): FormatValidationResult {
12+
if (value.isEmpty()) {
13+
return FormatValidator.Invalid()
14+
}
15+
val isFirstZero = value[0].code == ZERO_CODE
16+
for ((index, symbol) in value.withIndex()) {
17+
val code = symbol.code
18+
val isDigit = code in ZERO_CODE..NINE_CODE
19+
val isRef = symbol == REF_SYMBOL
20+
if (!isDigit) {
21+
return checkEnding(index, isRef, value)
22+
}
23+
if (code > ZERO_CODE && isFirstZero) {
24+
// leading zeros are not allowed
25+
return FormatValidator.Invalid()
26+
}
27+
}
28+
return FormatValidator.Valid()
29+
}
30+
31+
private fun checkEnding(
32+
index: Int,
33+
isRef: Boolean,
34+
value: String,
35+
): FormatValidationResult =
36+
when {
37+
// we must have a digit at the beginning
38+
index == 0 -> FormatValidator.Invalid()
39+
isRef ->
40+
if (index == value.lastIndex) {
41+
FormatValidator.Valid()
42+
} else {
43+
// # must be the last character
44+
FormatValidator.Invalid()
45+
}
46+
47+
else -> JsonPointerFormatValidator.validate(value.substring(index))
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.optimumcode.json.schema.assertions.general.format
2+
3+
import io.github.optimumcode.json.schema.assertions.general.format.FormatValidationTestSuite.TestCase
4+
import io.kotest.core.spec.style.FunSpec
5+
6+
class JsonSchemaJsonPointerFormatValidationTest : FunSpec() {
7+
init {
8+
FormatValidationTestSuite(
9+
format = "json-pointer",
10+
validTestCases =
11+
listOf(
12+
"",
13+
"/",
14+
"/test//a",
15+
"/test/",
16+
"/tes~0",
17+
"/test~1",
18+
),
19+
invalidTestCases =
20+
listOf(
21+
TestCase("test", "does not start from separator"),
22+
TestCase("/test~2", "invalid quotation"),
23+
TestCase("/test~", "trailing quotation"),
24+
),
25+
).run { testFormat() }
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.github.optimumcode.json.schema.assertions.general.format
2+
3+
import io.github.optimumcode.json.schema.assertions.general.format.FormatValidationTestSuite.TestCase
4+
import io.kotest.core.spec.style.FunSpec
5+
6+
class JsonSchemaRelativeJsonPointerFormatValidationTest : FunSpec() {
7+
init {
8+
FormatValidationTestSuite(
9+
format = "relative-json-pointer",
10+
validTestCases =
11+
listOf(
12+
"0",
13+
"1",
14+
"105",
15+
"0#",
16+
"105#",
17+
"0/test",
18+
"105/test",
19+
"0/0",
20+
),
21+
invalidTestCases =
22+
listOf(
23+
TestCase("", "empty RJP is not valid"),
24+
TestCase("01", "leading zeroes are not allowed"),
25+
TestCase("0##", "ref is the last character"),
26+
TestCase("0#/test", "ref and JSON pointer are not allowed"),
27+
TestCase("/test", "JSON pointer is not a valid RJP"),
28+
TestCase("test", "invalid Json Pointer"),
29+
),
30+
).run { testFormat() }
31+
}
32+
}

src/commonTest/kotlin/io/github/optimumcode/json/schema/assertions/general/format/JsonSchemaTimeFormatValidationTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class JsonSchemaTimeFormatValidationTest : FunSpec() {
2222
"12:42:45.00000001Z",
2323
"12:42:45.000000001Z",
2424
"12:42:45+04:00",
25-
"12:42:45+04:00",
2625
"12:42:45+23:59",
2726
"12:42:45.000000001+02:42",
2827
),

test-suites/src/commonTest/kotlin/io/github/optimumcode/json/schema/suite/AbstractSchemaTestSuite.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ internal val COMMON_FORMAT_FILTER =
5454
"ipv6" to emptySet(),
5555
"iri" to emptySet(),
5656
"iri-reference" to emptySet(),
57-
"json-pointer" to emptySet(),
5857
"regex" to emptySet(),
59-
"relative-json-pointer" to emptySet(),
6058
"uri" to emptySet(),
6159
"uri-reference" to emptySet(),
6260
"uri-template" to emptySet(),

0 commit comments

Comments
 (0)