527 lines
9.3 KiB
Go
527 lines
9.3 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 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 bindFieldsCreateReflect[T any](values []Field) (T, []Field) {
|
|
t := reflect.TypeFor[T]()
|
|
if hasCircularType(t) {
|
|
var r T
|
|
return r, values
|
|
}
|
|
|
|
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
|
|
}
|