Skip to content

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:

  1. Deserialize the fixture JSON into typed objects using the fixture schemas.
  2. Build the input (parse the pointer string, build the document from the typed elements, …).
  3. Execute the operation under test.
  4. Compare against the expect field; report pass/fail using the fixture name as 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.