in mapstr/mapstr.go [282:354]
func (m M) Traverse(path string, mode TraversalMode, visitor TraversalVisitor) (err error) {
segments := strings.Split(path, ".")
var match func(string, string) bool
switch mode {
case CaseInsensitiveMode:
match = strings.EqualFold
case CaseSensitiveMode:
match = func(a, b string) bool { return a == b }
}
// the initial value must be `true` for the first iteration to work
found := true
// start with the root
current := m
// allocate only once
var (
mapType bool
next interface{}
)
for i, segment := range segments {
if !found {
return fmt.Errorf("could not fetch value for key: %s, Error: %w ", path, ErrKeyNotFound)
}
found = false
// we have to go through the list of all key on each level to detect case-insensitive collisions
for k := range current {
if !match(segment, k) {
continue
}
// if already found on this level, it's a collision
if found {
return fmt.Errorf("multiple keys match %q on the same level of the path %q: %w", k, path, ErrKeyCollision)
}
// mark for collision detection
found = true
// we need to save this in case the visitor makes changes in keys
next = current[k]
err = visitor(current, k)
if err != nil {
return fmt.Errorf("error visiting key %q of the path %q: %w", k, path, err)
}
// if it's the last segment, we don't need to go deeper, skipping...
if i == len(segments)-1 {
continue
}
// try to go one level deeper
current, mapType = tryToMapStr(next)
if !mapType {
return fmt.Errorf("cannot continue path %q, next value %q is not a map: %w", path, k, ErrNotMapType)
}
// if it's a case-sensitive key match, we don't have to care about collision detection
// and we can simply stop iterating here.
if mode == CaseSensitiveMode {
break
}
}
}
if !found {
return fmt.Errorf("could not fetch value for key: %s, Error: %w", path, ErrKeyNotFound)
}
return nil
}