172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
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))
|
|
}
|
|
}
|