package bind import ( "fmt" "github.com/iancoleman/strcase" "reflect" "unicode" ) func exported(name string) bool { return unicode.IsUpper([]rune(name)[0]) } func fields(t reflect.Type) []Field { t = unpackType(t, pointer) list := t.Kind() == reflect.Slice t = unpackType(t, pointer|slice) if t.Kind() != reflect.Struct { return nil } var f []Field for i := 0; i < t.NumField(); i++ { tfi := t.Field(i) if !exported(tfi.Name) && !tfi.Anonymous { continue } if acceptsScalar(tfi.Type) { var fi Field ft := unpackType(tfi.Type, pointer|slice) fi.isBool = ft.Kind() == reflect.Bool fi.list = acceptsList(tfi.Type) fi.name = strcase.ToKebab(tfi.Name) fi.path = []string{tfi.Name} f = append(f, fi) continue } ffi := fields(tfi.Type) if !tfi.Anonymous { for i := range ffi { ffi[i].name = fmt.Sprintf( "%s-%s", strcase.ToKebab(tfi.Name), ffi[i].name, ) ffi[i].path = append( []string{tfi.Name}, ffi[i].path..., ) } } f = append(f, ffi...) } if list { for i := range f { f[i].list = true } } return f } func fieldFromValue(name string, v reflect.Value) Field { var fi Field fi.name = strcase.ToKebab(name) fi.path = []string{name} fi.value = v.Interface() fi.isBool = v.Kind() == reflect.Bool return fi } func scalarMapFields(v reflect.Value) []Field { var f []Field for _, key := range v.MapKeys() { value := v.MapIndex(key) name := key.Interface().(string) if value.Kind() == reflect.Slice { for i := 0; i < value.Len(); i++ { fi := fieldFromValue(name, value.Index(i)) fi.free = true f = append(f, fi) } } else { fi := fieldFromValue(name, value) fi.free = true f = append(f, fi) } } return f } func prependFieldName(name string, f []Field) { for i := range f { f[i].name = fmt.Sprintf("%s-%s", strcase.ToKebab(name), f[i].name) f[i].path = append([]string{name}, f[i].path...) } } func listFieldValues(fieldName string, l reflect.Value) []Field { var f []Field for i := 0; i < l.Len(); i++ { item := l.Index(i) item = unpackValue(item, pointer|iface|anytype) switch { case isScalar(item.Type()): f = append(f, fieldFromValue(fieldName, item)) case item.Kind() == reflect.Slice: f = append(f, listFieldValues(fieldName, item)...) case isScalarMap(item.Type()): mf := fieldValues(item) prependFieldName(fieldName, mf) f = append(f, mf...) case item.Kind() == reflect.Struct: sf := fieldValues(item) prependFieldName(fieldName, sf) f = append(f, sf...) } } return f } func fieldValues(v reflect.Value) []Field { var f []Field v = unpackValue(v, pointer|anytype|iface) if v.Kind() == reflect.Slice { for i := 0; i < v.Len(); i++ { f = append(f, fieldValues(v.Index(i))...) } return f } t := v.Type() if isScalarMap(t) { return scalarMapFields(v) } if t.Kind() != reflect.Struct { return nil } for i := 0; i < t.NumField(); i++ { tfi := t.Field(i) if !exported(tfi.Name) && !tfi.Anonymous { continue } vfi := v.Field(i) vfi = unpackValue(vfi, pointer|iface|anytype) switch { case isScalar(vfi.Type()): f = append(f, fieldFromValue(tfi.Name, vfi)) case vfi.Kind() == reflect.Slice: f = append(f, listFieldValues(tfi.Name, vfi)...) case isScalarMap(vfi.Type()): mf := fieldValues(vfi) prependFieldName(tfi.Name, mf) f = append(f, mf...) case vfi.Kind() == reflect.Struct: sf := fieldValues(vfi) if !tfi.Anonymous { prependFieldName(tfi.Name, sf) } f = append(f, sf...) } } return f } func fieldsReflect[T any]() []Field { t := reflect.TypeFor[T]() if hasCircularType(nil, t) { return nil } return fields(t) } func fieldValuesReflect(structure any) []Field { v := reflect.ValueOf(structure) if hasCircularReference(nil, v) { return nil } return fieldValues(v) }