157 lines
4.4 KiB
Go
157 lines
4.4 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"`
|
|
}
|
|
|
|
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
|
|
}
|