master
go 353 lines 8.46 KB
Raw
1 package common
2
3 import (
4 "fmt"
5 "maps"
6 "slices"
7 "strings"
8 )
9
10 // GetField retrieves a field from a nested config structure using a dot-separated path
11 // Example: GetField(config, "DNS.Resolvers") returns config["DNS"]["Resolvers"]
12 func GetField(config map[string]any, path string) (any, bool) {
13 parts := strings.Split(path, ".")
14 current := config
15
16 for i, part := range parts {
17 // Last part - return the value
18 if i == len(parts)-1 {
19 val, exists := current[part]
20 return val, exists
21 }
22
23 // Navigate deeper
24 next, exists := current[part]
25 if !exists {
26 return nil, false
27 }
28
29 // Ensure it's a map
30 nextMap, ok := next.(map[string]any)
31 if !ok {
32 return nil, false
33 }
34 current = nextMap
35 }
36
37 return nil, false
38 }
39
40 // SetField sets a field in a nested config structure using a dot-separated path
41 // It creates intermediate maps as needed
42 func SetField(config map[string]any, path string, value any) {
43 parts := strings.Split(path, ".")
44 current := config
45
46 for i, part := range parts {
47 // Last part - set the value
48 if i == len(parts)-1 {
49 current[part] = value
50 return
51 }
52
53 // Navigate or create intermediate maps
54 next, exists := current[part]
55 if !exists {
56 // Create new intermediate map
57 newMap := make(map[string]any)
58 current[part] = newMap
59 current = newMap
60 } else {
61 // Ensure it's a map
62 nextMap, ok := next.(map[string]any)
63 if !ok {
64 // Can't navigate further, replace with new map
65 newMap := make(map[string]any)
66 current[part] = newMap
67 current = newMap
68 } else {
69 current = nextMap
70 }
71 }
72 }
73 }
74
75 // DeleteField removes a field from a nested config structure
76 func DeleteField(config map[string]any, path string) bool {
77 parts := strings.Split(path, ".")
78
79 // Handle simple case
80 if len(parts) == 1 {
81 _, exists := config[parts[0]]
82 delete(config, parts[0])
83 return exists
84 }
85
86 // Navigate to parent
87 parentPath := strings.Join(parts[:len(parts)-1], ".")
88 parent, exists := GetField(config, parentPath)
89 if !exists {
90 return false
91 }
92
93 parentMap, ok := parent.(map[string]any)
94 if !ok {
95 return false
96 }
97
98 fieldName := parts[len(parts)-1]
99 _, exists = parentMap[fieldName]
100 delete(parentMap, fieldName)
101 return exists
102 }
103
104 // MoveField moves a field from one location to another
105 func MoveField(config map[string]any, from, to string) error {
106 value, exists := GetField(config, from)
107 if !exists {
108 return fmt.Errorf("source field %s does not exist", from)
109 }
110
111 SetField(config, to, value)
112 DeleteField(config, from)
113 return nil
114 }
115
116 // RenameField renames a field within the same parent
117 func RenameField(config map[string]any, path, oldName, newName string) error {
118 var parent map[string]any
119 if path == "" {
120 parent = config
121 } else {
122 p, exists := GetField(config, path)
123 if !exists {
124 return fmt.Errorf("parent path %s does not exist", path)
125 }
126 var ok bool
127 parent, ok = p.(map[string]any)
128 if !ok {
129 return fmt.Errorf("parent path %s is not a map", path)
130 }
131 }
132
133 value, exists := parent[oldName]
134 if !exists {
135 return fmt.Errorf("field %s does not exist", oldName)
136 }
137
138 parent[newName] = value
139 delete(parent, oldName)
140 return nil
141 }
142
143 // SetDefault sets a field value only if it doesn't already exist
144 func SetDefault(config map[string]any, path string, value any) {
145 if _, exists := GetField(config, path); !exists {
146 SetField(config, path, value)
147 }
148 }
149
150 // TransformField applies a transformation function to a field value
151 func TransformField(config map[string]any, path string, transformer func(any) any) error {
152 value, exists := GetField(config, path)
153 if !exists {
154 return fmt.Errorf("field %s does not exist", path)
155 }
156
157 newValue := transformer(value)
158 SetField(config, path, newValue)
159 return nil
160 }
161
162 // EnsureFieldIs checks if a field equals expected value, sets it if missing
163 func EnsureFieldIs(config map[string]any, path string, expected any) {
164 current, exists := GetField(config, path)
165 if !exists || current != expected {
166 SetField(config, path, expected)
167 }
168 }
169
170 // MergeInto merges multiple source fields into a destination map
171 func MergeInto(config map[string]any, destination string, sources ...string) {
172 var destMap map[string]any
173
174 // Get existing destination if it exists
175 if existing, exists := GetField(config, destination); exists {
176 if m, ok := existing.(map[string]any); ok {
177 destMap = m
178 }
179 }
180
181 // Merge each source
182 for _, source := range sources {
183 if value, exists := GetField(config, source); exists {
184 if sourceMap, ok := value.(map[string]any); ok {
185 if destMap == nil {
186 destMap = make(map[string]any)
187 }
188 maps.Copy(destMap, sourceMap)
189 }
190 }
191 }
192
193 if destMap != nil {
194 SetField(config, destination, destMap)
195 }
196 }
197
198 // CopyField copies a field value to a new location (keeps original)
199 func CopyField(config map[string]any, from, to string) error {
200 value, exists := GetField(config, from)
201 if !exists {
202 return fmt.Errorf("source field %s does not exist", from)
203 }
204
205 SetField(config, to, value)
206 return nil
207 }
208
209 // ConvertInterfaceSlice converts []interface{} to []string
210 func ConvertInterfaceSlice(slice []any) []string {
211 result := make([]string, 0, len(slice))
212 for _, item := range slice {
213 if str, ok := item.(string); ok {
214 result = append(result, str)
215 }
216 }
217 return result
218 }
219
220 // GetOrCreateSection gets or creates a map section in config
221 func GetOrCreateSection(config map[string]any, path string) map[string]any {
222 existing, exists := GetField(config, path)
223 if exists {
224 if section, ok := existing.(map[string]any); ok {
225 return section
226 }
227 }
228
229 // Create new section
230 section := make(map[string]any)
231 SetField(config, path, section)
232 return section
233 }
234
235 // SafeCastMap safely casts to map[string]any with fallback to empty map
236 func SafeCastMap(value any) map[string]any {
237 if m, ok := value.(map[string]any); ok {
238 return m
239 }
240 return make(map[string]any)
241 }
242
243 // SafeCastSlice safely casts to []interface{} with fallback to empty slice
244 func SafeCastSlice(value any) []any {
245 if s, ok := value.([]any); ok {
246 return s
247 }
248 return []any{}
249 }
250
251 // ReplaceDefaultsWithAuto replaces default values with "auto" in a map
252 func ReplaceDefaultsWithAuto(values map[string]any, defaults map[string]string) map[string]string {
253 result := make(map[string]string)
254 for k, v := range values {
255 if vStr, ok := v.(string); ok {
256 if replacement, isDefault := defaults[vStr]; isDefault {
257 result[k] = replacement
258 } else {
259 result[k] = vStr
260 }
261 }
262 }
263 return result
264 }
265
266 // EnsureSliceContains ensures a slice field contains a value
267 func EnsureSliceContains(config map[string]any, path string, value string) {
268 existing, exists := GetField(config, path)
269 if !exists {
270 SetField(config, path, []string{value})
271 return
272 }
273
274 if slice, ok := existing.([]any); ok {
275 // Check if value already exists
276 for _, item := range slice {
277 if str, ok := item.(string); ok && str == value {
278 return // Already contains value
279 }
280 }
281 // Add value
282 SetField(config, path, append(slice, value))
283 } else if strSlice, ok := existing.([]string); ok {
284 if !slices.Contains(strSlice, value) {
285 SetField(config, path, append(strSlice, value))
286 }
287 } else {
288 // Replace with new slice containing value
289 SetField(config, path, []string{value})
290 }
291 }
292
293 // ReplaceInSlice replaces old values with new in a slice field
294 func ReplaceInSlice(config map[string]any, path string, oldValue, newValue string) {
295 existing, exists := GetField(config, path)
296 if !exists {
297 return
298 }
299
300 if slice, ok := existing.([]any); ok {
301 result := make([]string, 0, len(slice))
302 for _, item := range slice {
303 if str, ok := item.(string); ok {
304 if str == oldValue {
305 result = append(result, newValue)
306 } else {
307 result = append(result, str)
308 }
309 }
310 }
311 SetField(config, path, result)
312 }
313 }
314
315 // GetMapSection gets a map section with error handling
316 func GetMapSection(config map[string]any, path string) (map[string]any, error) {
317 value, exists := GetField(config, path)
318 if !exists {
319 return nil, fmt.Errorf("section %s does not exist", path)
320 }
321
322 section, ok := value.(map[string]any)
323 if !ok {
324 return nil, fmt.Errorf("section %s is not a map", path)
325 }
326
327 return section, nil
328 }
329
330 // CloneStringMap clones a map[string]any to map[string]string
331 func CloneStringMap(m map[string]any) map[string]string {
332 result := make(map[string]string, len(m))
333 for k, v := range m {
334 if str, ok := v.(string); ok {
335 result[k] = str
336 }
337 }
338 return result
339 }
340
341 // IsEmptySlice checks if a value is an empty slice
342 func IsEmptySlice(value any) bool {
343 if value == nil {
344 return true
345 }
346 if slice, ok := value.([]any); ok {
347 return len(slice) == 0
348 }
349 if slice, ok := value.([]string); ok {
350 return len(slice) == 0
351 }
352 return false
353 }