1
0
bind/field.go

527 lines
9.3 KiB
Go
Raw Normal View History

2025-08-28 05:04:06 +02:00
package bind
import (
"fmt"
"github.com/iancoleman/strcase"
"reflect"
2025-08-31 00:40:47 +02:00
"strings"
2025-08-31 01:23:21 +02:00
"unicode"
2025-08-28 05:04:06 +02:00
)
2025-08-31 00:40:47 +02:00
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, "-")
}
2025-08-28 05:04:06 +02:00
func exported(name string) bool {
return unicode.IsUpper([]rune(name)[0])
}
2025-08-31 00:40:47 +02:00
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
}
2025-08-31 17:11:09 +02:00
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...)
}
2025-08-31 17:11:09 +02:00
}
2025-08-28 05:04:06 +02:00
func fields(t reflect.Type) []Field {
t = unpackType(t, pointer|slice)
2025-09-01 01:59:03 +02:00
if !isStruct(t) {
2025-08-28 05:04:06 +02:00
return nil
}
var f []Field
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
2025-08-28 05:04:06 +02:00
continue
}
if acceptsScalar(fi.Type) {
fi := fieldFromType(fi.Name, fi.Type)
2025-08-28 05:04:06 +02:00
f = append(f, fi)
continue
}
ffi := fields(fi.Type)
if !fi.Anonymous {
prependFieldName(fi.Name, ffi)
}
if acceptsList(fi.Type) {
2025-08-28 05:04:06 +02:00
for i := range ffi {
ffi[i].list = true
2025-08-28 05:04:06 +02:00
}
}
f = append(f, ffi...)
}
return f
}
func freeFields(f []Field) {
2025-08-31 00:40:47 +02:00
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()) {
2025-08-31 00:40:47 +02:00
continue
2025-08-28 05:04:06 +02:00
}
fi := fieldFromType(name, t)
fi.value = vvi.Interface()
f = append(f, fi)
2025-08-31 00:40:47 +02:00
}
2025-08-28 05:04:06 +02:00
if len(f) == 0 {
f = append(f, fieldFromType(name, t))
2025-08-28 05:04:06 +02:00
}
return f
2025-08-28 05:04:06 +02:00
}
2025-08-31 00:40:47 +02:00
func scalarMapFields(v reflect.Value) []Field {
2025-08-28 05:04:06 +02:00
var f []Field
2025-08-31 00:40:47 +02:00
for _, key := range v.MapKeys() {
name := key.Interface().(string)
value := v.MapIndex(key)
fk := scalarFieldValues(name, v.Type().Elem(), value)
2025-08-31 00:40:47 +02:00
freeFields(fk)
f = append(f, fk...)
2025-08-28 05:04:06 +02:00
}
return f
}
func structFields(v reflect.Value) []Field {
2025-08-28 05:04:06 +02:00
var f []Field
2025-08-31 00:40:47 +02:00
t := v.Type()
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
continue
}
2025-08-31 00:40:47 +02:00
vi := v.Field(i)
if acceptsScalar(fi.Type) {
f = append(f, scalarFieldValues(fi.Name, fi.Type, vi)...)
continue
2025-08-28 05:04:06 +02:00
}
2025-09-03 22:46:09 +02:00
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
}
}
2025-08-28 05:04:06 +02:00
f = append(f, ffi...)
2025-08-28 05:04:06 +02:00
}
return f
}
func fieldValues(v reflect.Value) []Field {
if !v.IsValid() {
2025-08-28 05:04:06 +02:00
return nil
}
var values []reflect.Value
allValues := unpackAllValues(v)
for _, vi := range allValues {
if acceptsFields(vi.Type()) {
values = append(values, vi)
2025-08-28 05:04:06 +02:00
}
}
2025-08-28 05:04:06 +02:00
var f []Field
for _, vi := range values {
ti := vi.Type()
if isScalarMap(ti) {
f = append(f, scalarMapFields(vi)...)
2025-09-01 03:11:04 +02:00
continue
}
f = append(f, structFields(vi)...)
2025-08-28 05:04:06 +02:00
}
return f
}
2025-08-31 00:40:47 +02:00
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
2025-08-31 00:40:47 +02:00
}
v := takeFieldValues(values)
2025-08-31 00:40:47 +02:00
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()
2025-09-03 22:46:09 +02:00
kv, _ := bindScalarCreate(kt, []any{key})
2025-08-31 00:40:47 +02:00
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)) {
2025-08-31 01:23:21 +02:00
v[i].name = v[i].name[len(name)+1:]
2025-08-31 00:40:47 +02:00
}
}
return v
}
func bindStructField(receiver reflect.Value, values []Field) bool {
2025-08-31 17:11:09 +02:00
var (
name, pathName string
bound bool
)
2025-08-31 00:40:47 +02:00
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 {
2025-08-31 17:11:09 +02:00
b := bindField(receiver.Field(i), trimNameAndPath(pathName, values))
bound = bound || b
continue
2025-08-31 00:40:47 +02:00
}
sfn := strcase.ToKebab(sf.Name)
2025-08-31 17:11:09 +02:00
if name == sfn || strings.HasPrefix(name, fmt.Sprintf("%s-", sfn)) {
b := bindField(receiver.Field(i), trimNameAndPath(sfn, values))
bound = bound || b
2025-08-31 00:40:47 +02:00
}
}
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
if !sf.Anonymous {
continue
}
2025-08-31 17:11:09 +02:00
b := bindField(receiver.Field(i), values)
bound = bound || b
2025-08-31 00:40:47 +02:00
}
2025-08-31 17:11:09 +02:00
return bound
2025-08-31 00:40:47 +02:00
}
func bindField(receiver reflect.Value, values []Field) bool {
if values[0].name == "" && len(values[0].path) == 0 {
ret := bindScalarField(receiver, values)
return ret
2025-08-31 00:40:47 +02:00
}
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)
}
2025-09-01 01:59:03 +02:00
if isStruct(fieldReceiver.Type()) {
2025-08-31 00:40:47 +02:00
return bindStructField(fieldReceiver, values)
}
if !receiver.CanSet() {
return false
}
t := receiver.Type()
ut := unpackType(t, pointer)
if ut.Kind() == reflect.Slice ||
isScalarMap(ut) ||
2025-09-01 01:59:03 +02:00
isStruct(ut) {
2025-08-31 00:40:47 +02:00
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
}
2025-08-28 05:04:06 +02:00
func fieldsReflect[T any]() []Field {
t := reflect.TypeFor[T]()
2025-08-31 00:40:47 +02:00
if hasCircularType(t) {
2025-08-28 05:04:06 +02:00
return nil
}
return fields(t)
}
func fieldValuesReflect(structure any) []Field {
v := reflect.ValueOf(structure)
2025-08-31 00:40:47 +02:00
if hasCircularReference(v) {
2025-08-28 05:04:06 +02:00
return nil
}
return fieldValues(v)
}
2025-08-31 00:40:47 +02:00
func groupFields(f []Field) [][]Field {
2025-08-31 17:11:09 +02:00
var pathsOrdered, namesOrdered []string
2025-08-31 00:40:47 +02:00
withPath, withoutPath := filterFields(hasPath, f)
paths := make(map[string][]Field)
for _, ff := range withPath {
ps := pathString(ff)
2025-08-31 17:11:09 +02:00
if _, set := paths[ps]; !set {
pathsOrdered = append(pathsOrdered, ps)
}
2025-08-31 00:40:47 +02:00
paths[ps] = append(paths[ps], ff)
}
names := make(map[string][]Field)
for _, ff := range withoutPath {
2025-08-31 17:11:09 +02:00
if _, set := names[ff.name]; !set {
namesOrdered = append(namesOrdered, ff.name)
}
2025-08-31 00:40:47 +02:00
names[ff.name] = append(names[ff.name], ff)
}
var groups [][]Field
2025-08-31 17:11:09 +02:00
for _, pname := range pathsOrdered {
group := paths[pname]
2025-08-31 00:40:47 +02:00
nfp := nameFromPath(group[0].path)
group = append(group, names[nfp]...)
delete(names, nfp)
groups = append(groups, group)
}
2025-08-31 17:11:09 +02:00
for _, name := range namesOrdered {
groups = append(groups, names[name])
2025-08-31 00:40:47 +02:00
}
return groups
}
2025-08-31 01:49:53 +02:00
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
}
2025-08-31 00:40:47 +02:00
func bindFieldsReflect(structure any, values []Field) []Field {
receiver := reflect.ValueOf(structure)
if hasCircularReference(receiver) {
return values
}
2025-08-31 17:11:09 +02:00
if !receiver.IsValid() || !acceptsFields(receiver.Type()) {
2025-08-31 00:40:47 +02:00
return values
}
2025-08-31 01:49:53 +02:00
return bindFields(receiver, values)
}
func bindFieldsCreateReflect[T any](values []Field) (T, []Field) {
t := reflect.TypeFor[T]()
if hasCircularType(t) {
var r T
return r, values
2025-08-31 00:40:47 +02:00
}
2025-08-31 01:49:53 +02:00
if !acceptsFields(t) {
var r T
return r, values
}
receiver, ok := allocate(t, 1)
if !ok {
var r T
return r, values
}
unmatched := bindFields(receiver, values)
if len(unmatched) == len(values) {
receiver = reflect.Zero(t)
}
return receiver.Interface().(T), unmatched
2025-08-31 00:40:47 +02:00
}