]>
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 | ||
15c0b25d AP |
42 | // ArrayWalker implementations are able to handle array elements found |
43 | // within complex structures. | |
44 | type ArrayWalker interface { | |
45 | Array(reflect.Value) error | |
46 | ArrayElem(int, reflect.Value) error | |
47 | } | |
48 | ||
bae9f6d2 JC |
49 | // StructWalker is an interface that has methods that are called for |
50 | // structs when a Walk is done. | |
51 | type StructWalker interface { | |
52 | Struct(reflect.Value) error | |
53 | StructField(reflect.StructField, reflect.Value) error | |
54 | } | |
55 | ||
56 | // EnterExitWalker implementations are notified before and after | |
57 | // they walk deeper into complex structures (into struct fields, | |
58 | // into slice elements, etc.) | |
59 | type EnterExitWalker interface { | |
60 | Enter(Location) error | |
61 | Exit(Location) error | |
62 | } | |
63 | ||
64 | // PointerWalker implementations are notified when the value they're | |
65 | // walking is a pointer or not. Pointer is called for _every_ value whether | |
66 | // it is a pointer or not. | |
67 | type PointerWalker interface { | |
68 | PointerEnter(bool) error | |
69 | PointerExit(bool) error | |
70 | } | |
71 | ||
72 | // SkipEntry can be returned from walk functions to skip walking | |
73 | // the value of this field. This is only valid in the following functions: | |
74 | // | |
15c0b25d | 75 | // - Struct: skips all fields from being walked |
bae9f6d2 JC |
76 | // - StructField: skips walking the struct value |
77 | // | |
78 | var SkipEntry = errors.New("skip this entry") | |
79 | ||
80 | // Walk takes an arbitrary value and an interface and traverses the | |
81 | // value, calling callbacks on the interface if they are supported. | |
82 | // The interface should implement one or more of the walker interfaces | |
83 | // in this package, such as PrimitiveWalker, StructWalker, etc. | |
84 | func Walk(data, walker interface{}) (err error) { | |
85 | v := reflect.ValueOf(data) | |
86 | ew, ok := walker.(EnterExitWalker) | |
87 | if ok { | |
88 | err = ew.Enter(WalkLoc) | |
89 | } | |
90 | ||
91 | if err == nil { | |
92 | err = walk(v, walker) | |
93 | } | |
94 | ||
95 | if ok && err == nil { | |
96 | err = ew.Exit(WalkLoc) | |
97 | } | |
98 | ||
99 | return | |
100 | } | |
101 | ||
102 | func walk(v reflect.Value, w interface{}) (err error) { | |
103 | // Determine if we're receiving a pointer and if so notify the walker. | |
104 | // The logic here is convoluted but very important (tests will fail if | |
105 | // almost any part is changed). I will try to explain here. | |
106 | // | |
107 | // First, we check if the value is an interface, if so, we really need | |
108 | // to check the interface's VALUE to see whether it is a pointer. | |
109 | // | |
110 | // Check whether the value is then a pointer. If so, then set pointer | |
111 | // to true to notify the user. | |
112 | // | |
113 | // If we still have a pointer or an interface after the indirections, then | |
114 | // we unwrap another level | |
115 | // | |
116 | // At this time, we also set "v" to be the dereferenced value. This is | |
117 | // because once we've unwrapped the pointer we want to use that value. | |
118 | pointer := false | |
119 | pointerV := v | |
120 | ||
121 | for { | |
122 | if pointerV.Kind() == reflect.Interface { | |
123 | if iw, ok := w.(InterfaceWalker); ok { | |
124 | if err = iw.Interface(pointerV); err != nil { | |
125 | return | |
126 | } | |
127 | } | |
128 | ||
129 | pointerV = pointerV.Elem() | |
130 | } | |
131 | ||
132 | if pointerV.Kind() == reflect.Ptr { | |
133 | pointer = true | |
134 | v = reflect.Indirect(pointerV) | |
135 | } | |
136 | if pw, ok := w.(PointerWalker); ok { | |
137 | if err = pw.PointerEnter(pointer); err != nil { | |
138 | return | |
139 | } | |
140 | ||
141 | defer func(pointer bool) { | |
142 | if err != nil { | |
143 | return | |
144 | } | |
145 | ||
146 | err = pw.PointerExit(pointer) | |
147 | }(pointer) | |
148 | } | |
149 | ||
150 | if pointer { | |
151 | pointerV = v | |
152 | } | |
153 | pointer = false | |
154 | ||
155 | // If we still have a pointer or interface we have to indirect another level. | |
156 | switch pointerV.Kind() { | |
157 | case reflect.Ptr, reflect.Interface: | |
158 | continue | |
159 | } | |
160 | break | |
161 | } | |
162 | ||
163 | // We preserve the original value here because if it is an interface | |
164 | // type, we want to pass that directly into the walkPrimitive, so that | |
165 | // we can set it. | |
166 | originalV := v | |
167 | if v.Kind() == reflect.Interface { | |
168 | v = v.Elem() | |
169 | } | |
170 | ||
171 | k := v.Kind() | |
172 | if k >= reflect.Int && k <= reflect.Complex128 { | |
173 | k = reflect.Int | |
174 | } | |
175 | ||
176 | switch k { | |
177 | // Primitives | |
178 | case reflect.Bool, reflect.Chan, reflect.Func, reflect.Int, reflect.String, reflect.Invalid: | |
179 | err = walkPrimitive(originalV, w) | |
180 | return | |
181 | case reflect.Map: | |
182 | err = walkMap(v, w) | |
183 | return | |
184 | case reflect.Slice: | |
185 | err = walkSlice(v, w) | |
186 | return | |
187 | case reflect.Struct: | |
188 | err = walkStruct(v, w) | |
189 | return | |
15c0b25d AP |
190 | case reflect.Array: |
191 | err = walkArray(v, w) | |
192 | return | |
bae9f6d2 JC |
193 | default: |
194 | panic("unsupported type: " + k.String()) | |
195 | } | |
196 | } | |
197 | ||
198 | func walkMap(v reflect.Value, w interface{}) error { | |
199 | ew, ewok := w.(EnterExitWalker) | |
200 | if ewok { | |
201 | ew.Enter(Map) | |
202 | } | |
203 | ||
204 | if mw, ok := w.(MapWalker); ok { | |
205 | if err := mw.Map(v); err != nil { | |
206 | return err | |
207 | } | |
208 | } | |
209 | ||
210 | for _, k := range v.MapKeys() { | |
211 | kv := v.MapIndex(k) | |
212 | ||
213 | if mw, ok := w.(MapWalker); ok { | |
214 | if err := mw.MapElem(v, k, kv); err != nil { | |
215 | return err | |
216 | } | |
217 | } | |
218 | ||
219 | ew, ok := w.(EnterExitWalker) | |
220 | if ok { | |
221 | ew.Enter(MapKey) | |
222 | } | |
223 | ||
224 | if err := walk(k, w); err != nil { | |
225 | return err | |
226 | } | |
227 | ||
228 | if ok { | |
229 | ew.Exit(MapKey) | |
230 | ew.Enter(MapValue) | |
231 | } | |
232 | ||
233 | if err := walk(kv, w); err != nil { | |
234 | return err | |
235 | } | |
236 | ||
237 | if ok { | |
238 | ew.Exit(MapValue) | |
239 | } | |
240 | } | |
241 | ||
242 | if ewok { | |
243 | ew.Exit(Map) | |
244 | } | |
245 | ||
246 | return nil | |
247 | } | |
248 | ||
249 | func walkPrimitive(v reflect.Value, w interface{}) error { | |
250 | if pw, ok := w.(PrimitiveWalker); ok { | |
251 | return pw.Primitive(v) | |
252 | } | |
253 | ||
254 | return nil | |
255 | } | |
256 | ||
257 | func walkSlice(v reflect.Value, w interface{}) (err error) { | |
258 | ew, ok := w.(EnterExitWalker) | |
259 | if ok { | |
260 | ew.Enter(Slice) | |
261 | } | |
262 | ||
263 | if sw, ok := w.(SliceWalker); ok { | |
264 | if err := sw.Slice(v); err != nil { | |
265 | return err | |
266 | } | |
267 | } | |
268 | ||
269 | for i := 0; i < v.Len(); i++ { | |
270 | elem := v.Index(i) | |
271 | ||
272 | if sw, ok := w.(SliceWalker); ok { | |
273 | if err := sw.SliceElem(i, elem); err != nil { | |
274 | return err | |
275 | } | |
276 | } | |
277 | ||
278 | ew, ok := w.(EnterExitWalker) | |
279 | if ok { | |
280 | ew.Enter(SliceElem) | |
281 | } | |
282 | ||
283 | if err := walk(elem, w); err != nil { | |
284 | return err | |
285 | } | |
286 | ||
287 | if ok { | |
288 | ew.Exit(SliceElem) | |
289 | } | |
290 | } | |
291 | ||
292 | ew, ok = w.(EnterExitWalker) | |
293 | if ok { | |
294 | ew.Exit(Slice) | |
295 | } | |
296 | ||
297 | return nil | |
298 | } | |
299 | ||
15c0b25d AP |
300 | func walkArray(v reflect.Value, w interface{}) (err error) { |
301 | ew, ok := w.(EnterExitWalker) | |
302 | if ok { | |
303 | ew.Enter(Array) | |
304 | } | |
305 | ||
306 | if aw, ok := w.(ArrayWalker); ok { | |
307 | if err := aw.Array(v); err != nil { | |
308 | return err | |
309 | } | |
310 | } | |
311 | ||
312 | for i := 0; i < v.Len(); i++ { | |
313 | elem := v.Index(i) | |
314 | ||
315 | if aw, ok := w.(ArrayWalker); ok { | |
316 | if err := aw.ArrayElem(i, elem); err != nil { | |
317 | return err | |
318 | } | |
319 | } | |
320 | ||
321 | ew, ok := w.(EnterExitWalker) | |
322 | if ok { | |
323 | ew.Enter(ArrayElem) | |
324 | } | |
325 | ||
326 | if err := walk(elem, w); err != nil { | |
327 | return err | |
328 | } | |
329 | ||
330 | if ok { | |
331 | ew.Exit(ArrayElem) | |
332 | } | |
333 | } | |
334 | ||
335 | ew, ok = w.(EnterExitWalker) | |
336 | if ok { | |
337 | ew.Exit(Array) | |
338 | } | |
339 | ||
340 | return nil | |
341 | } | |
342 | ||
bae9f6d2 JC |
343 | func walkStruct(v reflect.Value, w interface{}) (err error) { |
344 | ew, ewok := w.(EnterExitWalker) | |
345 | if ewok { | |
346 | ew.Enter(Struct) | |
347 | } | |
348 | ||
15c0b25d | 349 | skip := false |
bae9f6d2 | 350 | if sw, ok := w.(StructWalker); ok { |
15c0b25d AP |
351 | err = sw.Struct(v) |
352 | if err == SkipEntry { | |
353 | skip = true | |
354 | err = nil | |
355 | } | |
356 | if err != nil { | |
bae9f6d2 JC |
357 | return |
358 | } | |
359 | } | |
360 | ||
15c0b25d AP |
361 | if !skip { |
362 | vt := v.Type() | |
363 | for i := 0; i < vt.NumField(); i++ { | |
364 | sf := vt.Field(i) | |
365 | f := v.FieldByIndex([]int{i}) | |
bae9f6d2 | 366 | |
15c0b25d AP |
367 | if sw, ok := w.(StructWalker); ok { |
368 | err = sw.StructField(sf, f) | |
bae9f6d2 | 369 | |
15c0b25d AP |
370 | // SkipEntry just pretends this field doesn't even exist |
371 | if err == SkipEntry { | |
372 | continue | |
373 | } | |
374 | ||
375 | if err != nil { | |
376 | return | |
377 | } | |
378 | } | |
379 | ||
380 | ew, ok := w.(EnterExitWalker) | |
381 | if ok { | |
382 | ew.Enter(StructField) | |
bae9f6d2 JC |
383 | } |
384 | ||
15c0b25d | 385 | err = walk(f, w) |
bae9f6d2 JC |
386 | if err != nil { |
387 | return | |
388 | } | |
bae9f6d2 | 389 | |
15c0b25d AP |
390 | if ok { |
391 | ew.Exit(StructField) | |
392 | } | |
bae9f6d2 JC |
393 | } |
394 | } | |
395 | ||
396 | if ewok { | |
397 | ew.Exit(Struct) | |
398 | } | |
399 | ||
400 | return nil | |
401 | } |