summaryrefslogtreecommitdiffstats
path: root/pkg/structify/structify.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:36:04 +0000
commitb09c6d56832eb1718c07d74abf3bc6ae3fe4e030 (patch)
treed2caec2610d4ea887803ec9e9c3cd77136c448ba /pkg/structify/structify.go
parentInitial commit. (diff)
downloadicingadb-upstream.tar.xz
icingadb-upstream.zip
Adding upstream version 1.1.0.upstream/1.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pkg/structify/structify.go')
-rw-r--r--pkg/structify/structify.go179
1 files changed, 179 insertions, 0 deletions
diff --git a/pkg/structify/structify.go b/pkg/structify/structify.go
new file mode 100644
index 0000000..2b2b5bb
--- /dev/null
+++ b/pkg/structify/structify.go
@@ -0,0 +1,179 @@
+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)
+}