2025-08-18 14:24:31 +02:00
|
|
|
package wand
|
|
|
|
|
|
|
|
|
|
import (
|
2025-09-01 02:07:48 +02:00
|
|
|
"code.squareroundforest.org/arpio/bind"
|
|
|
|
|
"io"
|
2025-09-01 04:10:35 +02:00
|
|
|
"reflect"
|
2025-08-24 01:45:25 +02:00
|
|
|
"strings"
|
2025-09-01 04:10:35 +02:00
|
|
|
"time"
|
2025-08-18 14:24:31 +02:00
|
|
|
)
|
|
|
|
|
|
2025-09-06 21:38:50 +02:00
|
|
|
var (
|
|
|
|
|
structFieldsCache = make(map[reflect.Type][]bind.Field)
|
|
|
|
|
bindableTypes = make(map[reflect.Type]bool)
|
|
|
|
|
)
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func filter[T any](list []T, predicate func(T) bool) []T {
|
|
|
|
|
var filtered []T
|
|
|
|
|
for _, item := range list {
|
|
|
|
|
if predicate(item) {
|
|
|
|
|
filtered = append(filtered, item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filtered
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func not[T any](p func(T) bool) func(T) bool {
|
|
|
|
|
return func(v T) bool {
|
|
|
|
|
return !p(v)
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func and[T any](p ...func(T) bool) func(T) bool {
|
|
|
|
|
return func(v T) bool {
|
|
|
|
|
for _, pi := range p {
|
|
|
|
|
if !pi(v) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return true
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func or[T any](p ...func(T) bool) func(T) bool {
|
|
|
|
|
return func(v T) bool {
|
|
|
|
|
for _, pi := range p {
|
|
|
|
|
if pi(v) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return false
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func unpackType(t reflect.Type) reflect.Type {
|
2025-09-04 01:09:24 +02:00
|
|
|
if t == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t.Kind() {
|
|
|
|
|
case reflect.Pointer, reflect.Slice:
|
|
|
|
|
return unpackType(t.Elem())
|
|
|
|
|
default:
|
|
|
|
|
return t
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func unpackValueChecked(visited map[uintptr]bool, v reflect.Value) reflect.Value {
|
|
|
|
|
if !v.IsValid() {
|
|
|
|
|
return v
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
switch v.Kind() {
|
|
|
|
|
case reflect.Pointer:
|
|
|
|
|
p := v.Pointer()
|
|
|
|
|
if visited[p] {
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if visited == nil {
|
|
|
|
|
visited = make(map[uintptr]bool)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visited[p] = true
|
|
|
|
|
return unpackValueChecked(visited, v.Elem())
|
|
|
|
|
case reflect.Interface:
|
|
|
|
|
if v.IsNil() {
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unpackValueChecked(visited, v.Elem())
|
2025-08-24 01:45:25 +02:00
|
|
|
default:
|
2025-09-01 02:07:48 +02:00
|
|
|
return v
|
2025-08-24 01:45:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func unpackValue(v reflect.Value) reflect.Value {
|
|
|
|
|
return unpackValueChecked(nil, v)
|
2025-08-24 01:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func isFunc(v any) bool {
|
|
|
|
|
r := reflect.ValueOf(v)
|
|
|
|
|
r = unpackValue(r)
|
|
|
|
|
return r.Kind() == reflect.Func
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func isTime(t reflect.Type) bool {
|
|
|
|
|
if t == nil {
|
|
|
|
|
return false
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-04 01:09:24 +02:00
|
|
|
t = unpackType(t)
|
2025-09-01 02:07:48 +02:00
|
|
|
return t.ConvertibleTo(reflect.TypeFor[time.Time]())
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func isStruct(t reflect.Type) bool {
|
|
|
|
|
if t == nil {
|
|
|
|
|
return false
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
t = unpackType(t)
|
|
|
|
|
return !isTime(t) && t.Kind() == reflect.Struct
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func isReader(t reflect.Type) bool {
|
|
|
|
|
if t == nil || t.Kind() != reflect.Interface {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2025-08-24 01:45:25 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Reader]())
|
|
|
|
|
}
|
2025-08-24 01:45:25 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func isWriter(t reflect.Type) bool {
|
|
|
|
|
if t == nil || t.Kind() != reflect.Interface {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2025-08-24 01:45:25 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Writer]())
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-04 01:09:24 +02:00
|
|
|
func compatibleTypes(t ...bind.FieldType) bool {
|
2025-09-01 02:07:48 +02:00
|
|
|
if len(t) == 0 {
|
|
|
|
|
return false
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
if len(t) == 1 {
|
|
|
|
|
return true
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
switch t[0] {
|
2025-09-06 02:46:28 +02:00
|
|
|
case bind.String, bind.Any:
|
2025-09-01 02:07:48 +02:00
|
|
|
return compatibleTypes(t[1:]...)
|
2025-09-06 02:46:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t[1] {
|
|
|
|
|
case bind.String, bind.Any:
|
|
|
|
|
return compatibleTypes(t[1:]...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t[0] {
|
|
|
|
|
case bind.Bool:
|
|
|
|
|
switch t[1] {
|
|
|
|
|
case bind.Bool:
|
|
|
|
|
return compatibleTypes(t[1:]...)
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t[1] {
|
|
|
|
|
case bind.Bool:
|
|
|
|
|
return false
|
2025-09-01 02:07:48 +02:00
|
|
|
default:
|
2025-09-06 02:46:28 +02:00
|
|
|
return compatibleTypes(t[1:]...)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func parameters(f any) []reflect.Type {
|
|
|
|
|
r := reflect.ValueOf(f)
|
|
|
|
|
r = unpackValue(r)
|
|
|
|
|
if r.Kind() != reflect.Func {
|
|
|
|
|
return nil
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
var p []reflect.Type
|
|
|
|
|
t := r.Type()
|
|
|
|
|
for i := 0; i < t.NumIn(); i++ {
|
|
|
|
|
p = append(p, t.In(i))
|
2025-08-24 01:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return p
|
2025-08-24 01:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func structParameters(f any) []reflect.Type {
|
|
|
|
|
return filter(parameters(f), isStruct)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func structFields(s reflect.Type) []bind.Field {
|
2025-09-06 21:38:50 +02:00
|
|
|
if f, ok := structFieldsCache[s]; ok {
|
|
|
|
|
return f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f := bind.FieldsOf(s)
|
|
|
|
|
structFieldsCache[s] = f
|
|
|
|
|
return f
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func fields(f any) []bind.Field {
|
|
|
|
|
var fields []bind.Field
|
2025-09-01 03:25:18 +02:00
|
|
|
s := structParameters(f)
|
2025-09-01 02:07:48 +02:00
|
|
|
for _, si := range s {
|
|
|
|
|
fields = append(fields, structFields(si)...)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return fields
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func mapFields(f any) map[string][]bind.Field {
|
2025-09-01 03:25:18 +02:00
|
|
|
fields := fields(f)
|
2025-09-01 02:07:48 +02:00
|
|
|
m := make(map[string][]bind.Field)
|
|
|
|
|
for _, fi := range fields {
|
|
|
|
|
m[fi.Name()] = append(m[fi.Name()], fi)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return m
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func boolFields(f []bind.Field) []bind.Field {
|
2025-09-05 03:19:00 +02:00
|
|
|
return filter(f, func(f bind.Field) bool { return f.Type() == bind.Bool })
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func positional(f any) ([]reflect.Type, bool) {
|
|
|
|
|
p := filter(
|
|
|
|
|
parameters(f),
|
|
|
|
|
not(or(isReader, isWriter, isStruct)),
|
2025-08-18 14:24:31 +02:00
|
|
|
)
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
r := reflect.ValueOf(f)
|
|
|
|
|
r = unpackValue(r)
|
|
|
|
|
t := r.Type()
|
|
|
|
|
return p, t.IsVariadic()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func positionalIndices(f any) []int {
|
|
|
|
|
r := reflect.ValueOf(f)
|
|
|
|
|
r = unpackValue(r)
|
|
|
|
|
if r.Kind() != reflect.Func {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var indices []int
|
|
|
|
|
t := r.Type()
|
|
|
|
|
for i := 0; i < t.NumIn(); i++ {
|
|
|
|
|
p := t.In(i)
|
2025-12-10 20:31:10 +01:00
|
|
|
if isStruct(p) || isReader(p) || isWriter(p) {
|
2025-09-01 02:07:48 +02:00
|
|
|
continue
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
|
|
|
|
|
indices = append(indices, i)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
return indices
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func ioParameters(f any) ([]reflect.Type, []reflect.Type) {
|
|
|
|
|
p := parameters(f)
|
|
|
|
|
return filter(p, isReader), filter(p, isWriter)
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 02:07:48 +02:00
|
|
|
func bindable(t reflect.Type) bool {
|
2025-09-06 21:38:50 +02:00
|
|
|
if bindable, ok := bindableTypes[t]; ok {
|
|
|
|
|
return bindable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindable := bind.BindableType(t)
|
|
|
|
|
bindableTypes[t] = bindable
|
|
|
|
|
return bindable
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 20:31:10 +01:00
|
|
|
func scalarTypeStringOf(t reflect.Type) string {
|
|
|
|
|
if t == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t = unpackType(t)
|
|
|
|
|
return scalarTypeString(bind.FieldType(t.Kind()))
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 01:09:24 +02:00
|
|
|
func scalarTypeString(t bind.FieldType) string {
|
2025-09-01 02:07:48 +02:00
|
|
|
switch t {
|
|
|
|
|
case bind.Duration:
|
2025-09-05 03:19:00 +02:00
|
|
|
return "duration"
|
2025-09-01 02:07:48 +02:00
|
|
|
case bind.Time:
|
2025-09-05 03:19:00 +02:00
|
|
|
return "time"
|
2025-12-10 20:31:10 +01:00
|
|
|
case reflect.Interface:
|
|
|
|
|
return "any"
|
2025-09-01 02:07:48 +02:00
|
|
|
default:
|
2025-09-05 03:19:00 +02:00
|
|
|
return strings.ToLower(reflect.Kind(t).String())
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
|
2025-09-05 03:19:00 +02:00
|
|
|
func canScan(t bind.FieldType, v any) bool {
|
|
|
|
|
_, ok := bind.CreateAndBindScalarFieldType(t, v)
|
|
|
|
|
return ok
|
|
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
|
2025-09-05 03:19:00 +02:00
|
|
|
func canScanType(t reflect.Type, v any) bool {
|
|
|
|
|
_, ok := bind.CreateAndBindScalarFor(t, v)
|
|
|
|
|
return ok
|
2025-09-01 02:07:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func allocate(t reflect.Type) reflect.Value {
|
2025-08-18 14:24:31 +02:00
|
|
|
switch t.Kind() {
|
2025-08-26 14:12:18 +02:00
|
|
|
case reflect.Pointer:
|
2025-09-01 02:07:48 +02:00
|
|
|
et := t.Elem()
|
|
|
|
|
v := allocate(et)
|
|
|
|
|
p := reflect.New(et)
|
|
|
|
|
p.Elem().Set(v)
|
|
|
|
|
return p
|
|
|
|
|
case reflect.Slice:
|
|
|
|
|
v := allocate(t.Elem())
|
|
|
|
|
s := reflect.MakeSlice(t, 1, 1)
|
|
|
|
|
s.Index(0).Set(v)
|
|
|
|
|
return s
|
2025-08-18 14:24:31 +02:00
|
|
|
default:
|
2025-09-01 03:25:18 +02:00
|
|
|
p := reflect.New(t)
|
|
|
|
|
return p.Elem()
|
2025-09-01 02:07:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func bindScalar(receiver reflect.Value, value any) {
|
|
|
|
|
bind.BindScalar(receiver.Interface(), value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func bindFields(receiver reflect.Value, values map[string][]any) []string {
|
|
|
|
|
var f []bind.Field
|
2025-09-01 03:25:18 +02:00
|
|
|
for name, vals := range values {
|
|
|
|
|
for _, v := range vals {
|
|
|
|
|
f = append(f, bind.NamedValue(name, v))
|
|
|
|
|
}
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|
2025-09-01 02:07:48 +02:00
|
|
|
|
2025-09-04 01:09:24 +02:00
|
|
|
unmapped := bind.Bind(receiver.Interface(), f...)
|
2025-09-01 02:07:48 +02:00
|
|
|
|
|
|
|
|
var names []string
|
|
|
|
|
for _, um := range unmapped {
|
|
|
|
|
names = append(names, um.Name())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return names
|
2025-08-18 14:24:31 +02:00
|
|
|
}
|