Files
upstream-centrifugo/internal/configtypes/fieldmeta.go
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

104 lines
3.3 KiB
Go

package configtypes
import (
"reflect"
"strings"
)
// This file hosts small reflection helpers shared by config tooling that walks
// the configuration structs — currently the `configdoc` generator and the admin
// config redaction (Pro). Keeping them here, in OSS, gives both a single source
// of truth for how config fields are classified, named, and flattened. See also
// NameForEnv in namespace.go.
// JSONKey returns the configured key for a struct field: the first segment of the
// json tag (falling back to the mapstructure tag), or the Go field name when no
// tag is present.
func JSONKey(f reflect.StructField) string {
tag := f.Tag.Get("json")
if tag == "" {
tag = f.Tag.Get("mapstructure")
}
if tag == "" {
return f.Name
}
if name := strings.Split(tag, ",")[0]; name != "" {
return name
}
return f.Name
}
// IsSquash reports whether a struct field is squashed/inlined into its parent
// (an embedded struct, a mapstructure ",squash", or a yaml ",inline"). Such
// fields contribute their own fields to the parent rather than a nested object.
func IsSquash(f reflect.StructField) bool {
return f.Anonymous ||
strings.Contains(f.Tag.Get("mapstructure"), "squash") ||
strings.Contains(f.Tag.Get("yaml"), "inline")
}
// IsComplexType reports whether t is object-like — a struct or a slice/array of
// structs — and therefore cannot be populated from a single environment variable
// (its keys are set separately). Scalars, slices of scalars, and maps are not
// complex (each maps to one env var). time.Time is treated as a scalar.
func IsComplexType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Pointer:
return t.Elem().Kind() == reflect.Struct && !isTimeType(t.Elem())
case reflect.Struct:
return !isTimeType(t)
case reflect.Slice, reflect.Array:
elem := t.Elem()
if elem.Kind() == reflect.Pointer {
elem = elem.Elem()
}
return elem.Kind() == reflect.Struct && !isTimeType(elem)
default:
return false
}
}
func isTimeType(t reflect.Type) bool {
return t.PkgPath() == "time" && t.Name() == "Time"
}
// TypeLabel renders a human-friendly type label for a config field type. Struct
// and slice-of-struct types show their Go type name (e.g. "TLSConfig",
// "[]Consumer"); every other type shows its Go type string with the internal
// "configtypes." package qualifier stripped (e.g. "Duration", "PEMData", "int64",
// "[]string", "map[string]string"). Used by both the configdoc generator and the
// admin config UI (Pro) so the two surfaces present identical type labels.
func TypeLabel(t reflect.Type) string {
if t.Kind() == reflect.Pointer {
if t.Elem().Kind() == reflect.Struct {
if isTimeType(t.Elem()) {
return stripConfigtypesQualifier(t.String())
}
return t.Elem().Name()
}
}
if t.Kind() == reflect.Struct {
if isTimeType(t) {
return stripConfigtypesQualifier(t.String())
}
return t.Name()
}
if t.Kind() == reflect.Slice {
elem := t.Elem()
if elem.Kind() == reflect.Pointer {
elem = elem.Elem()
}
if elem.Kind() == reflect.Struct {
if isTimeType(elem) {
return stripConfigtypesQualifier(t.String())
}
return "[]" + elem.Name()
}
}
return stripConfigtypesQualifier(t.String())
}
func stripConfigtypesQualifier(typeStr string) string {
return strings.ReplaceAll(typeStr, "configtypes.", "")
}