Files
tailscale/tstest/typewalk/typewalk.go
Brad Fitzpatrick 653d0738f9 types/netmap: remove PrivateKey from NetworkMap
It's an unnecessary nuisance having it. We go out of our way to redact
it in so many places when we don't even need it there anyway.

Updates #12639

Change-Id: I5fc72e19e9cf36caeb42cf80ba430873f67167c3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-11-16 15:32:51 -08:00

107 lines
2.6 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package typewalk provides utilities to walk Go types using reflection.
package typewalk
import (
"iter"
"reflect"
"strings"
)
// Path describes a path via a type where a private key may be found,
// along with a function to test whether a reflect.Value at that path is
// non-zero.
type Path struct {
// Name is the path from the root type, suitable for using as a t.Run name.
Name string
// Walk returns the reflect.Value at the end of the path, given a root
// reflect.Value.
Walk func(root reflect.Value) (leaf reflect.Value)
}
// MatchingPaths returns a sequence of [Path] for all paths
// within the given type that end in a type matching match.
func MatchingPaths(rt reflect.Type, match func(reflect.Type) bool) iter.Seq[Path] {
// valFromRoot is a function that, given a reflect.Value of the root struct,
// returns the reflect.Value at some path within it.
type valFromRoot func(reflect.Value) reflect.Value
return func(yield func(Path) bool) {
var walk func(reflect.Type, valFromRoot)
var path []string
var done bool
seen := map[reflect.Type]bool{}
walk = func(t reflect.Type, getV valFromRoot) {
if seen[t] {
return
}
seen[t] = true
defer func() { seen[t] = false }()
if done {
return
}
if match(t) {
if !yield(Path{
Name: strings.Join(path, "."),
Walk: getV,
}) {
done = true
}
return
}
switch t.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Array:
walk(t.Elem(), func(root reflect.Value) reflect.Value {
v := getV(root)
return v.Elem()
})
case reflect.Struct:
for i := range t.NumField() {
sf := t.Field(i)
fieldName := sf.Name
if fieldName == "_" {
continue
}
path = append(path, fieldName)
walk(sf.Type, func(root reflect.Value) reflect.Value {
return getV(root).FieldByName(fieldName)
})
path = path[:len(path)-1]
if done {
return
}
}
case reflect.Map:
walk(t.Elem(), func(root reflect.Value) reflect.Value {
v := getV(root)
if v.Len() == 0 {
return reflect.Zero(t.Elem())
}
iter := v.MapRange()
iter.Next()
return iter.Value()
})
if done {
return
}
walk(t.Key(), func(root reflect.Value) reflect.Value {
v := getV(root)
if v.Len() == 0 {
return reflect.Zero(t.Key())
}
iter := v.MapRange()
iter.Next()
return iter.Key()
})
}
}
path = append(path, rt.Name())
walk(rt, func(v reflect.Value) reflect.Value { return v })
}
}