Files
chatlog_alpha/pkg/config/struct_kerys.go
lx1056758714-glitch 3d04a7f3eb 同步本地代码
2025-12-13 17:30:38 +08:00

150 lines
4.8 KiB
Go

package config
import (
"reflect"
"strings"
)
const sep = "."
// GetStructKeys returns all keys in a nested struct type, taking the name from the tag name or
// the field name. It handles an additional suffix squashValue like mapstructure does: if
// present on an embedded struct, name components for that embedded struct should not be
// included. It does not handle maps, does chase pointers, but does not check for loops in
// nesting.
func GetStructKeys(typ reflect.Type, tag, squashValue string) []string {
return appendStructKeys(typ, tag, ","+squashValue, nil, nil)
}
// appendStructKeys recursively appends to keys all keys of nested struct type typ, taking tag
// and squashValue from GetStructKeys. prefix holds all components of the path from the typ
// passed to GetStructKeys down to this typ.
func appendStructKeys(typ reflect.Type, tag, squashValue string, prefix []string, keys []string) []string {
// Dereference any pointers. This is a finite loop: Go types are well-founded.
for ; typ.Kind() == reflect.Ptr; typ = typ.Elem() {
}
// Handle only struct containers; terminate the recursion on anything else.
if typ.Kind() != reflect.Struct {
return append(keys, strings.Join(prefix, sep))
}
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)
var (
// fieldName is the name to use for the field.
fieldName string
// If squash is true, squash the sub-struct no additional accessor.
squash bool
ok bool
)
if fieldName, ok = fieldType.Tag.Lookup(tag); ok {
if strings.HasSuffix(fieldName, squashValue) {
squash = true
fieldName = strings.TrimSuffix(fieldName, squashValue)
}
} else {
fieldName = strings.ToLower(fieldType.Name)
}
// Update prefix to recurse into this field.
if !squash {
prefix = append(prefix, fieldName)
}
keys = appendStructKeys(fieldType.Type, tag, squashValue, prefix, keys)
// Restore prefix.
if !squash {
prefix = prefix[:len(prefix)-1]
}
}
return keys
}
// ValidateMissingRequiredKeys returns all keys of value in GetStructKeys format that have an
// additional required tag set but are unset.
func ValidateMissingRequiredKeys(value interface{}, tag, squashValue string) []string {
return appendStructKeysIfZero(reflect.ValueOf(value), tag, ","+squashValue, "validate", "required", nil, nil)
}
// isScalar returns true if kind is "scalar", i.e. has no Elem(). This
// recites the list from reflect/type.go and may start to give incorrect
// results if new kinds are added to the language.
func isScalar(kind reflect.Kind) bool {
switch kind {
case reflect.Array:
case reflect.Chan:
case reflect.Map:
case reflect.Ptr:
case reflect.Slice:
return false
}
return true
}
func appendStructKeysIfZero(value reflect.Value, tag, squashValue, validateTag, requiredValue string, prefix []string, keys []string) []string {
// finite loop: Go types are well-founded.
for value.Kind() == reflect.Ptr {
if value.IsZero() { // If required, would already have errored out.
return keys
}
value = value.Elem()
}
if !isScalar(value.Kind()) {
// Why use Type().Elem() when reflect.Value provides a perfectly good Elem()
// method? The two are *not* the same, e.g. for nil pointers value.Elem() is
// invalid and has no Kind(). (See https://play.golang.org/p/M3ZV19AZAW0)
if !isScalar(value.Type().Elem().Kind()) {
// TODO(ariels): Possible to add, but need to define the semantics. One
// way might be to validate each field according to its type.
panic("No support for detecting required keys inside " + value.Kind().String() + " of structs")
}
}
// Handle only struct containers; terminate the recursion on anything else.
if value.Kind() != reflect.Struct {
return keys
}
for i := 0; i < value.NumField(); i++ {
fieldType := value.Type().Field(i)
fieldValue := value.Field(i)
var (
// fieldName is the name to use for the field.
fieldName string
// If squash is true, squash the sub-struct no additional accessor.
squash bool
ok bool
)
if fieldName, ok = fieldType.Tag.Lookup(tag); ok {
if strings.HasSuffix(fieldName, squashValue) {
squash = true
fieldName = strings.TrimSuffix(fieldName, squashValue)
}
} else {
fieldName = strings.ToLower(fieldType.Name)
}
// Perform any needed validations.
if validationsString, ok := fieldType.Tag.Lookup(validateTag); ok {
for _, validation := range strings.Split(validationsString, ",") {
// Validate "required" field.
if validation == requiredValue && fieldValue.IsZero() {
keys = append(keys, strings.Join(append(prefix, fieldName), sep))
}
}
}
// Update prefix to recurse into this field.
if !squash {
prefix = append(prefix, fieldName)
}
keys = appendStructKeysIfZero(fieldValue, tag, squashValue, validateTag, requiredValue, prefix, keys)
// Restore prefix.
if !squash {
prefix = prefix[:len(prefix)-1]
}
}
return keys
}