-
Notifications
You must be signed in to change notification settings - Fork 0
/
reflect.go
135 lines (116 loc) · 2.73 KB
/
reflect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package bcl
import (
"fmt"
"reflect"
"strings"
)
func copyBlocks(dest any, blocks []Block) error {
destPtr := reflect.ValueOf(dest)
if destPtr.Kind() != reflect.Pointer {
return TypeErr("expected pointer to a slice of structs")
}
destSlice := destPtr.Elem()
if destSlice.Kind() != reflect.Slice {
return TypeErr("expected pointer to a slice of structs")
}
newSlice := reflect.MakeSlice(destSlice.Type(), len(blocks), len(blocks))
for i, block := range blocks {
err := copyBlock(newSlice.Index(i), block)
if err != nil {
return err
}
}
destSlice.Set(newSlice)
return nil
}
func copyBlock(v reflect.Value, block Block) error {
t := v.Type()
if st, bt := t.Name(), block.Type; st != "" && !unsnakeEq(st, bt) {
return StructErr(
fmt.Sprintf("mismatch: struct type %s, block type %s", st, bt),
)
}
tagged := map[string]int{}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if tagv := f.Tag.Get("bcl"); tagv != "" {
tagged[tagv] = i
}
}
setField := func(name string, x any) error {
var f reflect.StructField
var ok bool
if len(tagged) > 0 {
var i int
i, ok = tagged[name]
if ok {
f = t.Field(i)
}
}
if !ok {
name, _, _ := strings.Cut(name, ".")
f, ok = t.FieldByNameFunc(unsnakeMatcher(name))
}
if !ok {
return StructErr(
fmt.Sprintf("field mapping for %q not found in struct", name),
)
}
if !f.IsExported() {
return StructErr(
fmt.Sprintf("found field %q but is unexported", f.Name),
)
}
namei := f.Index[0]
vx := reflect.ValueOf(x)
if vx.Type().AssignableTo(blockType) {
return copyBlock(v.Field(namei), x.(Block))
}
if st, bt := f.Type, vx.Type(); !bt.AssignableTo(st) {
return StructErr(
fmt.Sprintf(
"type mismatch for the mapped field: "+
"struct.%s has %s, block.%s has %s",
f.Name, st, name, bt,
),
)
}
v.Field(namei).Set(vx)
return nil
}
err := setField("Name", block.Name)
if err != nil {
// todo: do better than string search for filtering the error
if se, ok := err.(StructErr); block.Name == "" && ok &&
strings.Contains(string(se), " not found ") {
goto fields
}
return err
}
fields:
for fkey, fval := range block.Fields {
err = setField(fkey, fval)
if err != nil {
return err
}
}
return nil
}
func unsnakeMatcher(snake string) func(string) bool {
u := strings.ReplaceAll(snake, "_", "")
return func(s string) bool {
return strings.EqualFold(s, u)
}
}
func unsnakeEq(orig, snake string) bool {
return unsnakeMatcher(snake)(orig)
}
type TypeErr string
type StructErr string
func (e TypeErr) Error() string { return string(e) }
func (e StructErr) Error() string { return string(e) }
var blockType reflect.Type
func init() {
var b Block
blockType = reflect.TypeOf(b)
}