260 lines
5.2 KiB
Go
260 lines
5.2 KiB
Go
package path
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"cloud.google.com/go/civil"
|
|
"github.com/gopatchy/jsrest"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
type WalkCallback func(string, []string, reflect.StructField)
|
|
|
|
var (
|
|
TimeTimeType = reflect.TypeOf(time.Time{})
|
|
CivilDateType = reflect.TypeOf(civil.Date{})
|
|
|
|
ErrNotAStruct = errors.New("not a struct")
|
|
ErrUnknownFieldName = errors.New("unknown field name")
|
|
)
|
|
|
|
func Get(obj any, path string) (any, error) {
|
|
v, err := GetValue(reflect.ValueOf(obj), path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v.Interface(), nil
|
|
}
|
|
|
|
func GetValue(v reflect.Value, path string) (reflect.Value, error) {
|
|
parts := strings.Split(path, ".")
|
|
return getRecursive(v, parts, []string{})
|
|
}
|
|
|
|
func getRecursive(v reflect.Value, parts []string, prev []string) (reflect.Value, error) {
|
|
if v.Kind() == reflect.Pointer {
|
|
if v.IsNil() {
|
|
v = reflect.Zero(v.Type().Elem())
|
|
} else {
|
|
v = reflect.Indirect(v)
|
|
}
|
|
}
|
|
|
|
if len(parts) == 0 {
|
|
return v, nil
|
|
}
|
|
|
|
if v.Kind() != reflect.Struct {
|
|
return reflect.Value{}, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", strings.Join(prev, "."), ErrNotAStruct)
|
|
}
|
|
|
|
part := parts[0]
|
|
|
|
sub, found := getField(v, part)
|
|
if !found {
|
|
return reflect.Value{}, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", errorPath(prev, part), ErrUnknownFieldName)
|
|
}
|
|
|
|
newPrev := []string{}
|
|
newPrev = append(newPrev, prev...)
|
|
newPrev = append(newPrev, part)
|
|
|
|
return getRecursive(sub, parts[1:], newPrev)
|
|
}
|
|
|
|
func getField(v reflect.Value, name string) (reflect.Value, bool) {
|
|
field, found := getStructField(v.Type(), name)
|
|
if !found {
|
|
return reflect.Value{}, false
|
|
}
|
|
|
|
return v.FieldByName(field.Name), true
|
|
}
|
|
|
|
func Set(obj any, path string, val string) error {
|
|
return SetValue(reflect.ValueOf(obj), path, val)
|
|
}
|
|
|
|
func SetValue(v reflect.Value, path string, val string) error {
|
|
parts := strings.Split(path, ".")
|
|
return setRecursive(v, parts, []string{}, val)
|
|
}
|
|
|
|
func setRecursive(v reflect.Value, parts []string, prev []string, val string) error {
|
|
if v.Kind() == reflect.Pointer {
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
|
|
v = reflect.Indirect(v)
|
|
}
|
|
|
|
if len(parts) == 0 {
|
|
n, err := parse(val, v.Interface())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, ok := n.(*timeVal); ok {
|
|
n = n.(*timeVal).time
|
|
}
|
|
|
|
v.Set(reflect.ValueOf(n))
|
|
|
|
return nil
|
|
}
|
|
|
|
if v.Kind() != reflect.Struct {
|
|
return jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", strings.Join(prev, "."), ErrNotAStruct)
|
|
}
|
|
|
|
part := parts[0]
|
|
|
|
sub, found := getField(v, part)
|
|
if !found {
|
|
return jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", errorPath(prev, part), ErrUnknownFieldName)
|
|
}
|
|
|
|
newPrev := []string{}
|
|
newPrev = append(newPrev, prev...)
|
|
newPrev = append(newPrev, part)
|
|
|
|
return setRecursive(sub, parts[1:], newPrev, val)
|
|
}
|
|
|
|
func List(obj any) []string {
|
|
return ListType(reflect.TypeOf(obj))
|
|
}
|
|
|
|
func ListType(t reflect.Type) []string {
|
|
list := []string{}
|
|
|
|
WalkType(t, func(path string, _ []string, field reflect.StructField) {
|
|
t := MaybeIndirectType(field.Type)
|
|
if t.Kind() == reflect.Struct && t != TimeTimeType && t != CivilDateType {
|
|
return
|
|
}
|
|
|
|
list = append(list, path)
|
|
})
|
|
|
|
sort.Strings(list)
|
|
|
|
return list
|
|
}
|
|
|
|
func GetFieldType(t reflect.Type, path string) reflect.Type {
|
|
parts := strings.Split(path, ".")
|
|
|
|
for _, part := range parts {
|
|
field, found := getStructField(MaybeIndirectType(t), part)
|
|
if !found {
|
|
return nil
|
|
}
|
|
|
|
t = field.Type
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
func FindTagValueType(t reflect.Type, key, value string) (string, bool) {
|
|
ret := ""
|
|
|
|
WalkType(t, func(path string, _ []string, field reflect.StructField) {
|
|
tag, found := field.Tag.Lookup(key)
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
parts := strings.Split(tag, ",")
|
|
|
|
if slices.Contains(parts, value) {
|
|
ret = path
|
|
}
|
|
})
|
|
|
|
return ret, ret != ""
|
|
}
|
|
|
|
func Walk(obj any, cb WalkCallback) {
|
|
WalkType(reflect.TypeOf(obj), cb)
|
|
}
|
|
|
|
func WalkType(t reflect.Type, cb WalkCallback) {
|
|
walkRecursive(MaybeIndirectType(t), cb, []string{})
|
|
}
|
|
|
|
func walkRecursive(t reflect.Type, cb WalkCallback, prev []string) {
|
|
for i := 0; i < t.NumField(); i++ {
|
|
sub := t.Field(i)
|
|
|
|
newPrev := []string{}
|
|
newPrev = append(newPrev, prev...)
|
|
|
|
if !sub.Anonymous {
|
|
newPrev = append(newPrev, FieldName(sub))
|
|
}
|
|
|
|
t := MaybeIndirectType(sub.Type)
|
|
|
|
if len(newPrev) > 0 {
|
|
cb(strings.Join(newPrev, "."), newPrev, sub)
|
|
}
|
|
|
|
if t.Kind() == reflect.Struct && t != TimeTimeType && t != CivilDateType {
|
|
walkRecursive(t, cb, newPrev)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getStructField(t reflect.Type, name string) (reflect.StructField, bool) {
|
|
name = strings.ToLower(name)
|
|
|
|
return t.FieldByNameFunc(func(iterName string) bool {
|
|
iterField, iterOK := t.FieldByName(iterName)
|
|
if !iterOK {
|
|
panic(iterName)
|
|
}
|
|
|
|
return strings.ToLower(FieldName(iterField)) == name
|
|
})
|
|
}
|
|
|
|
func errorPath(prev []string, part string) string {
|
|
if len(prev) == 0 {
|
|
return part
|
|
}
|
|
|
|
return fmt.Sprintf("%s.%s", strings.Join(prev, "."), part)
|
|
}
|
|
|
|
func FieldName(field reflect.StructField) string {
|
|
tag := field.Tag.Get("json")
|
|
if tag != "" {
|
|
if tag == "-" {
|
|
return ""
|
|
}
|
|
|
|
parts := strings.SplitN(tag, ",", 2)
|
|
|
|
return parts[0]
|
|
}
|
|
|
|
return field.Name
|
|
}
|
|
|
|
func MaybeIndirectType(t reflect.Type) reflect.Type {
|
|
if t.Kind() == reflect.Pointer {
|
|
return t.Elem()
|
|
}
|
|
|
|
return t
|
|
}
|