Decode native diff paths without revalidation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Consolidated audit catalog
|
||||
|
||||
Status: **4 distinct open findings: 4 P3**, consolidated from 30
|
||||
Status: **3 distinct open findings: 3 P3**, consolidated from 30
|
||||
numbered entries in seven audit documents. Reviewed audit state:
|
||||
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
||||
|
||||
@@ -507,6 +507,9 @@ construction becomes linear.
|
||||
|
||||
## F23 — P3: Concise diff rendering validates already-valid native paths
|
||||
|
||||
**Complete.** The private decoder accepts ConfigKit's native path and performs
|
||||
single-pass token decoding. Focused diff, token and snapshot tests pass.
|
||||
|
||||
ckconfig's private pointer decoder validates arbitrary strings even though its
|
||||
sole production caller supplies the canonical string of an immutable ConfigKit
|
||||
`Path`. Tests sustain malformed-input branches that this boundary cannot reach.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Audit remediation implementation plan
|
||||
|
||||
Status: in progress; Stages 1–20 complete.
|
||||
Status: in progress; Stages 1–21 complete.
|
||||
|
||||
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
||||
intervening changes recorded decisions and retired the completed older
|
||||
@@ -662,6 +662,11 @@ record these outcomes separately.
|
||||
|
||||
## Stage 21 — Decode native diff paths without revalidation
|
||||
|
||||
**Complete.** Concise diff adaptation decodes tokens from native ConfigKit paths
|
||||
without revalidating arbitrary strings. Focused diff, pointer and snapshot tests
|
||||
pass offline in the aligned Go 1.27.0 workspace with `-mod=readonly`, including
|
||||
root, empty, Unicode and escaped keys.
|
||||
|
||||
Findings: F23. Source: ckconfig `diff_report.go` and diff report tests.
|
||||
|
||||
1. Change the private token helper to accept configkit.Path. Split its canonical
|
||||
|
||||
@@ -8,39 +8,17 @@ import (
|
||||
configkit "go.maximumdirect.net/configkit"
|
||||
)
|
||||
|
||||
func decodeJSONPointer(pointer string) ([]string, error) {
|
||||
func decodeJSONPointer(path configkit.Path) []string {
|
||||
pointer := path.String()
|
||||
if pointer == "" {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
if pointer[0] != '/' {
|
||||
return nil, fmt.Errorf("decode JSON pointer %q: path must start with /", pointer)
|
||||
}
|
||||
|
||||
parts := strings.Split(pointer[1:], "/")
|
||||
decoder := strings.NewReplacer("~1", "/", "~0", "~")
|
||||
for index, part := range parts {
|
||||
var decoded strings.Builder
|
||||
decoded.Grow(len(part))
|
||||
for offset := 0; offset < len(part); offset++ {
|
||||
if part[offset] != '~' {
|
||||
decoded.WriteByte(part[offset])
|
||||
continue
|
||||
}
|
||||
if offset+1 >= len(part) {
|
||||
return nil, fmt.Errorf("decode JSON pointer %q: incomplete escape in token %d", pointer, index)
|
||||
}
|
||||
offset++
|
||||
switch part[offset] {
|
||||
case '0':
|
||||
decoded.WriteByte('~')
|
||||
case '1':
|
||||
decoded.WriteByte('/')
|
||||
default:
|
||||
return nil, fmt.Errorf("decode JSON pointer %q: invalid escape in token %d", pointer, index)
|
||||
}
|
||||
}
|
||||
parts[index] = decoded.String()
|
||||
parts[index] = decoder.Replace(part)
|
||||
}
|
||||
return parts, nil
|
||||
return parts
|
||||
}
|
||||
|
||||
func placeReportValue(root any, path []string, value any) (any, error) {
|
||||
@@ -135,10 +113,7 @@ func renderDiffReport(differences []configkit.Difference) (any, error) {
|
||||
if !difference.Right.Present {
|
||||
continue
|
||||
}
|
||||
tokens, err := decodeJSONPointer(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("difference at %q: %w", path, err)
|
||||
}
|
||||
tokens := decodeJSONPointer(difference.Path)
|
||||
value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("difference at %q: %w", path, err)
|
||||
|
||||
@@ -3,6 +3,8 @@ package ckconfig
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
configkit "go.maximumdirect.net/configkit"
|
||||
)
|
||||
|
||||
func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) {
|
||||
@@ -16,21 +18,18 @@ func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) {
|
||||
{pointer: "/0/../a.b/50%", want: []string{"0", "..", "a.b", "50%"}},
|
||||
{pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}},
|
||||
{pointer: "/~01", want: []string{"~1"}},
|
||||
{pointer: "/café", want: []string{"café"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got, err := decodeJSONPointer(test.pointer)
|
||||
path, err := configkit.ParsePath(test.pointer)
|
||||
if err != nil {
|
||||
t.Fatalf("decode %q: %v", test.pointer, err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := decodeJSONPointer(path)
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("decode %q = %#v, want %#v", test.pointer, got, test.want)
|
||||
}
|
||||
}
|
||||
for _, pointer := range []string{"missing-leading-slash", "/bad~", "/bad~2escape"} {
|
||||
if _, err := decodeJSONPointer(pointer); err == nil {
|
||||
t.Errorf("decode %q unexpectedly succeeded", pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceReportValue(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user