-
Notifications
You must be signed in to change notification settings - Fork 2
/
dot.go
157 lines (130 loc) · 4.47 KB
/
dot.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package dot
import (
"fmt"
"reflect"
"strings"
)
// Repetitive error message
var errUnknownPath = "unknown path: %s"
// Scenario is a type to define a scenario for
// further action depending on the type
type Scenario uint
// Basic scenarios depending on type
const (
Array Scenario = iota
Channel
Map
Slice
Var
)
// Each scenario has its own error message when the type does not match
var errMsg = map[Scenario]string{
Array: "a %s type array cannot contain a %s type value in path %s",
Channel: "channel of type %s cannot contain a value of type %s in path %s",
Slice: "a slice of type %s cannot contain a value of type %s in path %s",
Map: "the map value is of type %s and cannot contain a value of type %s in path %s",
Var: "type %s cannot contain a value of type %s in path %s",
}
// Dot provides the necessary methods for manipulating
// the value from the data types provided
type Dot struct {
Object reflect.Value // Object is the reflection interface of the object provided for manipulation
Content any // Content is the value to be inserted in the specified path
Placeholders map[string]any // Placeholders contains substitutes by name for specific map key types
}
// New initialises a new structure with the necessary data for value manipulation
// The object provided must be a pointer
func New(obj any) (*Dot, error) {
innerObj := reflect.ValueOf(obj)
if innerObj.Kind() != reflect.Ptr {
return nil, fmt.Errorf("expected a pointer")
}
return &Dot{
Object: innerObj.Elem(),
Placeholders: make(map[string]any),
}, nil
}
// Replace creates a new placeholder by replacing the map key with a specific value
//
// e.g. map path: Insert("Data.mapKey", 1), Replace("mapKey", SpecificConstant)
// it is equivalent to: Data[SpecificConstant] = 1
func (d *Dot) Replace(key string, value any) {
d.Placeholders[key] = value
}
// Insert receives a specific path separated by dots and the value to be inserted into that path
func (d *Dot) Insert(path string, content any) error {
// Save the content in the structure
d.Content = content
// Separate the received path by a point
parts := strings.Split(path, ".")
if path == "" {
parts = []string{}
}
return d.insert(d.Object, "", parts, Var)
}
// insert is called recursively to insert a value into the specified path
func (d *Dot) insert(innerObj reflect.Value, previousPath string, parts []string, source Scenario) error {
// Preparing the current path on the current segment
currentPath := preparePath(previousPath, parts)
for index, fieldName := range parts {
// Preparing a value insertion path
currentPath = preparePath(previousPath, parts[:index+1])
// Removing segments already traversed from the path
remainingParts := parts[index:]
// Determine the type of current path segment
switch innerObj.Kind() {
case reflect.Map:
err := d.inMap(innerObj, currentPath, remainingParts)
if err != nil {
return err
}
return nil
case reflect.Slice:
err := d.inSlice(innerObj, currentPath, remainingParts)
if err != nil {
return err
}
return nil
case reflect.Array:
err := d.inArray(innerObj, currentPath, remainingParts)
if err != nil {
return err
}
return nil
case reflect.Struct:
innerObj = innerObj.FieldByName(fieldName)
// The value is inserted into the channel immediately,
// so we call the method to insert the value into the channel
if innerObj.Kind() == reflect.Chan {
return d.inChannel(innerObj, currentPath, remainingParts)
}
case reflect.Interface:
return fmt.Errorf(
"the type in %s is interface{} and it is impossible to further predict the path",
previousPath,
)
default:
// If it is logical to already insert a value in the specified path,
// but the path has not yet ended, it means that the path is specified incorrectly
if len(remainingParts) != 0 {
return fmt.Errorf(errUnknownPath, currentPath)
}
}
if innerObj.Kind() == reflect.Invalid {
return fmt.Errorf(errUnknownPath, currentPath)
}
}
return set(innerObj, currentPath, d.Content, source)
}
// set is the final step for inserting a value on the specified path
func set(innerObj reflect.Value, currentPath string, content any, source Scenario) error {
value := reflect.ValueOf(content)
// Checking for type matching
if innerObj.Type() != value.Type() && innerObj.Kind() != reflect.Interface {
return fmt.Errorf(
errMsg[source], innerObj.Type(), value.Type(), currentPath,
)
}
innerObj.Set(value)
return nil
}