Decode native diff paths without revalidation
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Consolidated audit catalog
|
# 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:
|
numbered entries in seven audit documents. Reviewed audit state:
|
||||||
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
||||||
|
|
||||||
@@ -507,6 +507,9 @@ construction becomes linear.
|
|||||||
|
|
||||||
## F23 — P3: Concise diff rendering validates already-valid native paths
|
## 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
|
ckconfig's private pointer decoder validates arbitrary strings even though its
|
||||||
sole production caller supplies the canonical string of an immutable ConfigKit
|
sole production caller supplies the canonical string of an immutable ConfigKit
|
||||||
`Path`. Tests sustain malformed-input branches that this boundary cannot reach.
|
`Path`. Tests sustain malformed-input branches that this boundary cannot reach.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Audit remediation implementation plan
|
# Audit remediation implementation plan
|
||||||
|
|
||||||
Status: in progress; Stages 1–20 complete.
|
Status: in progress; Stages 1–21 complete.
|
||||||
|
|
||||||
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
||||||
intervening changes recorded decisions and retired the completed older
|
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
|
## 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.
|
Findings: F23. Source: ckconfig `diff_report.go` and diff report tests.
|
||||||
|
|
||||||
1. Change the private token helper to accept configkit.Path. Split its canonical
|
1. Change the private token helper to accept configkit.Path. Split its canonical
|
||||||
|
|||||||
@@ -8,39 +8,17 @@ import (
|
|||||||
configkit "go.maximumdirect.net/configkit"
|
configkit "go.maximumdirect.net/configkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeJSONPointer(pointer string) ([]string, error) {
|
func decodeJSONPointer(path configkit.Path) []string {
|
||||||
|
pointer := path.String()
|
||||||
if pointer == "" {
|
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:], "/")
|
parts := strings.Split(pointer[1:], "/")
|
||||||
|
decoder := strings.NewReplacer("~1", "/", "~0", "~")
|
||||||
for index, part := range parts {
|
for index, part := range parts {
|
||||||
var decoded strings.Builder
|
parts[index] = decoder.Replace(part)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
return parts, nil
|
return parts
|
||||||
}
|
}
|
||||||
|
|
||||||
func placeReportValue(root any, path []string, value any) (any, error) {
|
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 {
|
if !difference.Right.Present {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tokens, err := decodeJSONPointer(path)
|
tokens := decodeJSONPointer(difference.Path)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("difference at %q: %w", path, err)
|
|
||||||
}
|
|
||||||
value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML))
|
value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("difference at %q: %w", path, err)
|
return nil, fmt.Errorf("difference at %q: %w", path, err)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package ckconfig
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
configkit "go.maximumdirect.net/configkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) {
|
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: "/0/../a.b/50%", want: []string{"0", "..", "a.b", "50%"}},
|
||||||
{pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}},
|
{pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}},
|
||||||
{pointer: "/~01", want: []string{"~1"}},
|
{pointer: "/~01", want: []string{"~1"}},
|
||||||
|
{pointer: "/café", want: []string{"café"}},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
got, err := decodeJSONPointer(test.pointer)
|
path, err := configkit.ParsePath(test.pointer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("decode %q: %v", test.pointer, err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
got := decodeJSONPointer(path)
|
||||||
if !reflect.DeepEqual(got, test.want) {
|
if !reflect.DeepEqual(got, test.want) {
|
||||||
t.Errorf("decode %q = %#v, want %#v", test.pointer, 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) {
|
func TestPlaceReportValue(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user