mirror of
https://github.com/Icinga/icingadb.git
synced 2026-02-19 02:28:28 -05:00
179 lines
4.5 KiB
Go
179 lines
4.5 KiB
Go
package structify
|
|
|
|
import (
|
|
"encoding"
|
|
"fmt"
|
|
"github.com/icinga/icingadb/pkg/contracts"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/exp/constraints"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
// structBranch represents either a leaf or a subTree.
|
|
type structBranch struct {
|
|
// field specifies the struct field index.
|
|
field int
|
|
// leaf specifies the map key to parse the struct field from.
|
|
leaf string
|
|
// subTree specifies the struct field's inner tree.
|
|
subTree []structBranch
|
|
}
|
|
|
|
type MapStructifier = func(map[string]interface{}) (interface{}, error)
|
|
|
|
// MakeMapStructifier builds a function which parses a map's string values into a new struct of type t
|
|
// and returns a pointer to it. tag specifies which tag connects struct fields to map keys.
|
|
// MakeMapStructifier panics if it detects an unsupported type (suitable for usage in init() or global vars).
|
|
func MakeMapStructifier(t reflect.Type, tag string) MapStructifier {
|
|
tree := buildStructTree(t, tag)
|
|
|
|
return func(kv map[string]interface{}) (interface{}, error) {
|
|
vPtr := reflect.New(t)
|
|
ptr := vPtr.Interface()
|
|
|
|
if initer, ok := ptr.(contracts.Initer); ok {
|
|
initer.Init()
|
|
}
|
|
|
|
vPtrElem := vPtr.Elem()
|
|
err := errors.Wrapf(structifyMapByTree(kv, tree, vPtrElem, vPtrElem, new([]int)), "can't structify map %#v by tree %#v", kv, tree)
|
|
|
|
return ptr, err
|
|
}
|
|
}
|
|
|
|
// buildStructTree assembles a tree which represents the struct t based on tag.
|
|
func buildStructTree(t reflect.Type, tag string) []structBranch {
|
|
var tree []structBranch
|
|
numFields := t.NumField()
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
if field := t.Field(i); field.PkgPath == "" {
|
|
switch tagValue := field.Tag.Get(tag); tagValue {
|
|
case "", "-":
|
|
case ",inline":
|
|
if subTree := buildStructTree(field.Type, tag); subTree != nil {
|
|
tree = append(tree, structBranch{i, "", subTree})
|
|
}
|
|
default:
|
|
// If parseString doesn't support *T, it'll panic.
|
|
_ = parseString("", reflect.New(field.Type).Interface())
|
|
|
|
tree = append(tree, structBranch{i, tagValue, nil})
|
|
}
|
|
}
|
|
}
|
|
|
|
return tree
|
|
}
|
|
|
|
// structifyMapByTree parses src's string values into the struct dest according to tree's specification.
|
|
func structifyMapByTree(src map[string]interface{}, tree []structBranch, dest, root reflect.Value, stack *[]int) error {
|
|
*stack = append(*stack, 0)
|
|
defer func() {
|
|
*stack = (*stack)[:len(*stack)-1]
|
|
}()
|
|
|
|
for _, branch := range tree {
|
|
(*stack)[len(*stack)-1] = branch.field
|
|
|
|
if branch.subTree == nil {
|
|
if v, ok := src[branch.leaf]; ok {
|
|
if vs, ok := v.(string); ok {
|
|
if err := parseString(vs, dest.Field(branch.field).Addr().Interface()); err != nil {
|
|
rt := root.Type()
|
|
typ := rt
|
|
var path []string
|
|
|
|
for _, i := range *stack {
|
|
f := typ.Field(i)
|
|
path = append(path, f.Name)
|
|
typ = f.Type
|
|
}
|
|
|
|
return errors.Wrapf(err, "can't parse %s into the %s %s#%s: %s",
|
|
branch.leaf, typ.Name(), rt.Name(), strings.Join(path, "."), vs)
|
|
}
|
|
}
|
|
}
|
|
} else if err := structifyMapByTree(src, branch.subTree, dest.Field(branch.field), root, stack); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseString parses src into *dest.
|
|
func parseString(src string, dest interface{}) error {
|
|
switch ptr := dest.(type) {
|
|
case encoding.TextUnmarshaler:
|
|
return ptr.UnmarshalText([]byte(src))
|
|
case *string:
|
|
*ptr = src
|
|
return nil
|
|
case **string:
|
|
*ptr = &src
|
|
return nil
|
|
case *uint8:
|
|
return parseUint(src, ptr)
|
|
case *uint16:
|
|
return parseUint(src, ptr)
|
|
case *uint32:
|
|
return parseUint(src, ptr)
|
|
case *uint64:
|
|
return parseUint(src, ptr)
|
|
case *int8:
|
|
return parseInt(src, ptr)
|
|
case *int16:
|
|
return parseInt(src, ptr)
|
|
case *int32:
|
|
return parseInt(src, ptr)
|
|
case *int64:
|
|
return parseInt(src, ptr)
|
|
case *float32:
|
|
return parseFloat(src, ptr)
|
|
case *float64:
|
|
return parseFloat(src, ptr)
|
|
default:
|
|
panic(fmt.Sprintf("unsupported type: %T", dest))
|
|
}
|
|
}
|
|
|
|
// parseUint parses src into *dest.
|
|
func parseUint[T constraints.Unsigned](src string, dest *T) error {
|
|
i, err := strconv.ParseUint(src, 10, bitSizeOf[T]())
|
|
if err == nil {
|
|
*dest = T(i)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// parseInt parses src into *dest.
|
|
func parseInt[T constraints.Signed](src string, dest *T) error {
|
|
i, err := strconv.ParseInt(src, 10, bitSizeOf[T]())
|
|
if err == nil {
|
|
*dest = T(i)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// parseFloat parses src into *dest.
|
|
func parseFloat[T constraints.Float](src string, dest *T) error {
|
|
f, err := strconv.ParseFloat(src, bitSizeOf[T]())
|
|
if err == nil {
|
|
*dest = T(f)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func bitSizeOf[T any]() int {
|
|
var x T
|
|
return int(unsafe.Sizeof(x) * 8)
|
|
}
|