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"` } 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 }