package configkit import ( "bytes" "slices" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml" ) // Snapshot projection is the shared redaction boundary for effective YAML // and explanation values. It borrows frozen trees and returns a detached tree. func valueSnapshot(root *document.Node, path Path, secrets map[string]bool) ValueSnapshot { node, present := documentAtPath(root, path) if !present { return ValueSnapshot{} } safe, redacted := safeSnapshotDocument(node, secrets, path) var output bytes.Buffer if err := yamladapter.Write(&output, safe); err != nil { return ValueSnapshot{Present: true, Redacted: true, YAML: "'[REDACTED]'\n"} } return ValueSnapshot{Present: true, Redacted: redacted, YAML: output.String()} } func safeSnapshotDocument(node *document.Node, secrets map[string]bool, path Path) (*document.Node, bool) { if node == nil { return nil, false } if pathIsSecret(secrets, path) { return &document.Node{Kind: document.String, Text: "[REDACTED]"}, true } result := *node redacted := false switch node.Kind { case document.Mapping: result.Mapping = make([]document.Entry, len(node.Mapping)) copy(result.Mapping, node.Mapping) sortDocumentMapping(result.Mapping) for index := range result.Mapping { entry := &result.Mapping[index] entry.Value, redacted = safeSnapshotChild(entry.Value, secrets, path.Key(entry.Key), redacted) } case document.Sequence: result.Sequence = make([]*document.Node, len(node.Sequence)) for index, child := range node.Sequence { result.Sequence[index], redacted = safeSnapshotChild(child, secrets, path.Index(index), redacted) } } return &result, redacted } func safeSnapshotChild(node *document.Node, secrets map[string]bool, path Path, prior bool) (*document.Node, bool) { safe, redacted := safeSnapshotDocument(node, secrets, path) return safe, prior || redacted } func sortDocumentMapping(entries []document.Entry) { slices.SortFunc(entries, func(left, right document.Entry) int { return strings.Compare(left.Key, right.Key) }) }