Skip to content

Conformance-Testing a Custom Adapter

You have written a custom adapterKpaStruct / KpaList / KpaPrimitive (and, for mutation, RebuildableKpaStruct / RebuildableKpaList) over some tree type kPointer doesn't ship a module for. This page shows how to point the portable algorithm/resolve.json and algorithm/mutate.json fixtures at it, using the published kpointer-conformance module.

What the fixtures actually exercise

resolve and mutate are generic functions in kpointer-adapter, written once against the sealed KpaElement hierarchy — they are not reimplemented per adapter. So running the algorithm fixtures against your adapter isn't testing a from-scratch reimplementation of resolve/mutate; it's testing whether your KpaStruct / KpaList / KpaPrimitive (and factory, if mutable) correctly report their contents — keys, indices, absent-vs-error, and rebuild-on-mutate — so that the shared algorithm behaves correctly on top of them. That is exactly the contract Verifying your adapter points at.

Add the dependency

kpointer-conformance is published on Maven Central under the same group as the rest of kPointer:

[versions]
kpointer = "0.2.0"

[libraries]
kpointer-conformance = { module = "com.commonsware.kpointer:kpointer-conformance", version.ref = "kpointer" }

Declare it as a test dependency in the module holding your adapter:

dependencies {
    testImplementation(libs.kpointer.conformance)
}

The module gives you two small, reusable pieces for this: ConformanceCapabilities (to skip cases your adapter deliberately doesn't support) and ConformanceResult (a Passed / Failed / Skipped sealed type for uniform reporting). The fixture data — the actual algorithm/resolve.json and algorithm/mutate.json files — is not embedded in the artifact; it ships separately in the conformance distribution (or you can read it directly from a checkout of the conformance/ directory). Download or vendor whichever fixture files you want to run.

Bridge the fixture document shape to your tree

Each algorithm/resolve.json / algorithm/mutate.json case carries its input document (and, for resolve, its expected result) as an adapter-neutral, discriminated JSON shape — see Fixture Schemas for the full grammar:

{ "type": "struct", "fields": { "name": { "type": "string", "value": "Alice" } } }
{ "type": "list", "elements": [ { "type": "long", "value": 1 } ] }

Write a small converter from that shape into your own tree type — the same role SimpleDocElement.toKpaElement() plays for the reference JSON adapter in kpointer-conformance's own test suite:

import kotlinx.serialization.json.*

fun JsonElement.toMyTree(): MyNode = when (val type = jsonObject["type"]!!.jsonPrimitive.content) {
    "string" -> MyNode.leaf(jsonObject["value"]!!.jsonPrimitive.content)
    "long" -> MyNode.leaf(jsonObject["value"]!!.jsonPrimitive.long)
    "struct" -> MyNode.struct(jsonObject["fields"]!!.jsonObject.mapValues { (_, v) -> v.toMyTree() })
    "list" -> MyNode.list(jsonObject["elements"]!!.jsonArray.map { it.toMyTree() })
    // … boolean, double, null
    else -> error("unhandled fixture element type: $type")
}

You need the same kind of function to compare your adapter's resolved KpaElement back against a fixture's expect.element — mirroring SimpleDocElement.matches().

Run the fixtures

For each case in the parsed algorithm/resolve.json, build your document, resolve the pointer with the real get operator from kpointer-adapter (the same function every other adapter uses), and compare:

val root: MyKpaStruct = caseDocumentJson.toMyTree().asKpaStruct()
val pointer = KPointer.from(caseJson["pointer"]!!.jsonPrimitive.content)
val result = root[pointer]   // com.commonsware.kpointer.adapter.get

when (val expect = caseJson["expect"]!!.jsonObject) {
    // "type": "element" -> assert result != null && expect.element.matches(result)
    // "type": "absent"  -> assert result == null
    // "type": "error"   -> assert the KpaAccessError thrown and its canonicalErrorCode()
}

algorithm/mutate.json follows the same shape, driving your RebuildableKpaStruct / RebuildableKpaList and the mutate { … } DSL instead of get.

A green run across both files, for every case whose requiresCapability (if any) your ConformanceCapabilities profile declares, is the same bar the built-in JSON/YAML/KSoup adapters are held to.