Share semantic document traversal helpers
This commit is contained in:
@@ -8,7 +8,8 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
- canonical, control-safe diagnostic rendering and structured `LoadError` access;
|
||||
- opaque file, byte, reader, and no-input source construction in `source.go`,
|
||||
with shared regular-file checks and bounded read mechanics in `source_file.go`;
|
||||
- a parser-independent ordered tree in `internal/document`;
|
||||
- a parser-independent ordered tree in `internal/document`, with root-private
|
||||
semantic lookup, indexing, and value traversal in `document_walk.go`;
|
||||
- the sole upstream YAML integration in `internal/yaml`, which translates validated
|
||||
input into that owned tree and exposes no parser types outside its boundary;
|
||||
`parse.go` owns shared translation into detached, ordered public diagnostics;
|
||||
|
||||
@@ -163,7 +163,14 @@ reliably induce an important filesystem failure. Run race tests for this unit.
|
||||
|
||||
## Stage 4 — Establish shared document traversal ownership
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Completion: `document_walk.go` owns nil-safe preorder value walking, borrowed
|
||||
indexing, and semantic lookup. Metadata shapes and projected sensitivity use the
|
||||
walker with defensive path copies. Specialized origins and budget traversals
|
||||
remain explicit. Full offline tests (including historical path shapes, metadata
|
||||
detachment, and dynamic secrets), vet, build, and whitespace checks passed with
|
||||
Go 1.26.7 on Linux/amd64.
|
||||
|
||||
Evidence: `indexDocument`, `walkDocumentPaths`, and `projectedSensitivity` repeat
|
||||
semantic tree traversal in [load_metadata.go](../../load_metadata.go),
|
||||
|
||||
66
document_walk.go
Normal file
66
document_walk.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package configkit
|
||||
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
|
||||
// walkDocument visits value nodes in preorder, preserving stored mapping order
|
||||
// and sequence index order. Mapping keys are not separate nodes in this walk.
|
||||
// The callback borrows paths and nodes; clone any mutable state it retains.
|
||||
// Specialized origin, pruning, and budget traversals retain their own semantics.
|
||||
func walkDocument(node *document.Node, path Path, visit func(Path, *document.Node)) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
visit(path, node)
|
||||
switch node.Kind {
|
||||
case document.Mapping:
|
||||
for _, entry := range node.Mapping {
|
||||
walkDocument(entry.Value, path.Key(entry.Key), visit)
|
||||
}
|
||||
case document.Sequence:
|
||||
for index, child := range node.Sequence {
|
||||
walkDocument(child, path.Index(index), visit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// indexDocument borrows nodes from its caller-owned tree.
|
||||
func indexDocument(node *document.Node, path Path, index map[string]*document.Node) {
|
||||
walkDocument(node, path, func(path Path, node *document.Node) {
|
||||
index[path.String()] = node
|
||||
})
|
||||
}
|
||||
|
||||
func documentAtPath(root *document.Node, path Path) (*document.Node, bool) {
|
||||
if root == nil || !path.valid() {
|
||||
return nil, false
|
||||
}
|
||||
current := root
|
||||
for _, segment := range path.segments {
|
||||
switch current.Kind {
|
||||
case document.Mapping:
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return nil, false
|
||||
}
|
||||
found := false
|
||||
for _, entry := range current.Mapping {
|
||||
if entry.Key == segment.key {
|
||||
current = entry.Value
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
case document.Sequence:
|
||||
index, ok := schemaIndex(segment)
|
||||
if !ok || index >= len(current.Sequence) {
|
||||
return nil, false
|
||||
}
|
||||
current = current.Sequence[index]
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return current, true
|
||||
}
|
||||
35
load.go
35
load.go
@@ -400,41 +400,6 @@ func checkVersion(rule *VersionRule, root *document.Node) []Diagnostic {
|
||||
return canonicalDiagnostics(state.diagnostics)
|
||||
}
|
||||
|
||||
func documentAtPath(root *document.Node, path Path) (*document.Node, bool) {
|
||||
if root == nil || !path.valid() {
|
||||
return nil, false
|
||||
}
|
||||
current := root
|
||||
for _, segment := range path.segments {
|
||||
switch current.Kind {
|
||||
case document.Mapping:
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return nil, false
|
||||
}
|
||||
found := false
|
||||
for _, entry := range current.Mapping {
|
||||
if entry.Key == segment.key {
|
||||
current = entry.Value
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
case document.Sequence:
|
||||
index, ok := schemaIndex(segment)
|
||||
if !ok || index >= len(current.Sequence) {
|
||||
return nil, false
|
||||
}
|
||||
current = current.Sequence[index]
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return current, true
|
||||
}
|
||||
|
||||
func documentLocationPointer(location document.Location) *Location {
|
||||
if !locationKnown(location) {
|
||||
return nil
|
||||
|
||||
@@ -159,23 +159,6 @@ func collectFieldInfo(node *document.Node, path Path, decoded decodedConfigurati
|
||||
}
|
||||
}
|
||||
|
||||
func indexDocument(node *document.Node, path Path, index map[string]*document.Node) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
index[path.String()] = node
|
||||
switch node.Kind {
|
||||
case document.Mapping:
|
||||
for _, entry := range node.Mapping {
|
||||
indexDocument(entry.Value, path.Key(entry.Key), index)
|
||||
}
|
||||
case document.Sequence:
|
||||
for position, child := range node.Sequence {
|
||||
indexDocument(child, path.Index(position), index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMetadata(metadata Metadata) Metadata {
|
||||
if metadata.fields == nil && metadata.history == nil {
|
||||
return Metadata{}
|
||||
|
||||
23
metadata.go
23
metadata.go
@@ -85,8 +85,8 @@ func (m Metadata) hasHistoricalPath(path Path) bool {
|
||||
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) {
|
||||
m.effectivePaths = make(map[string]Path)
|
||||
m.historicalPaths = make(map[string][]Path)
|
||||
walkDocumentPaths(final, Path{}, func(path Path) { m.effectivePaths[path.String()] = path.clone() })
|
||||
add := func(path Path) {
|
||||
walkDocument(final, Path{}, func(path Path, _ *document.Node) { m.effectivePaths[path.String()] = path.clone() })
|
||||
add := func(path Path, _ *document.Node) {
|
||||
pointer := path.String()
|
||||
for _, prior := range m.historicalPaths[pointer] {
|
||||
if matchesPathShape(path, prior) {
|
||||
@@ -95,9 +95,9 @@ func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []his
|
||||
}
|
||||
m.historicalPaths[pointer] = append(m.historicalPaths[pointer], path.clone())
|
||||
}
|
||||
walkDocumentPaths(final, Path{}, add)
|
||||
walkDocument(final, Path{}, add)
|
||||
for _, tree := range earlier {
|
||||
walkDocumentPaths(tree, Path{}, add)
|
||||
walkDocument(tree, Path{}, add)
|
||||
}
|
||||
for _, application := range applications {
|
||||
// Overrides have mapping-only prefixes; parsed tokens are keys in this layer.
|
||||
@@ -105,19 +105,6 @@ func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []his
|
||||
for _, segment := range application.path.segments {
|
||||
prefix = prefix.Key(segment.key)
|
||||
}
|
||||
walkDocumentPaths(application.value, prefix, add)
|
||||
}
|
||||
}
|
||||
|
||||
func walkDocumentPaths(node *document.Node, path Path, visit func(Path)) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
visit(path)
|
||||
for _, entry := range node.Mapping {
|
||||
walkDocumentPaths(entry.Value, path.Key(entry.Key), visit)
|
||||
}
|
||||
for i, child := range node.Sequence {
|
||||
walkDocumentPaths(child, path.Index(i), visit)
|
||||
walkDocument(application.value, prefix, add)
|
||||
}
|
||||
}
|
||||
|
||||
15
secret.go
15
secret.go
@@ -144,21 +144,10 @@ func schemaSecretRoot(schema *schemaDescriptor, path Path) (Path, bool) {
|
||||
// is equal. Validators must preserve this inspection metadata as well as values.
|
||||
func projectedSensitivity(root *document.Node) map[string]Path {
|
||||
result := make(map[string]Path)
|
||||
var visit func(*document.Node, Path)
|
||||
visit = func(node *document.Node, path Path) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
walkDocument(root, Path{}, func(path Path, node *document.Node) {
|
||||
if node.Sensitive {
|
||||
result[path.String()] = path.clone()
|
||||
}
|
||||
for _, entry := range node.Mapping {
|
||||
visit(entry.Value, path.Key(entry.Key))
|
||||
}
|
||||
for i, child := range node.Sequence {
|
||||
visit(child, path.Index(i))
|
||||
}
|
||||
}
|
||||
visit(root, Path{})
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user