indexConfigObjects recursively searches ptr for object fields named "@id" and maps that ID value to the full configPath in the index. This function is NOT safe for concurrent access; obtain a write lock on currentCtxMu.
(ptr any, configPath string, index map[string]string)
| 288 | // This function is NOT safe for concurrent access; obtain a write lock |
| 289 | // on currentCtxMu. |
| 290 | func indexConfigObjects(ptr any, configPath string, index map[string]string) error { |
| 291 | switch val := ptr.(type) { |
| 292 | case map[string]any: |
| 293 | for k, v := range val { |
| 294 | if k == idKey { |
| 295 | var idStr string |
| 296 | switch idVal := v.(type) { |
| 297 | case string: |
| 298 | idStr = idVal |
| 299 | case float64: // all JSON numbers decode as float64 |
| 300 | idStr = fmt.Sprintf("%v", idVal) |
| 301 | default: |
| 302 | return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey) |
| 303 | } |
| 304 | if existingPath, ok := index[idStr]; ok { |
| 305 | return fmt.Errorf("duplicate ID '%s' found at %s and %s", idStr, existingPath, configPath) |
| 306 | } |
| 307 | index[idStr] = configPath |
| 308 | continue |
| 309 | } |
| 310 | // traverse this object property recursively |
| 311 | err := indexConfigObjects(val[k], path.Join(configPath, k), index) |
| 312 | if err != nil { |
| 313 | return err |
| 314 | } |
| 315 | } |
| 316 | case []any: |
| 317 | // traverse each element of the array recursively |
| 318 | for i := range val { |
| 319 | err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index) |
| 320 | if err != nil { |
| 321 | return err |
| 322 | } |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | return nil |
| 327 | } |
| 328 | |
| 329 | // unsyncedDecodeAndRun removes any meta fields (like @id tags) |
| 330 | // from cfgJSON, decodes the result into a *Config, and runs |