-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsource.go
More file actions
149 lines (133 loc) · 3.87 KB
/
Copy pathsource.go
File metadata and controls
149 lines (133 loc) · 3.87 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package validator
import (
"reflect"
"sort"
"strings"
"github.com/libtnb/validator/conv"
)
// varFieldName is the validation name of a Value input.
const varFieldName = "value"
// source is the data set behind a Validation. Each input kind (struct, map,
// single value) implements it, so the engine resolves names, "sometimes"
// absence and the root value without knowing which kind it holds.
type source interface {
// lookup resolves a validation name. found is false when the name is not
// part of the data set; a found name may still carry an invalid Value
// (nil pointer, explicit null).
lookup(name string) (reflect.Value, bool)
// absent reports whether a "sometimes" rule should skip name entirely.
absent(name string) bool
// raw returns the complete input, for Field.Root.
raw() any
}
// mapSource backs Map, JSON and Values inputs.
type mapSource struct{ m map[string]any }
func (s mapSource) lookup(name string) (reflect.Value, bool) {
return lookupInMap(s.m, name)
}
// absent distinguishes a missing key from an explicit null: only the former
// skips a "sometimes" field.
func (s mapSource) absent(name string) bool {
_, found := lookupInMap(s.m, name)
return !found
}
func (s mapSource) raw() any { return s.m }
// varSource backs Value inputs. Only varFieldName resolves, so cross-field
// references miss rather than compare the value with itself.
type varSource struct{ val reflect.Value }
func (s *varSource) lookup(name string) (reflect.Value, bool) {
if name != varFieldName {
return reflect.Value{}, false
}
return unwrapValue(s.val), true
}
func (s *varSource) absent(name string) bool { return name != varFieldName }
func (s *varSource) raw() any { return valToAny(s.val) }
func toValue(v any) reflect.Value {
if v == nil {
return reflect.Value{}
}
return unwrapValue(reflect.ValueOf(v))
}
// lookupInMap resolves a dotted name, nested path before flat key so a decoy key can't shadow a nested field.
func lookupInMap(m map[string]any, name string) (reflect.Value, bool) {
if v, ok := descendDotted(m, name); ok {
return toValue(v), true
}
if v, ok := m[name]; ok {
return toValue(v), true
}
return reflect.Value{}, false
}
func descendDotted(m map[string]any, path string) (any, bool) {
head, rest, ok := strings.Cut(path, ".")
if !ok {
return nil, false
}
next, ok := m[head]
if !ok {
return nil, false
}
nm, ok := asStringMap(unwrap(next))
if !ok {
return nil, false
}
if v, ok := descendDotted(nm, rest); ok {
return v, true
}
if v, ok := nm[rest]; ok {
return v, true
}
return nil, false
}
// asStringMap returns v as a map[string]any for any map kind. String keys are
// copied as-is; other key types are rendered, sorted so a collision (int 1 vs
// string "1") has a deterministic survivor.
func asStringMap(v any) (map[string]any, bool) {
switch m := v.(type) {
case nil:
return nil, false
case map[string]any:
return m, true
case map[string]string:
out := make(map[string]any, len(m))
for k, val := range m {
out[k] = val
}
return out, true
}
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Map {
return nil, false
}
if rv.Type().Key().Kind() == reflect.String {
out := make(map[string]any, rv.Len())
for it := rv.MapRange(); it.Next(); {
out[it.Key().String()] = it.Value().Interface()
}
return out, true
}
type kv struct {
mapKeyOrder
v any
}
entries := make([]kv, 0, rv.Len())
for it := rv.MapRange(); it.Next(); {
k, vv := it.Key(), it.Value()
entries = append(entries, kv{
mapKeyOrder: mapKeyOrder{
keyRepr: conv.ToString(k.Interface()),
keyType: dynType(k).String(),
valRepr: conv.ToString(vv.Interface()),
valType: dynType(vv).String(),
},
v: vv.Interface(),
})
}
sort.Slice(entries, func(a, b int) bool { return entries[a].less(entries[b].mapKeyOrder) })
m := make(map[string]any, len(entries))
for _, e := range entries {
m[e.keyRepr] = e.v
}
return m, true
}