|
| 1 | +# Phase 1.5 — Error Source Pointer Conformance |
| 2 | + |
| 3 | +> **Module:** `jsonapi-java-core` |
| 4 | +> **Packages:** `io.github.kazemek.jsonapi.core.model`, `io.github.kazemek.jsonapi.core.internal`, |
| 5 | +> `io.github.kazemek.jsonapi.core.validation` |
| 6 | +> **Dependencies:** Phase 1.1, Phase 1.4 |
| 7 | +> **Status:** Complete |
| 8 | +
|
| 9 | +## Goal |
| 10 | + |
| 11 | +Make `ErrorSource.pointer` reject non–RFC 6901 JSON Pointer syntax while keeping the existing |
| 12 | +public API and architecture intact. |
| 13 | + |
| 14 | +## Research and constraints |
| 15 | + |
| 16 | +- [JSON:API 1.1 error objects](https://jsonapi.org/format/1.1/#error-objects) — `errors[].source.pointer` |
| 17 | + is a JSON Pointer identifying a value in the request document. Consequence: validate pointer |
| 18 | + syntax; do not resolve against a document in core. |
| 19 | +- [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901) — empty string `""` is a valid pointer; a |
| 20 | + non-empty pointer must start with `/`; only `~0` and `~1` are valid escapes inside a reference |
| 21 | + token (`~01` is valid; bare `~`, `~2`, and other `~` forms are invalid); reference tokens may |
| 22 | + contain Unicode. Do not accept URI-fragment form (`#/…`) for `ErrorSource.pointer`. |
| 23 | +- [ADR-003](../../docs/adr/003-validation-and-immutability.md) — local invariants belong in the |
| 24 | + public construction path via stable `ValidationRuleCode` and JSON Pointer-like paths; keep |
| 25 | + local vs aggregate separation; do not extract a new validation service for this change. |
| 26 | +- Existing pattern: [`Link`](../../jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Link.java) |
| 27 | + uses [`SyntaxValidators`](../../jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/internal/SyntaxValidators.java) |
| 28 | + + `LocalValidation.fail` at compact-constructor time. [`ErrorSource`](../../jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/ErrorSource.java) |
| 29 | + currently validates only `additionalMembers` under base path `/errors/source` and accepts arbitrary |
| 30 | + `pointer` strings — this is a stricter-validation behavior change for previously accepted invalid |
| 31 | + pointers. |
| 32 | +- [`docs/conformance.md`](../../docs/conformance.md) has “Error source additional members” but no |
| 33 | + `source.pointer` / RFC 6901 row yet; add one (syntax-only, no resolution). |
| 34 | +- Existing [`JsonPointers`](../../jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/internal/JsonPointers.java) |
| 35 | + is emit/escape/build only — do **not** extend it for inbound syntax validation; put the check in |
| 36 | + `SyntaxValidators` like other inbound syntax helpers. |
| 37 | +- No new ADR. Nullness unchanged (`pointer` already `@Nullable`). Public validate-flow / diagnostic |
| 38 | + surface changes (`ErrorSource` construction + new `ValidationRuleCode`), so refresh |
| 39 | + `jsonapi-java-core` docs per the `module-docs` skill (do not copy its checklist into this file). |
| 40 | + |
| 41 | +## Deliverables |
| 42 | + |
| 43 | +1. **JSON Pointer syntax helper** in `SyntaxValidators` (e.g. `isValidJsonPointer(String)`), |
| 44 | + syntax-only, matching existing internal conventions; no new package or public type; do not |
| 45 | + extend `JsonPointers` for validation. |
| 46 | +2. **`ErrorSource` constructor validation:** when `pointer` is non-null, accept valid RFC 6901 |
| 47 | + syntax and reject invalid syntax with `ValidationRuleCode.INVALID_JSON_POINTER` at |
| 48 | + `/errors/source/pointer`; `null` remains valid (optional member). |
| 49 | +3. **Focused Spock coverage:** extend `SyntaxValidatorsSpec` for the RFC 6901 boolean matrix, and |
| 50 | + model/`ErrorSource` specs for `INVALID_JSON_POINTER` + `/errors/source/pointer` (including `""`, |
| 51 | + escapes, `~01`, bare `~`, missing leading `/`, Unicode, and rejected URI-fragment `#/…`). |
| 52 | +4. **Conformance row** in `docs/conformance.md` marking `source.pointer` RFC 6901 syntax as |
| 53 | + supported, syntax-only, with no document resolution in core. |
| 54 | +5. **Module docs** for `jsonapi-java-core` per the `module-docs` skill: agent/invariant and |
| 55 | + entry-point notes for syntax-only `ErrorSource.pointer` RFC 6901 validation, linking |
| 56 | + conformance. |
| 57 | + |
| 58 | +## Non-goals |
| 59 | + |
| 60 | +- Resolving pointers against request/response documents or checking that a pointer targets a |
| 61 | + valid JSON:API member. |
| 62 | +- URI-fragment JSON Pointer syntax (`#/…`). |
| 63 | +- Semantic validation of `parameter`, `header`, or broader `ErrorObject` rules. |
| 64 | +- Public `JsonPointer` type, new packages, new validation services, extracting `SyntaxValidators`, |
| 65 | + extending `JsonPointers` for inbound validation, new dependencies, or a new ADR. |
| 66 | +- Phase 4.1 conformance/hardening umbrella work; Jackson/Spring modules. |
| 67 | + |
| 68 | +## Implementation boundaries |
| 69 | + |
| 70 | +- Touch `jsonapi-java-core` only: `SyntaxValidators`, `ErrorSource`, `ValidationRuleCode`, mirrored |
| 71 | + Spock specs (`SyntaxValidatorsSpec` and model/`ErrorSource`), `docs/conformance.md`, and |
| 72 | + `jsonapi-java-core` module docs required by `module-docs`. |
| 73 | +- Do not change `ErrorSource`’s public record shape, `ErrorObject`, or `JsonApiDocumentValidator` |
| 74 | + unless the existing local-validation architecture absolutely requires it (prefer constructor-only). |
| 75 | +- Public API surface unchanged except stricter rejection of invalid pointer strings and one new |
| 76 | + public enum constant on `ValidationRuleCode`. |
| 77 | + |
| 78 | +## Test strategy |
| 79 | + |
| 80 | +- Helper (`SyntaxValidatorsSpec`): boolean table for `""`, `/`, `/data`, `/data/0/id`, `/a~0b`, |
| 81 | + `/a~1b`, `/a~01b`, Unicode token path (e.g. `/données`) → true; `data`, `/a~`, `/a~2`, `/a~x`, |
| 82 | + `#/data` → false. |
| 83 | +- Model/`ErrorSource`: `pointer == null` and valid pointers construct successfully; invalid |
| 84 | + pointers throw `JsonApiValidationException` with `ruleCode == INVALID_JSON_POINTER` and path |
| 85 | + `/errors/source/pointer` (mirror `LinkSpec` conventions). |
| 86 | + |
| 87 | +## Acceptance criteria |
| 88 | + |
| 89 | +- [x] Non-null `ErrorSource.pointer` accepts RFC 6901 syntax (including `""`, `~0`/`~1`/`~01`, and |
| 90 | + Unicode tokens) and rejects invalid syntax with `INVALID_JSON_POINTER` at |
| 91 | + `/errors/source/pointer`. |
| 92 | +- [x] `pointer == null` remains valid; no public API redesign, no document-resolution logic, and no |
| 93 | + new dependency. |
| 94 | +- [x] `docs/conformance.md` documents syntax-only RFC 6901 support for `source.pointer`. |
| 95 | +- [x] The canonical `module-docs` checklist passes for `jsonapi-java-core`. |
| 96 | +- [x] `./gradlew :jsonapi-java-core:test --tests 'io.github.kazemek.jsonapi.core.internal.SyntaxValidatorsSpec' --tests 'io.github.kazemek.jsonapi.core.model.*'` |
| 97 | + passes. |
| 98 | +- [x] `./gradlew clean build` passes. |
| 99 | +- [x] Spotless passes (`./gradlew spotlessApply` then `./gradlew spotlessCheck`). |
| 100 | +- [x] When `SONAR_TOKEN` is available, the Sonar Quality Gate passes; without it, report Sonar |
| 101 | + blocked and that CI must still pass the gate. |
0 commit comments