package ckconfig import ( "fmt" "strings" commandkit "go.maximumdirect.net/commandkit" configkit "go.maximumdirect.net/configkit" ) func decodeJSONPointer(pointer string) ([]string, error) { if pointer == "" { return nil, nil } if pointer[0] != '/' { return nil, fmt.Errorf("decode JSON pointer %q: path must start with /", pointer) } parts := strings.Split(pointer[1:], "/") 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() } return parts, nil } func placeReportValue(root any, path []string, value any) (any, error) { if len(path) == 0 { return value, nil } current, ok := root.(map[string]any) if !ok { return nil, fmt.Errorf("report path ancestor is not a mapping") } for index, token := range path { if index == len(path)-1 { current[token] = value return root, nil } child, exists := current[token] if !exists { next := make(map[string]any) current[token] = next current = next continue } next, ok := child.(map[string]any) if !ok { return nil, fmt.Errorf("report path ancestor %q is not a mapping", token) } current = next } return root, nil } type diffReport struct { Additions any `json:"additions"` Changes any `json:"changes"` Deletions []string `json:"deletions"` } type detailedDiffReport struct { Scope string `json:"scope"` Differences []detailedDifference `json:"differences"` } type detailedDifference struct { Path string `json:"path"` Before detailedDiffSnapshot `json:"before"` After detailedDiffSnapshot `json:"after"` HiddenChanged bool `json:"hidden_changed"` } type detailedDiffSnapshot struct { Present bool `json:"present"` Redacted bool `json:"redacted"` Value any `json:"value"` } func renderDetailedDiff(scope string, differences []configkit.Difference) (any, error) { report := detailedDiffReport{Scope: scope, Differences: make([]detailedDifference, 0, len(differences))} for _, difference := range differences { before, err := detailedSnapshot(difference.Left) if err != nil { return nil, fmt.Errorf("difference at %q before: %w", difference.Path.String(), err) } after, err := detailedSnapshot(difference.Right) if err != nil { return nil, fmt.Errorf("difference at %q after: %w", difference.Path.String(), err) } report.Differences = append(report.Differences, detailedDifference{Path: difference.Path.String(), Before: before, After: after, HiddenChanged: difference.HiddenChanged}) } return report, nil } func detailedSnapshot(snapshot configkit.ValueSnapshot) (detailedDiffSnapshot, error) { report := detailedDiffSnapshot{Present: snapshot.Present, Redacted: snapshot.Redacted} if snapshot.Present { value, err := commandkit.DecodeYAMLReport(strings.NewReader(snapshot.YAML)) if err != nil { return report, err } report.Value = value } return report, nil } func renderDiffReport(differences []configkit.Difference) (any, error) { report := diffReport{Additions: map[string]any{}, Changes: map[string]any{}, Deletions: []string{}} for _, difference := range differences { path := difference.Path.String() if difference.Left.Present && !difference.Right.Present { report.Deletions = append(report.Deletions, path) continue } if !difference.Right.Present { continue } tokens, err := decodeJSONPointer(path) if err != nil { return nil, fmt.Errorf("difference at %q: %w", path, err) } value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML)) if err != nil { return nil, fmt.Errorf("difference at %q: %w", path, err) } if difference.Left.Present { report.Changes, err = placeReportValue(report.Changes, tokens, value) } else { report.Additions, err = placeReportValue(report.Additions, tokens, value) } if err != nil { return nil, fmt.Errorf("difference at %q: %w", path, err) } } return report, nil }