1
0
bind/field.go
2025-09-04 00:33:05 +02:00

546 lines
9.7 KiB
Go

package bind
import (
"fmt"
"github.com/iancoleman/strcase"
"reflect"
"strings"
"unicode"
)
func pathString(f Field) string {
return strings.Join(f.path, ":")
}
func nameFromPath(p []string) string {
var pp []string
for _, pi := range p {
pp = append(pp, strcase.ToKebab(pi))
}
return strings.Join(pp, "-")
}
func exported(name string) bool {
return unicode.IsUpper([]rune(name)[0])
}
func filterFields(predicate func(Field) bool, f []Field) ([]Field, []Field) {
var yes, no []Field
for _, fi := range f {
if predicate(fi) {
yes = append(yes, fi)
continue
}
no = append(no, fi)
}
return yes, no
}
func fieldHasCircRef(f Field) bool {
return hasCircularReference(reflect.ValueOf(f.Value()))
}
func hasPath(f Field) bool {
return len(f.path) > 0
}
func fieldFromType(name string, t reflect.Type) Field {
var f Field
ft := unpackType(t, pointer|slice)
f.typ = scalarType(ft)
f.list = acceptsList(t)
f.name = strcase.ToKebab(name)
f.path = []string{name}
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 fields(t reflect.Type) []Field {
t = unpackType(t, pointer|slice)
if !isStruct(t) {
return nil
}
var f []Field
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
continue
}
if acceptsScalar(fi.Type) {
fi := fieldFromType(fi.Name, fi.Type)
f = append(f, fi)
continue
}
ffi := fields(fi.Type)
if !fi.Anonymous {
prependFieldName(fi.Name, ffi)
}
if acceptsList(fi.Type) {
for i := range ffi {
ffi[i].list = true
}
}
f = append(f, ffi...)
}
return f
}
func freeFields(f []Field) {
for i := range f {
f[i].free = true
}
}
func scalarFieldValues(name string, t reflect.Type, v reflect.Value) []Field {
var f []Field
vv := unpackAllValues(v)
for _, vvi := range vv {
if !vvi.IsValid() || !isScalar(vvi.Type()) {
continue
}
fi := fieldFromType(name, t)
fi.value = vvi.Interface()
f = append(f, fi)
}
if len(f) == 0 {
f = append(f, fieldFromType(name, t))
}
return f
}
func scalarMapFields(v reflect.Value) []Field {
var f []Field
for _, key := range v.MapKeys() {
name := key.Interface().(string)
value := v.MapIndex(key)
fk := scalarFieldValues(name, v.Type().Elem(), value)
freeFields(fk)
f = append(f, fk...)
}
return f
}
func structFields(v reflect.Value) []Field {
var f []Field
t := v.Type()
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
continue
}
vi := v.Field(i)
if acceptsScalar(fi.Type) {
f = append(f, scalarFieldValues(fi.Name, fi.Type, vi)...)
continue
}
vvi := vi
if unpackType(fi.Type, pointer).Kind() == reflect.Slice {
var ok bool
vvi, ok = allocate(fi.Type, 1)
if !ok {
continue
}
}
ffi := fieldValues(vvi)
if !fi.Anonymous {
prependFieldName(fi.Name, ffi)
}
if acceptsList(fi.Type) {
for i := range ffi {
ffi[i].list = true
}
}
f = append(f, ffi...)
}
return f
}
func fieldValues(v reflect.Value) []Field {
if !v.IsValid() {
return nil
}
var values []reflect.Value
allValues := unpackAllValues(v)
for _, vi := range allValues {
if acceptsFields(vi.Type()) {
values = append(values, vi)
}
}
var f []Field
for _, vi := range values {
ti := vi.Type()
if isScalarMap(ti) {
f = append(f, scalarMapFields(vi)...)
continue
}
f = append(f, structFields(vi)...)
}
return f
}
func takeFieldValues(f []Field) []any {
var v []any
for _, fi := range f {
v = append(v, fi.Value())
}
return v
}
func bindScalarField(receiver reflect.Value, values []Field) bool {
if !receiver.CanSet() {
return false
}
v := takeFieldValues(values)
rv, ok := allocate(receiver.Type(), len(v))
if !ok {
return false
}
if ok = bindScalar(rv, v); ok {
receiver.Set(rv)
}
return ok
}
func bindListField(receiver reflect.Value, values []Field) bool {
if receiver.Len() < len(values) && !receiver.CanSet() {
return false
}
if receiver.Len() < len(values) {
newList, ok := allocate(receiver.Type(), len(values))
if !ok {
return false
}
reflect.Copy(newList, receiver)
receiver.Set(newList)
}
for i := range values {
if !bindField(receiver.Index(i), values[i:i+1]) {
return false
}
}
return true
}
func bindMapField(receiver reflect.Value, values []Field) bool {
for _, v := range values {
if len(v.path) > 1 {
return false
}
}
if receiver.IsZero() && !receiver.CanSet() {
return false
}
var key string
fp, nfp := filterFields(hasPath, values)
if len(fp) > 0 {
key = fp[0].path[0]
} else {
key = nfp[0].name
}
v := takeFieldValues(values)
t := receiver.Type()
kt := t.Key()
vt := t.Elem()
kv, _ := bindScalarCreate(kt, []any{key})
vv, ok := bindScalarCreate(vt, v)
if !ok {
return false
}
if receiver.IsZero() {
rv, ok := allocate(receiver.Type(), 1)
if !ok {
return false
}
receiver.Set(rv)
}
receiver.SetMapIndex(kv, vv)
return true
}
func trimNameAndPath(name string, values []Field) []Field {
v := make([]Field, len(values))
copy(v, values)
for i := range v {
if len(v[i].path) > 0 {
v[i].path = v[i].path[1:]
}
if v[i].name == name {
v[i].name = ""
}
if strings.HasPrefix(v[i].name, fmt.Sprintf("%s-", name)) {
v[i].name = v[i].name[len(name)+1:]
}
}
return v
}
func bindStructField(receiver reflect.Value, values []Field) bool {
var (
name, pathName string
bound bool
)
fp, nfp := filterFields(hasPath, values)
if len(fp) > 0 {
pathName = fp[0].path[0]
}
if len(nfp) > 0 {
name = nfp[0].name
}
t := receiver.Type()
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
if sf.Anonymous {
continue
}
if sf.Name == pathName {
b := bindField(receiver.Field(i), trimNameAndPath(pathName, values))
bound = bound || b
continue
}
sfn := strcase.ToKebab(sf.Name)
if name == sfn || strings.HasPrefix(name, fmt.Sprintf("%s-", sfn)) {
b := bindField(receiver.Field(i), trimNameAndPath(sfn, values))
bound = bound || b
}
}
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
if !sf.Anonymous {
continue
}
b := bindField(receiver.Field(i), values)
bound = bound || b
}
return bound
}
func bindField(receiver reflect.Value, values []Field) bool {
if values[0].name == "" && len(values[0].path) == 0 {
ret := bindScalarField(receiver, values)
return ret
}
listReceiver := unpackValue(receiver, pointer|iface|anytype)
if listReceiver.Kind() == reflect.Slice {
return bindListField(listReceiver, values)
}
fieldReceiver := unpackValue(receiver, pointer|slice|iface)
if isScalarMap(fieldReceiver.Type()) {
return bindMapField(fieldReceiver, values)
}
if isStruct(fieldReceiver.Type()) {
return bindStructField(fieldReceiver, values)
}
if !receiver.CanSet() {
return false
}
t := receiver.Type()
ut := unpackType(t, pointer)
if ut.Kind() == reflect.Slice ||
isScalarMap(ut) ||
isStruct(ut) {
l := 1
if ut.Kind() == reflect.Slice {
l = len(values)
}
rv, ok := allocate(t, l)
if !ok {
return false
}
if !bindField(rv, values) {
return false
}
receiver.Set(rv)
return true
}
return false
}
func fieldsReflect[T any]() []Field {
t := reflect.TypeFor[T]()
if hasCircularType(t) {
return nil
}
return fields(t)
}
func fieldsOf(t reflect.Type) []Field {
if t == nil {
return nil
}
if hasCircularType(t) {
return nil
}
return fields(t)
}
func fieldValuesReflect(structure any) []Field {
v := reflect.ValueOf(structure)
if hasCircularReference(v) {
return nil
}
return fieldValues(v)
}
func groupFields(f []Field) [][]Field {
var pathsOrdered, namesOrdered []string
withPath, withoutPath := filterFields(hasPath, f)
paths := make(map[string][]Field)
for _, ff := range withPath {
ps := pathString(ff)
if _, set := paths[ps]; !set {
pathsOrdered = append(pathsOrdered, ps)
}
paths[ps] = append(paths[ps], ff)
}
names := make(map[string][]Field)
for _, ff := range withoutPath {
if _, set := names[ff.name]; !set {
namesOrdered = append(namesOrdered, ff.name)
}
names[ff.name] = append(names[ff.name], ff)
}
var groups [][]Field
for _, pname := range pathsOrdered {
group := paths[pname]
nfp := nameFromPath(group[0].path)
group = append(group, names[nfp]...)
delete(names, nfp)
groups = append(groups, group)
}
for _, name := range namesOrdered {
groups = append(groups, names[name])
}
return groups
}
func bindFields(receiver reflect.Value, values []Field) []Field {
unmatched, try := filterFields(fieldHasCircRef, values)
groups := groupFields(try)
for _, g := range groups {
if !bindField(receiver, g) {
unmatched = append(unmatched, g...)
}
}
return unmatched
}
func bindFieldsReflect(structure any, values []Field) []Field {
receiver := reflect.ValueOf(structure)
if hasCircularReference(receiver) {
return values
}
if !receiver.IsValid() || !acceptsFields(receiver.Type()) {
return values
}
return bindFields(receiver, values)
}
func bindFieldsCreate(t reflect.Type, values []Field) (reflect.Value, []Field) {
if hasCircularType(t) {
return reflect.Zero(t), values
}
if !acceptsFields(t) {
return reflect.Zero(t), values
}
receiver, ok := allocate(t, 1)
if !ok {
return reflect.Zero(t), values
}
unmatched := bindFields(receiver, values)
if len(unmatched) == len(values) {
receiver = reflect.Zero(t)
}
return receiver, unmatched
}
func bindFieldsCreateReflect[T any](values []Field) (T, []Field) {
t := reflect.TypeFor[T]()
receiver, unmatched := bindFieldsCreate(t, values)
if len(unmatched) == len(values) {
var t T
return t, unmatched
}
return receiver.Interface().(T), unmatched
}