Repository navigation
Expand file tree
/
Copy pathmerge.go
More file actions
285 lines (235 loc) · 7.95 KB
/
Copy pathmerge.go
File metadata and controls
285 lines (235 loc) · 7.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package config
import (
"context"
"errors"
"fmt"
"github.com/tarantool/go-config/v2/keypath"
"github.com/tarantool/go-config/v2/tree"
)
// isNumericString reports whether str consists only of ASCII digits.
func isNumericString(str string) bool {
if str == "" {
return false
}
for i := range len(str) {
if str[i] < '0' || str[i] > '9' {
return false
}
}
return true
}
// MergeCollector reads all values from a collector and merges them into the tree
// using the default merging logic.
// The collector's priority is determined by the caller (higher priority collectors
// should be merged later). This function handles primitive replacement, slice
// replacement, map recursive merging, and key order preservation based on the
// collector's KeepOrder flag.
func MergeCollector(ctx context.Context, root *tree.Node, col Collector) error {
return MergeCollectorWithMerger(ctx, root, col, Default)
}
// MergeCollectorWithMerger reads all values from a collector and merges them into the tree
// using the provided merger. Returns a CollectorError if any errors occur during processing.
// Multiple errors are accumulated and returned together.
func MergeCollectorWithMerger(ctx context.Context, root *tree.Node, col Collector, merger Merger) error {
mergeCtx := merger.CreateContext(col)
valueCh := col.Read(ctx)
var errs []error
for val := range valueCh {
meta := val.Meta()
var raw any
err := val.Get(&raw)
if err != nil {
errs = append(errs, fmt.Errorf("failed to get raw value for key %s: %w", meta.Key.String(), err))
continue
}
err = merger.MergeValue(mergeCtx, root, meta.Key, raw)
if err != nil {
errs = append(errs, fmt.Errorf("merge value at %s: %w", meta.Key.String(), err))
continue
}
// If the source value carries a format-specific annotation
// (e.g., the YAML node it was parsed from), forward it onto
// the destination tree node so that marshalers can reproduce
// scalar style and comments.
copyAnnotation(root, meta.Key, val)
copyRanges(root, meta.Key, val)
copyTypeFixed(root, meta.Key, val)
}
err := mergeCtx.ApplyOrdering(root)
if err != nil {
errs = append(errs, fmt.Errorf("apply ordering: %w", err))
}
if len(errs) > 0 {
return NewCollectorError(col.Name(), errors.Join(errs...))
}
return nil
}
// mergeValue merges a single value into the tree at the specified path.
func mergeValue(root *tree.Node, keyPath keypath.KeyPath, value any, col Collector) {
if len(keyPath) == 0 {
// Merge at root.
mergeNodeValue(root, value, col)
return
}
// Navigate or create nodes along the path.
node := root
for i, segment := range keyPath {
isLast := i == len(keyPath)-1
child := node.Child(segment)
if child == nil {
// If node currently has a leaf value, clear it because we're adding a child.
if node.Value != nil {
node.Value = nil
}
child = tree.New()
node.SetChild(segment, child)
}
if !isLast && isNumericString(keyPath[i+1]) {
child.MarkArray()
}
if isLast {
// This is the target node for the value.
mergeNodeValue(child, value, col)
} else {
node = child
}
}
}
// mergeNodeValue merges a value into an existing node, handling type-specific merging logic.
func mergeNodeValue(node *tree.Node, value any, col Collector) {
// Determine the merging behavior based on the type of the new value and existing node.
// If the node currently holds a map (has children) and the new value is also a map,
// we need to merge recursively. Otherwise, we replace the node's value entirely.
//
// Slices are always replaced completely (no element‑wise merging).
// Primitives are replaced.
// Array nodes are treated as opaque (replaced wholesale) — they are not
// maps even though they are non-leaf.
m, newIsMap := value.(map[string]any)
currentIsMap := isMapNode(node)
if newIsMap {
if currentIsMap {
// Recursive map merging.
mergeMapIntoNode(node, m, col)
} else {
// Convert leaf or array to map: clear value, drop array flag,
// drop existing children, then merge the map into a clean slate.
node.ClearChildren()
node.UnmarkArray()
node.Value = nil
mergeMapIntoNode(node, m, col)
}
} else {
// Replacement (primitive, slice, or map overwriting non‑map).
// Clear children, drop array flag, reset order flag.
node.ClearChildren()
node.UnmarkArray()
node.Value = value
node.SetTypeFixed(false)
}
// Update node metadata.
node.Source = col.Name()
node.Revision = string(col.Revision())
}
// copyAnnotation forwards the format-specific annotation from a source Value
// onto the destination tree node at path, when the source exposes one.
func copyAnnotation(root *tree.Node, path keypath.KeyPath, src Value) {
carrier, ok := src.(interface{ Annotation() any })
if !ok {
return
}
anno := carrier.Annotation()
if anno == nil {
return
}
dest := root.Get(path)
if dest == nil {
return
}
dest.SetAnnotation(anno)
}
// copyRanges forwards the source ranges a value carries onto the destination
// nodes along its path. The value's own node always takes its range, so a
// value without one does not keep the range of the value it replaced; a map
// or array above it takes a range only when the collector knows one.
func copyRanges(root *tree.Node, path keypath.KeyPath, src Value) {
var ranges []tree.Range
if carrier, ok := src.(interface{ PathRanges() []tree.Range }); ok {
ranges = carrier.PathRanges()
}
if len(ranges) == 0 || len(ranges) > len(path)+1 {
ranges = []tree.Range{tree.NewZeroRange()}
}
// The ranges end with the value's own node; align them with path's end.
first := len(path) + 1 - len(ranges)
for i, rng := range ranges {
own := i == len(ranges)-1
if !own && rng == tree.NewZeroRange() {
continue
}
if dest := root.Get(path[:first+i]); dest != nil {
dest.Range = rng
}
}
}
// copyTypeFixed forwards whether the source fixed the type of a value onto
// the destination node at path. The node always takes the flag of the value
// merged into it, so an untyped value does not keep the fixed type of the
// value it replaced.
func copyTypeFixed(root *tree.Node, path keypath.KeyPath, src Value) {
dest := root.Get(path)
if dest == nil {
return
}
carrier, ok := src.(interface{ TypeFixed() bool })
dest.SetTypeFixed(ok && carrier.TypeFixed())
}
// mergeTreeInto folds src into dst at the tree level.
// Map-into-map is recursive; any other src child (leaf or array) replaces
// the dst child wholesale (carrying Source, Revision, Range, annotation,
// and isArray). Arrays are opaque — index-merging would leak orphan
// lower-priority indices into the result.
// When src.OrderSet() and !dst.OrderSet() the dst children are reordered
// to match src's key order and dst.OrderSet() is set true.
func mergeTreeInto(dst, src *tree.Node) {
if src.Range != tree.NewZeroRange() {
dst.Range = src.Range
}
for _, key := range src.ChildrenKeys() {
srcChild := src.Child(key)
dstChild := dst.Child(key)
// Both sides must be maps (not leaves, not arrays) to recurse.
if isMapNode(dstChild) && isMapNode(srcChild) {
// Carry ordering before recursing so children see the right dst state.
if srcChild.OrderSet() && !dstChild.OrderSet() {
_ = dstChild.ReorderChildren(srcChild.ChildrenKeys())
dstChild.SetOrderSet(true)
}
mergeTreeInto(dstChild, srcChild)
continue
}
// Otherwise replace: clone the entire src child subtree into dst.
dst.SetChild(key, cloneNode(srcChild))
}
// Apply ordering at this level.
if src.OrderSet() && !dst.OrderSet() {
_ = dst.ReorderChildren(src.ChildrenKeys())
dst.SetOrderSet(true)
}
}
// mergeMapIntoNode merges a map into a node's children recursively.
func mergeMapIntoNode(node *tree.Node, m map[string]any, col Collector) {
// If node has a leaf value, clear it because we're merging a map into it.
if node.Value != nil {
node.Value = nil
}
for key, val := range m {
child := node.Child(key)
if child == nil {
child = tree.New()
node.SetChild(key, child)
}
// Recurse for this child.
mergeNodeValue(child, val, col)
}
}