Files
commandkit/integrations/ckconfig/diff_report.go

111 lines
2.8 KiB
Go

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
}