]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // reflectwalk is a package that allows you to "walk" complex structures |
2 | // similar to how you may "walk" a filesystem: visiting every element one | |
3 | // by one and calling callback functions allowing you to handle and manipulate | |
4 | // those elements. | |
5 | package reflectwalk | |
6 | ||
7 | import ( | |
8 | "errors" | |
9 | "reflect" | |
10 | ) | |
11 | ||
12 | // PrimitiveWalker implementations are able to handle primitive values | |
13 | // within complex structures. Primitive values are numbers, strings, | |
14 | // booleans, funcs, chans. | |
15 | // | |
16 | // These primitive values are often members of more complex | |
17 | // structures (slices, maps, etc.) that are walkable by other interfaces. | |
18 | type PrimitiveWalker interface { | |
19 | Primitive(reflect.Value) error | |
20 | } | |
21 | ||
22 | // InterfaceWalker implementations are able to handle interface values as they | |
23 | // are encountered during the walk. | |
24 | type InterfaceWalker interface { | |
25 | Interface(reflect.Value) error | |
26 | } | |
27 | ||
28 | // MapWalker implementations are able to handle individual elements | |
29 | // found within a map structure. | |
30 | type MapWalker interface { | |
31 | Map(m reflect.Value) error | |
32 | MapElem(m, k, v reflect.Value) error | |
33 | } | |
34 | ||
35 | // SliceWalker implementations are able to handle slice elements found | |
36 | // within complex structures. | |
37 | type SliceWalker interface { | |
38 | Slice(reflect.Value) error | |
39 | SliceElem(int, reflect.Value) error | |
40 | } | |
41 | ||
42 | // StructWalker is an interface that has methods that are called for | |
43 | // structs when a Walk is done. | |
44 | type StructWalker interface { | |
45 | Struct(reflect.Value) error | |
46 | StructField(reflect.StructField, reflect.Value) error | |
47 | } | |
48 | ||
49 | // EnterExitWalker implementations are notified before and after | |
50 | // they walk deeper into complex structures (into struct fields, | |
51 | // into slice elements, etc.) | |
52 | type EnterExitWalker interface { | |
53 | Enter(Location) error | |
54 | Exit(Location) error | |
55 | } | |
56 | ||
57 | // PointerWalker implementations are notified when the value they're | |
58 | // walking is a pointer or not. Pointer is called for _every_ value whether | |
59 | // it is a pointer or not. | |
60 | type PointerWalker interface { | |
61 | PointerEnter(bool) error | |
62 | PointerExit(bool) error | |
63 | } | |
64 | ||
65 | // SkipEntry can be returned from walk functions to skip walking | |
66 | // the value of this field. This is only valid in the following functions: | |
67 | // | |
68 | // - StructField: skips walking the struct value | |
69 | // | |
70 | var SkipEntry = errors.New("skip this entry") | |
71 | ||
72 | // Walk takes an arbitrary value and an interface and traverses the | |
73 | // value, calling callbacks on the interface if they are supported. | |
74 | // The interface should implement one or more of the walker interfaces | |
75 | // in this package, such as PrimitiveWalker, StructWalker, etc. | |
76 | func Walk(data, walker interface{}) (err error) { | |
77 | v := reflect.ValueOf(data) | |
78 | ew, ok := walker.(EnterExitWalker) | |
79 | if ok { | |
80 | err = ew.Enter(WalkLoc) | |
81 | } | |
82 | ||
83 | if err == nil { | |
84 | err = walk(v, walker) | |
85 | } | |
86 | ||
87 | if ok && err == nil { | |
88 | err = ew.Exit(WalkLoc) | |
89 | } | |
90 | ||
91 | return | |
92 | } | |
93 | ||
94 | func walk(v reflect.Value, w interface{}) (err error) { | |
95 | // Determine if we're receiving a pointer and if so notify the walker. | |
96 | // The logic here is convoluted but very important (tests will fail if | |
97 | // almost any part is changed). I will try to explain here. | |
98 | // | |
99 | // First, we check if the value is an interface, if so, we really need | |
100 | // to check the interface's VALUE to see whether it is a pointer. | |
101 | // | |
102 | // Check whether the value is then a pointer. If so, then set pointer | |
103 | // to true to notify the user. | |
104 | // | |
105 | // If we still have a pointer or an interface after the indirections, then | |
106 | // we unwrap another level | |
107 | // | |
108 | // At this time, we also set "v" to be the dereferenced value. This is | |
109 | // because once we've unwrapped the pointer we want to use that value. | |
110 | pointer := false | |
111 | pointerV := v | |
112 | ||
113 | for { | |
114 | if pointerV.Kind() == reflect.Interface { | |
115 | if iw, ok := w.(InterfaceWalker); ok { | |
116 | if err = iw.Interface(pointerV); err != nil { | |
117 | return | |
118 | } | |
119 | } | |
120 | ||
121 | pointerV = pointerV.Elem() | |
122 | } | |
123 | ||
124 | if pointerV.Kind() == reflect.Ptr { | |
125 | pointer = true | |
126 | v = reflect.Indirect(pointerV) | |
127 | } | |
128 | if pw, ok := w.(PointerWalker); ok { | |
129 | if err = pw.PointerEnter(pointer); err != nil { | |
130 | return | |
131 | } | |
132 | ||
133 | defer func(pointer bool) { | |
134 | if err != nil { | |
135 | return | |
136 | } | |
137 | ||
138 | err = pw.PointerExit(pointer) | |
139 | }(pointer) | |
140 | } | |
141 | ||
142 | if pointer { | |
143 | pointerV = v | |
144 | } | |
145 | pointer = false | |
146 | ||
147 | // If we still have a pointer or interface we have to indirect another level. | |
148 | switch pointerV.Kind() { | |
149 | case reflect.Ptr, reflect.Interface: | |
150 | continue | |
151 | } | |
152 | break | |
153 | } | |
154 | ||
155 | // We preserve the original value here because if it is an interface | |
156 | // type, we want to pass that directly into the walkPrimitive, so that | |
157 | // we can set it. | |
158 | originalV := v | |
159 | if v.Kind() == reflect.Interface { | |
160 | v = v.Elem() | |
161 | } | |
162 | ||
163 | k := v.Kind() | |
164 | if k >= reflect.Int && k <= reflect.Complex128 { | |
165 | k = reflect.Int | |
166 | } | |
167 | ||
168 | switch k { | |
169 | // Primitives | |
170 | case reflect.Bool, reflect.Chan, reflect.Func, reflect.Int, reflect.String, reflect.Invalid: | |
171 | err = walkPrimitive(originalV, w) | |
172 | return | |
173 | case reflect.Map: | |
174 | err = walkMap(v, w) | |
175 | return | |
176 | case reflect.Slice: | |
177 | err = walkSlice(v, w) | |
178 | return | |
179 | case reflect.Struct: | |
180 | err = walkStruct(v, w) | |
181 | return | |
182 | default: | |
183 | panic("unsupported type: " + k.String()) | |
184 | } | |
185 | } | |
186 | ||
187 | func walkMap(v reflect.Value, w interface{}) error { | |
188 | ew, ewok := w.(EnterExitWalker) | |
189 | if ewok { | |
190 | ew.Enter(Map) | |
191 | } | |
192 | ||
193 | if mw, ok := w.(MapWalker); ok { | |
194 | if err := mw.Map(v); err != nil { | |
195 | return err | |
196 | } | |
197 | } | |
198 | ||
199 | for _, k := range v.MapKeys() { | |
200 | kv := v.MapIndex(k) | |
201 | ||
202 | if mw, ok := w.(MapWalker); ok { | |
203 | if err := mw.MapElem(v, k, kv); err != nil { | |
204 | return err | |
205 | } | |
206 | } | |
207 | ||
208 | ew, ok := w.(EnterExitWalker) | |
209 | if ok { | |
210 | ew.Enter(MapKey) | |
211 | } | |
212 | ||
213 | if err := walk(k, w); err != nil { | |
214 | return err | |
215 | } | |
216 | ||
217 | if ok { | |
218 | ew.Exit(MapKey) | |
219 | ew.Enter(MapValue) | |
220 | } | |
221 | ||
222 | if err := walk(kv, w); err != nil { | |
223 | return err | |
224 | } | |
225 | ||
226 | if ok { | |
227 | ew.Exit(MapValue) | |
228 | } | |
229 | } | |
230 | ||
231 | if ewok { | |
232 | ew.Exit(Map) | |
233 | } | |
234 | ||
235 | return nil | |
236 | } | |
237 | ||
238 | func walkPrimitive(v reflect.Value, w interface{}) error { | |
239 | if pw, ok := w.(PrimitiveWalker); ok { | |
240 | return pw.Primitive(v) | |
241 | } | |
242 | ||
243 | return nil | |
244 | } | |
245 | ||
246 | func walkSlice(v reflect.Value, w interface{}) (err error) { | |
247 | ew, ok := w.(EnterExitWalker) | |
248 | if ok { | |
249 | ew.Enter(Slice) | |
250 | } | |
251 | ||
252 | if sw, ok := w.(SliceWalker); ok { | |
253 | if err := sw.Slice(v); err != nil { | |
254 | return err | |
255 | } | |
256 | } | |
257 | ||
258 | for i := 0; i < v.Len(); i++ { | |
259 | elem := v.Index(i) | |
260 | ||
261 | if sw, ok := w.(SliceWalker); ok { | |
262 | if err := sw.SliceElem(i, elem); err != nil { | |
263 | return err | |
264 | } | |
265 | } | |
266 | ||
267 | ew, ok := w.(EnterExitWalker) | |
268 | if ok { | |
269 | ew.Enter(SliceElem) | |
270 | } | |
271 | ||
272 | if err := walk(elem, w); err != nil { | |
273 | return err | |
274 | } | |
275 | ||
276 | if ok { | |
277 | ew.Exit(SliceElem) | |
278 | } | |
279 | } | |
280 | ||
281 | ew, ok = w.(EnterExitWalker) | |
282 | if ok { | |
283 | ew.Exit(Slice) | |
284 | } | |
285 | ||
286 | return nil | |
287 | } | |
288 | ||
289 | func walkStruct(v reflect.Value, w interface{}) (err error) { | |
290 | ew, ewok := w.(EnterExitWalker) | |
291 | if ewok { | |
292 | ew.Enter(Struct) | |
293 | } | |
294 | ||
295 | if sw, ok := w.(StructWalker); ok { | |
296 | if err = sw.Struct(v); err != nil { | |
297 | return | |
298 | } | |
299 | } | |
300 | ||
301 | vt := v.Type() | |
302 | for i := 0; i < vt.NumField(); i++ { | |
303 | sf := vt.Field(i) | |
304 | f := v.FieldByIndex([]int{i}) | |
305 | ||
306 | if sw, ok := w.(StructWalker); ok { | |
307 | err = sw.StructField(sf, f) | |
308 | ||
309 | // SkipEntry just pretends this field doesn't even exist | |
310 | if err == SkipEntry { | |
311 | continue | |
312 | } | |
313 | ||
314 | if err != nil { | |
315 | return | |
316 | } | |
317 | } | |
318 | ||
319 | ew, ok := w.(EnterExitWalker) | |
320 | if ok { | |
321 | ew.Enter(StructField) | |
322 | } | |
323 | ||
324 | err = walk(f, w) | |
325 | if err != nil { | |
326 | return | |
327 | } | |
328 | ||
329 | if ok { | |
330 | ew.Exit(StructField) | |
331 | } | |
332 | } | |
333 | ||
334 | if ewok { | |
335 | ew.Exit(Struct) | |
336 | } | |
337 | ||
338 | return nil | |
339 | } |