package main import "fmt" // providerCollector owns selector, matches, and seen for provider traversal. type providerCollector struct { selector string matches []match seen map[string]bool } // newProviderCollector returns a collector ready to find selector matches. func newProviderCollector(selector string) *providerCollector { return &providerCollector{ selector: selector, seen: make(map[string]bool), } } // addEntryMatch records a match when the entry value or key equals the selector. // isKeyMatch reports whether the map key itself equals the selector. // item is the entry value, which may be a scalar, map with id/provider, or other. func (c *providerCollector) addEntryMatch(isKeyMatch bool, item interface{}, entryPath string) { if isKeyMatch { c.matches = append(c.matches, match{Path: entryPath, Value: item}) c.seen[entryPath] = true return } switch v := item.(type) { case string: if v == c.selector && !c.seen[entryPath] { c.matches = append(c.matches, match{Path: entryPath, Value: v}) c.seen[entryPath] = true } case map[string]interface{}: if id, ok := v["id"].(string); ok && id == c.selector { if !c.seen[entryPath] { c.matches = append(c.matches, match{Path: entryPath, Value: v}) c.seen[entryPath] = true } } else if provider, ok := v["provider"].(string); ok && provider == c.selector { if !c.seen[entryPath] { c.matches = append(c.matches, match{Path: entryPath, Value: v}) c.seen[entryPath] = true } } } } // collect walks val and records provider matches, recursing into nested shapes. func (c *providerCollector) collect(val interface{}, path string) { switch v := val.(type) { case map[string]interface{}: c.checkProviderField(v, path) c.checkProvidersField(v, path) c.checkDirectProvidersField(v, path) c.recurseNonProviderFields(v, path) case []interface{}: for i, item := range v { c.collect(item, fmt.Sprintf("%s[%d]", path, i)) } } } // checkProviderField inspects the singular "provider" key for selector matches. func (c *providerCollector) checkProviderField(obj map[string]interface{}, basePath string) { provVal, ok := obj["provider"] if !ok { return } childPath := joinPath(basePath, "provider") switch p := provVal.(type) { case string: if p == c.selector && !c.seen[childPath] { c.matches = append(c.matches, match{Path: childPath, Value: p}) c.seen[childPath] = true } case map[string]interface{}: if id, ok := p["id"].(string); ok && id == c.selector { if !c.seen[childPath] { c.matches = append(c.matches, match{Path: childPath, Value: p}) c.seen[childPath] = true } } else if id, ok := p["provider"].(string); ok && id == c.selector { if !c.seen[childPath] { c.matches = append(c.matches, match{Path: childPath, Value: p}) c.seen[childPath] = true } } } } // checkProvidersField inspects the "providers" key (map or list) for selector matches. func (c *providerCollector) checkProvidersField(obj map[string]interface{}, basePath string) { provsVal, ok := obj["providers"] if !ok { return } childPath := joinPath(basePath, "providers") switch p := provsVal.(type) { case map[string]interface{}: for key, item := range p { entryPath := joinPath(childPath, key) c.addEntryMatch(key == c.selector, item, entryPath) c.collect(item, entryPath) } case []interface{}: for i, item := range p { pPath := fmt.Sprintf("%s[%d]", childPath, i) if s, ok := item.(string); ok && s == c.selector { if !c.seen[pPath] { c.matches = append(c.matches, match{Path: pPath, Value: s}) c.seen[pPath] = true } } else if m, ok := item.(map[string]interface{}); ok { if id, ok := m["id"].(string); ok && id == c.selector { if !c.seen[pPath] { c.matches = append(c.matches, match{Path: pPath, Value: m}) c.seen[pPath] = true } } else if id, ok := m["provider"].(string); ok && id == c.selector { if !c.seen[pPath] { c.matches = append(c.matches, match{Path: pPath, Value: m}) c.seen[pPath] = true } } } c.collect(item, pPath) } } } // checkDirectProvidersField inspects the "direct_providers" list for selector matches. func (c *providerCollector) checkDirectProvidersField(obj map[string]interface{}, basePath string) { dpsVal, ok := obj["direct_providers"] if !ok { return } p, ok := dpsVal.([]interface{}) if !ok { return } childPath := joinPath(basePath, "direct_providers") for i, item := range p { pPath := fmt.Sprintf("%s[%d]", childPath, i) if s, ok := item.(string); ok && s == c.selector { if !c.seen[pPath] { c.matches = append(c.matches, match{Path: pPath, Value: s}) c.seen[pPath] = true } } else if m, ok := item.(map[string]interface{}); ok { if id, ok := m["id"].(string); ok && id == c.selector { if !c.seen[pPath] { c.matches = append(c.matches, match{Path: pPath, Value: m}) c.seen[pPath] = true } } } c.collect(item, pPath) } } // recurseNonProviderFields continues traversal into fields that are not provider-shaped. func (c *providerCollector) recurseNonProviderFields(obj map[string]interface{}, basePath string) { for k, valItem := range obj { if k == "provider" || k == "providers" || k == "direct_providers" { continue } c.collect(valItem, joinPath(basePath, k)) } }