The Conformance Suite¶
kPointer ships a language-agnostic conformance suite in the conformance/ directory of the
repository. Each fixture file is a JSON array of test cases. A conformance runner in any language
can deserialize these fixtures and check its own implementation without reading a line of Kotlin.
The reference implementation dogfoods the suite: kpointer-core drives its own tests from the
syntax/* fixtures and kpointer-adapter from the algorithm/* fixtures.
If you are writing a workalike in another language, these fixtures are your acceptance test. This page describes their structure; the workalike guide covers the Kotlin-specific idioms you will need to map.
Directory layout¶
conformance/
syntax/
parsing.json # RFC 6901 parsing, fragment, dot-notation, auto-dispatch
relative-apply.json # Applying a relative pointer to an absolute pointer
relative-compute.json # Computing the relative pointer between two absolute pointers
algorithm/
resolve.json # Resolving an absolute pointer against a document
mutate.json # Applying set/remove mutations to a document
Normative versus advisory¶
Some expectation fields are normative — every conforming implementation must satisfy them. Others are advisory — they describe kPointer-specific behavior a port may omit if the concept does not apply.
| Field | Where | Normative? |
|---|---|---|
rfc6901 |
parsing, relative-apply |
✅ |
fragment |
parsing |
✅ |
depth, isRoot |
parsing |
✅ |
dotNotation |
parsing |
⚠️ advisory (skip if unsupported; null = skip) |
key / index |
relative-apply |
✅ |
element |
resolve |
✅ |
document |
mutate |
✅ |
type: "error" (+ errorKind) |
all | ✅ (see below) |
Error kinds¶
Every { "type": "error", … } expectation may carry an errorKind — a language-neutral string
naming why the operation failed. errorKind is normative output: a conforming implementation
must fail with the named kind. How the failure materializes is unconstrained, such as a Kotlin exception
hierarchy, a Rust Result/enum, or a TypeScript tagged error. When errorKind is absent or null,
the fixture accepts any failure.
errorKind |
Layer | Normative? | Meaning |
|---|---|---|---|
pointer-missing-leading-slash |
parse | ✅ | RFC 6901 string did not start with / |
fragment-missing-hash |
parse | ✅ | Fragment string did not start with # |
fragment-malformed-percent-encoding |
parse | ✅ | Truncated or invalid %XX in a fragment |
dot-empty-segment |
parse | ⚠️ advisory | Dot-notation produced an empty segment; skip if dot-notation is unsupported |
relative-malformed-syntax |
relative | ✅ | Relative pointer syntactically malformed (empty, no leading digit, leading zero, bad adjustment, trailing junk) |
relative-levels-exceed-depth |
relative | ✅ | Requested more levels up than the base depth |
relative-adjust-on-root |
relative | ✅ | Index adjustment applied at the root |
relative-adjust-non-integer |
relative | ✅ | Index adjustment on a non-integer segment |
relative-negative-index |
relative | ✅ | Index adjustment produced a negative index |
relative-hash-on-root |
relative | ✅ | # used when the resolved pointer is root |
resolve-through-primitive |
resolve | ✅ | Navigated through a primitive intermediate value |
resolve-list-index-not-integer |
resolve | ✅ | Non-integer segment addressing a list |
mutate-root |
mutate | ✅ | Set or remove targeted the root pointer |
mutate-remove-absent |
mutate | ✅ | Removed a struct key that is not present |
mutate-through-non-container |
mutate | ✅ | Navigated into an absent or primitive intermediate |
mutate-invalid-list-index |
mutate | ✅ | List index is non-integer or out of range |
mutate-append-token-misused |
mutate | ✅ | - used at an intermediate position or for removal |
Numeric overflow is deliberately unspecified
The relative-pointer draft declares no maximum for the levels-up prefix or the +N/-N
adjustment, so overflow is port-specific: no errorKind, no fixture. A port may choose its
own behavior for arbitrarily large numbers.
Absent versus error¶
Missing items are treated more gently than are trying to do the impossible:
- A resolution that simply does not find a value returns absent (
null/undefined) — this covers both a missing struct key and an out-of-range list index. Throwing for either is a conformance failure. - Only a structural fault is an error: navigating through a primitive, or addressing a list with a non-integer segment.
The element type system¶
Algorithm-layer fixtures represent document nodes as typed objects rather than raw JSON, because raw
JSON cannot distinguish 42 (integer) from 42.0 (float), nor null-vs-absent. Every element carries
a "type" discriminator:
type |
Additional fields | Description |
|---|---|---|
"string" |
"value": <string> |
A UTF-8 string value |
"boolean" |
"value": <boolean> |
A boolean value |
"long" |
"value": <integer> |
A 64-bit signed integer |
"double" |
"value": <number> |
A 64-bit IEEE 754 floating-point value |
"null" |
(none) | An explicit null value |
"struct" |
"fields": { <key>: <element> } |
An object / map of named elements |
"list" |
"elements": [ <element>, … ] |
An ordered list of elements |
The explicit long-vs-double split lets single-numeric-type languages (JavaScript, AssemblyScript)
preserve and test the semantic distinction.
Two elements match when their types agree, primitive values are equal, struct keys map pairwise
with no extras, and lists are equal in length and elementwise.
Coverage matrix — RFC 6901 edge cases¶
A conformance suite is only as good as the edges it names. The table below maps the classic RFC
6901 edge cases to the fixture names that exercise them, so that a missing row is visibly a
missing fixture.
| Edge case | Example | Fixture(s) |
|---|---|---|
~0 decodes to ~ |
{"a~b":1}, "/a~0b" → 1 |
tilde-zero-escape, resolve-tilde-encoded-key |
~1 decodes to / |
{"a/b":1}, "/a~1b" → 1 |
tilde-one-escape, resolve-tilde-encoded-key |
~01 decodes as ~0+1, yielding key a~1b not a/b |
{"a~1b":1,"a/b":2}, "/a~01b" → 1 |
tilde-zero-one-ordering, resolve-tilde-zero-one-ordering |
"/" is one empty-string segment, not the root |
{"":1}, "/" → 1 (depth 1, not root) |
empty-segment-key |
| Trailing slash produces a second empty-string segment | {"foo":{"":1}}, "/foo/" → 1 |
trailing-slash-two-segments |
| Numeric segment on a list is an integer index | ["a","b"], "/1" → "b" |
resolve-list-element-from-list, resolve-list-element-from-struct |
| Numeric segment on a struct is a literal key, not an index | {"0":"x"}, "/0" → "x" |
resolve-numeric-key-on-struct |
| Non-integer segment on a list is an error, not absent | ["a"], "/foo" → error (not null) |
resolve-list-index-not-integer, error-mutate-list-index-not-integer |
"-" as final segment on a list: appends |
["a"], set "/-" to "b" → ["a","b"] |
append-list-element |
"-" used for removal or at an intermediate position: error |
["a"], remove "/-" → error |
error-mutate-append-token-misused |
"-" as final segment on a struct: literal key, not an error |
{"x":"a"}, set "/-" to "b" → {"x":"a","-":"b"} |
set-dash-key-on-struct |
# query when the resolved pointer is root: error |
base "", relative "0#" → error |
error-hash-at-root |
| Percent-encoded fragment segment | {"föö":1}, "#/f%C3%B6%C3%B6" → 1 |
percent-encoded-fragment |
| Malformed percent-encoding in a fragment: error | "#%zz" → error |
error-fragment-malformed-percent-encoding |
| Percent-encoding across multiple segments | {"föö":{"é":1}}, "#/f%C3%B6%C3%B6/%C3%A9" → 1 |
percent-encoded-fragment-multi-segment |
All edge cases are covered by fixtures.
Writing a runner¶
A minimal runner, per fixture category:
- Deserialize the fixture JSON into typed objects using the fixture schemas.
- Build the input (parse the pointer string, build the document from the typed elements, …).
- Execute the operation under test.
- Compare against the
expectfield; report pass/fail using the fixturenameas the test-case id.
The name field is a stable, kebab-case identifier suitable as a test name in any framework.
Next: Writing a Workalike covers the Kotlin idioms you will meet in the reference and how to map them into your target language.