Module split
This commit is contained in:
259
path.go
Normal file
259
path.go
Normal file
@@ -0,0 +1,259 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user