├── LICENSE ├── README.md ├── utils.go ├── bench_test.go ├── pair_test.go ├── utils_test.go ├── walk.go ├── pair.go ├── example_test.go ├── walk_test.go ├── iter.go └── iter_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Chris Stockton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Package: iter 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/cstockton/go-iter)](https://goreportcard.com/report/github.com/cstockton/go-iter) 4 | 5 | > Get: 6 | > ```bash 7 | > go get -u github.com/cstockton/go-iter 8 | > ``` 9 | > 10 | > Example: 11 | > ```Go 12 | > func Example() { 13 | > v := []interface{}{"a", "b", []string{"c", "d"}} 14 | > err := iter.Walk(v, func(el iter.Pair) error { 15 | > // check for errors 16 | > if err := el.Err(); err != nil { 17 | > return err 18 | > } 19 | > 20 | > // Halt iteration by returning an error. 21 | > if el.Depth() > 1 { 22 | > return errors.New("Stopping this walk.") 23 | > } 24 | > 25 | > fmt.Println(el) 26 | > return nil 27 | > }) 28 | > if err == nil { 29 | > log.Fatal(err) 30 | > } 31 | > 32 | > // Output: 33 | > // Pair{(int) 0 => a (string)} 34 | > // Pair{(int) 1 => b (string)} 35 | > } 36 | > ``` 37 | 38 | 39 | ## About 40 | 41 | Package iter provides primitives for walking arbitrary data structures. 42 | 43 | 44 | ## Bugs and Patches 45 | 46 | Feel free to report bugs and submit pull requests. 47 | 48 | * bugs: 49 | 50 | * patches: 51 | 52 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Functions that are not worth an import dependency are in here, they come from 9 | // another package of mine, go-refutil and are well tested. 10 | 11 | // indirect will perform recursive indirection on the given value. It should 12 | // never panic and will return a value unless indirection is impossible due to 13 | // infinite recursion in cases like `type Element *Element`. 14 | func indirect(value interface{}) interface{} { 15 | 16 | // Just to be safe, recursion should not be possible but I may be 17 | // missing an edge case. 18 | for { 19 | 20 | val := reflect.ValueOf(value) 21 | if !val.IsValid() || val.Kind() != reflect.Ptr { 22 | // Value is not a pointer. 23 | return value 24 | } 25 | 26 | res := reflect.Indirect(val) 27 | if !res.IsValid() || !res.CanInterface() { 28 | // Invalid value or can't be returned as interface{}. 29 | return value 30 | } 31 | 32 | // Test for a circular type. 33 | if res.Kind() == reflect.Ptr && val.Pointer() == res.Pointer() { 34 | return value 35 | } 36 | 37 | // Next round. 38 | value = res.Interface() 39 | } 40 | } 41 | 42 | // recoverFn will attempt to execute f, if f return a non-nil error it will be 43 | // returned. If f panics this function will attempt to recover() and return a 44 | // error instead. 45 | func recoverFn(f func() error) (err error) { 46 | defer func() { 47 | if r := recover(); r != nil { 48 | switch T := r.(type) { 49 | case error: 50 | err = T 51 | default: 52 | err = fmt.Errorf("panic: %v", r) 53 | } 54 | } 55 | }() 56 | err = f() 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkIteration(b *testing.B) { 10 | for x := uint64(4); x < 18; x += 4 { 11 | benchCount := 1 << (x - 1) 12 | 13 | b.Run(fmt.Sprintf("Slices/%d", benchCount), func(b *testing.B) { 14 | benchStrings := make([]string, benchCount) 15 | for y := 0; y < benchCount; y++ { 16 | benchStrings[y] = fmt.Sprintf("bench string %v", y) 17 | } 18 | benchVal := reflect.ValueOf(benchStrings) 19 | benchVals := make([]reflect.Value, len(benchStrings)) 20 | for i, v := range benchStrings { 21 | benchVals[i] = reflect.ValueOf(v) 22 | } 23 | b.ResetTimer() 24 | 25 | b.Run("Range", func(b *testing.B) { 26 | f := func(idx int, v reflect.Value) error { 27 | return nil 28 | } 29 | b.ResetTimer() 30 | for i := 0; i < b.N; i++ { 31 | for z, v := range benchVals { 32 | f(z, v) 33 | } 34 | } 35 | }) 36 | b.Run("Iter", func(b *testing.B) { 37 | it := &Iter{} 38 | f := func(idx int, v reflect.Value) error { 39 | return nil 40 | } 41 | b.ResetTimer() 42 | for i := 0; i < b.N; i++ { 43 | it.IterSlice(benchVal, f) 44 | } 45 | }) 46 | b.Run("RecoverIter", func(b *testing.B) { 47 | it := recoverIter{&Iter{}} 48 | f := func(idx int, v reflect.Value) error { 49 | return nil 50 | } 51 | b.ResetTimer() 52 | for i := 0; i < b.N; i++ { 53 | it.IterSlice(benchVal, f) 54 | } 55 | }) 56 | }) 57 | b.Run(fmt.Sprintf("Maps/%d", benchCount), func(b *testing.B) { 58 | benchMaps := make(map[int]string, benchCount) 59 | for y := 0; y < benchCount; y++ { 60 | benchMaps[y] = fmt.Sprintf("bench string %v", y) 61 | } 62 | benchVal := reflect.ValueOf(benchMaps) 63 | benchVals := make([]reflect.Value, len(benchMaps)) 64 | for i, v := range benchMaps { 65 | benchVals[i] = reflect.ValueOf(v) 66 | } 67 | b.ResetTimer() 68 | 69 | b.Run("Range", func(b *testing.B) { 70 | f := func(k, v reflect.Value) error { 71 | return nil 72 | } 73 | b.ResetTimer() 74 | for i := 0; i < b.N; i++ { 75 | for z, v := range benchVals { 76 | f(benchVals[z], v) 77 | } 78 | } 79 | }) 80 | b.Run("Iter", func(b *testing.B) { 81 | it := &Iter{} 82 | f := func(k, v reflect.Value) error { 83 | return nil 84 | } 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | it.IterMap(benchVal, f) 88 | } 89 | }) 90 | b.Run("RecoverIter", func(b *testing.B) { 91 | it := recoverIter{&Iter{}} 92 | f := func(idx int, v reflect.Value) error { 93 | return nil 94 | } 95 | b.ResetTimer() 96 | for i := 0; i < b.N; i++ { 97 | it.IterSlice(benchVal, f) 98 | } 99 | }) 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pair_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestPairInterface(t *testing.T) { 11 | var _ Pair = (*pair)(nil) 12 | } 13 | 14 | func TestPair(t *testing.T) { 15 | tok := func(t *testing.T, pr Pair) { 16 | if err := pr.Err(); err != nil { 17 | t.Fatalf("pair returned error: %v", err) 18 | } 19 | } 20 | rk := "rkey" 21 | rv := "rval" 22 | root := NewPair(nil, rk, rv, nil) 23 | tok(t, root) 24 | 25 | t.Run("String", func(t *testing.T) { 26 | exp := `Pair{(string) rkey => rval (string)}` 27 | if got := fmt.Sprintf("%v", root); got != exp { 28 | t.Errorf("String() failed:\n exp: %#v\n got: %#v", exp, got) 29 | } 30 | }) 31 | 32 | t.Run("String", func(t *testing.T) { 33 | type tstruct struct { 34 | Str string 35 | } 36 | typ := reflect.TypeOf(tstruct{"foo"}) 37 | pr := NewPair(nil, typ.Field(0), "foo", nil) 38 | exp := `Pair{(reflect.StructField) Str => foo (string)}` 39 | if got := fmt.Sprintf("%v", pr); got != exp { 40 | t.Errorf("String() failed:\n exp: %#v\n got: %#v", exp, got) 41 | } 42 | }) 43 | t.Run("Pair", func(t *testing.T) { 44 | k, v := root.Pair() 45 | if !reflect.DeepEqual(rk, k) { 46 | t.Errorf("DeepEqual failed Pair() key:\n exp: %#v\n got: %#v", rk, k) 47 | } 48 | if !reflect.DeepEqual(rv, v) { 49 | t.Errorf("DeepEqual failed Pair() value:\n exp: %#v\n got: %#v", rv, v) 50 | } 51 | }) 52 | 53 | t.Run("Err", func(t *testing.T) { 54 | err := errors.New("exp err") 55 | pr := &pair{err: err} 56 | pr.err = err 57 | if got := pr.Err(); !reflect.DeepEqual(err, got) { 58 | t.Errorf("DeepEqual failed pr.Err():\n exp: %#v\n got: %#v", err, got) 59 | } 60 | }) 61 | 62 | t.Run("Parent", func(t *testing.T) { 63 | k := "key" 64 | pr := NewPair(root, k, nil, nil) 65 | if got := pr.Parent(); !reflect.DeepEqual(root, got) { 66 | t.Errorf("DeepEqual failed pr.Parent():\n exp: %#v\n got: %#v", k, got) 67 | } 68 | }) 69 | 70 | t.Run("Depth", func(t *testing.T) { 71 | exp := 100 72 | var root Pair 73 | for i := 0; i <= exp; i++ { 74 | root = NewPair(root, i, fmt.Sprintf("Val%d", i), nil) 75 | tok(t, root) 76 | if got := root.Depth(); got != i { 77 | t.Errorf("failed pr.Depth():\n exp: %#v\n got: %#v", i, got) 78 | } 79 | } 80 | if got := root.Depth(); got != exp { 81 | t.Errorf("failed pr.Depth():\n exp: %#v\n got: %#v", exp, got) 82 | } 83 | }) 84 | 85 | t.Run("Key", func(t *testing.T) { 86 | k := "key" 87 | pr := NewPair(nil, k, nil, nil) 88 | if got := pr.Key(); !reflect.DeepEqual(k, got) { 89 | t.Errorf("DeepEqual failed pr.Key():\n exp: %#v\n got: %#v", k, got) 90 | } 91 | }) 92 | 93 | t.Run("Value", func(t *testing.T) { 94 | v := "val" 95 | pr := NewPair(nil, v, nil, nil) 96 | if got := pr.Key(); !reflect.DeepEqual(v, got) { 97 | t.Errorf("DeepEqual failed pr.Val():\n exp: %#v\n got: %#v", v, got) 98 | } 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | nilValues = []interface{}{ 12 | (*interface{})(nil), (**interface{})(nil), (***interface{})(nil), 13 | (func())(nil), (*func())(nil), (**func())(nil), (***func())(nil), 14 | (chan int)(nil), (*chan int)(nil), (**chan int)(nil), (***chan int)(nil), 15 | ([]int)(nil), (*[]int)(nil), (**[]int)(nil), (***[]int)(nil), 16 | (map[int]int)(nil), (*map[int]int)(nil), (**map[int]int)(nil), 17 | (***map[int]int)(nil), 18 | } 19 | ) 20 | 21 | func TestIndirect(t *testing.T) { 22 | type testindirectCircular *testindirectCircular 23 | teq := func(t testing.TB, exp, got interface{}) { 24 | if !reflect.DeepEqual(exp, got) { 25 | t.Errorf("DeepEqual failed:\n exp: %#v\n got: %#v", exp, got) 26 | } 27 | } 28 | 29 | t.Run("Basic", func(t *testing.T) { 30 | int64v := int64(123) 31 | int64vp := &int64v 32 | int64vpp := &int64vp 33 | int64vppp := &int64vpp 34 | int64vpppp := &int64vppp 35 | teq(t, indirect(int64v), int64v) 36 | teq(t, indirect(int64vp), int64v) 37 | teq(t, indirect(int64vpp), int64v) 38 | teq(t, indirect(int64vppp), int64v) 39 | teq(t, indirect(int64vpppp), int64v) 40 | }) 41 | t.Run("Nils", func(t *testing.T) { 42 | for _, n := range nilValues { 43 | indirect(n) 44 | } 45 | }) 46 | t.Run("Circular", func(t *testing.T) { 47 | var circular testindirectCircular 48 | circular = &circular 49 | teq(t, indirect(circular), circular) 50 | }) 51 | } 52 | 53 | func TestRecoverFn(t *testing.T) { 54 | t.Run("CallsFunc", func(t *testing.T) { 55 | var called bool 56 | 57 | err := recoverFn(func() error { 58 | called = true 59 | return nil 60 | }) 61 | if err != nil { 62 | t.Error("expected no error in recoverFn()") 63 | } 64 | if !called { 65 | t.Error("Expected recoverFn() to call func") 66 | } 67 | }) 68 | t.Run("PropagatesError", func(t *testing.T) { 69 | err := fmt.Errorf("expect this error") 70 | rerr := recoverFn(func() error { 71 | return err 72 | }) 73 | if err != rerr { 74 | t.Error("expected recoverFn() to propagate") 75 | } 76 | }) 77 | t.Run("PropagatesPanicError", func(t *testing.T) { 78 | err := fmt.Errorf("expect this error") 79 | rerr := recoverFn(func() error { 80 | panic(err) 81 | }) 82 | if err != rerr { 83 | t.Error("Expected recoverFn() to propagate") 84 | } 85 | }) 86 | t.Run("PropagatesRuntimeError", func(t *testing.T) { 87 | err := recoverFn(func() error { 88 | sl := []int{} 89 | _ = sl[0] 90 | return nil 91 | }) 92 | if err == nil { 93 | t.Error("expected runtime error to propagate") 94 | } 95 | if _, ok := err.(runtime.Error); !ok { 96 | t.Error("expected runtime error to retain type type") 97 | } 98 | }) 99 | t.Run("PropagatesString", func(t *testing.T) { 100 | exp := "panic: string type panic" 101 | rerr := recoverFn(func() error { 102 | panic("string type panic") 103 | }) 104 | if exp != rerr.Error() { 105 | t.Errorf("expected recoverFn() to return %v, got: %v", exp, rerr) 106 | } 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /walk.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import "reflect" 4 | 5 | // Walk will recursively walk the given interface value as long as an error does 6 | // not occur. The pair func will be given a interface value for each value 7 | // visited during walking and is expected to return an error if it thinks the 8 | // traversal should end. A nil value and error is given to the walk func if an 9 | // inaccessible value (can't reflect.Interface()) is found. 10 | // 11 | // Walk is called on each element of maps, slices and arrays. If the underlying 12 | // iterator is configured for channels it receives until one fails. Channels 13 | // should probably be avoided as ranging over them is more concise. 14 | func Walk(value interface{}, f func(el Pair) error) error { 15 | return defaultWalker.Walk(value, f) 16 | } 17 | 18 | // A Walker is used to perform a full traversal of each child value of any Go 19 | // type. Each implementation may use their own algorithm for traversal, giving 20 | // no guarantee for the order each element is visited in. 21 | type Walker interface { 22 | Walk(value interface{}, f func(el Pair) error) error 23 | } 24 | 25 | // NewWalker returns a new Walker backed by the given Iterator. It will use a 26 | // basic dfs traversal and will not visit items that can not be converted to an 27 | // interface. 28 | func NewWalker(iterator Iterator) Walker { 29 | return &dfsWalker{Iterator: iterator} 30 | } 31 | 32 | type dfsWalker struct { 33 | Iterator 34 | } 35 | 36 | func (w dfsWalker) Walk(value interface{}, f func(el Pair) error) error { 37 | root := &pair{ 38 | key: nil, 39 | val: value, 40 | pnt: nil, 41 | err: nil, 42 | } 43 | return w.walk(root, f) 44 | } 45 | 46 | func (w dfsWalker) walk(el Pair, f func(Pair) error) error { 47 | in := reflect.ValueOf(indirect(el.Val())) 48 | 49 | switch in.Kind() { 50 | case reflect.Slice, reflect.Array: 51 | return w.IterSlice(in, w.seqVisitFunc(el, f)) 52 | case reflect.Struct: 53 | return w.IterStruct(in, w.structVisitFunc(el, f)) 54 | case reflect.Chan: 55 | return w.IterChan(in, w.seqVisitFunc(el, f)) 56 | case reflect.Map: 57 | return w.IterMap(in, w.mapVisitFunc(el, f)) 58 | default: 59 | return f(el) 60 | } 61 | } 62 | 63 | type structVisitFn func(field reflect.StructField, value reflect.Value) error 64 | 65 | func (w dfsWalker) structVisitFunc(el Pair, f func(Pair) error) structVisitFn { 66 | return func(s reflect.StructField, v reflect.Value) error { 67 | if !v.IsValid() || !v.CanInterface() { 68 | return nil 69 | } 70 | return w.walk(&pair{s, v.Interface(), el, nil}, f) 71 | } 72 | } 73 | 74 | type seqVisitFunc func(idx int, value reflect.Value) error 75 | 76 | func (w dfsWalker) seqVisitFunc(el Pair, f func(Pair) error) seqVisitFunc { 77 | return func(idx int, v reflect.Value) error { 78 | if !v.IsValid() || !v.CanInterface() { 79 | return nil 80 | } 81 | return w.walk(&pair{idx, v.Interface(), el, nil}, f) 82 | } 83 | } 84 | 85 | type mapVisitFunc func(key, value reflect.Value) error 86 | 87 | func (w dfsWalker) mapVisitFunc(el Pair, f func(Pair) error) mapVisitFunc { 88 | return func(k, v reflect.Value) error { 89 | if !k.IsValid() || !v.IsValid() || 90 | !k.CanInterface() || !v.CanInterface() { 91 | return nil 92 | } 93 | return w.walk(&pair{k.Interface(), v.Interface(), el, nil}, f) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pair.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Pair represents a dyadic pair of values visited by a Walker. The root Pair 9 | // will have no Parent() and it is permissible for a Pair to contain only a Key 10 | // or Val. This means any of this interfaces types with nil zero values could be 11 | // nil regardless if Err() is non-nil. The Err() field may be populated by the 12 | // Walker or propagated from a user returned error. 13 | // 14 | // Caveats: 15 | // 16 | // Since channels do not have a sequential number to represent a location 17 | // within a finite space a simple numeric counter relative to the first 18 | // receive operation within the current block instead. This means that future 19 | // calls to the same channel could return a identical sequence number. 20 | // 21 | // Example of elements: 22 | // 23 | // []int{1, 2} -> []Pair{ {0, 1}, {1, 2} } 24 | // map[str]int{"a": 1, "b": 2} -> []Pair{ {"a", 1}, {"b", 2} } 25 | // 26 | // Structs: 27 | // 28 | // Key() will contain a reflect.StructFieldvalue, which may be anonymous or 29 | // unexported. 30 | // Val() will contain the associated fields current value. 31 | // 32 | // Slices, Arrays and Channels: 33 | // 34 | // Key() will contain a int type representing the elements location within the 35 | // sequence. 36 | // Val() will contain the element value located at Key(). 37 | // 38 | // Maps: 39 | // 40 | // Key() will be a interface value of the maps key used to access the Val(). 41 | // Val() will be a interface value of the value located at Key(). 42 | // 43 | type Pair interface { 44 | 45 | // Err will return any error associated with the retrieval of this Pair. 46 | Err() error 47 | 48 | // Depth returns how many structured elements this Pair is nested within. 49 | Depth() int 50 | 51 | // Parent will return the parent Pair this Pair is associated to. For 52 | // example if this Pair belongs to a map type, the parent would contain a 53 | // Pair with Kind() reflect.Map and a reflect.Value of the map Value. If 54 | // this is the top most element then it will have no Parent. 55 | Parent() Pair 56 | 57 | // Key will return the key associated with this value. 58 | Key() interface{} 59 | 60 | // Val will return the value associated with this key. 61 | Val() interface{} 62 | 63 | // Pair returns the key and value for this Pair. 64 | Pair() (interface{}, interface{}) 65 | } 66 | 67 | type pair struct { 68 | key interface{} 69 | val interface{} 70 | pnt Pair 71 | err error 72 | } 73 | 74 | // NewPair returns a new key-value pair to be used by Walkers. 75 | func NewPair(parent Pair, key, value interface{}, err error) Pair { 76 | return &pair{ 77 | key: key, 78 | val: value, 79 | pnt: parent, 80 | err: err, 81 | } 82 | } 83 | 84 | func (pr *pair) Err() error { 85 | return pr.err 86 | } 87 | 88 | func (pr *pair) Depth() int { 89 | if pr.pnt == nil { 90 | return 0 91 | } 92 | return 1 + pr.pnt.Depth() 93 | } 94 | 95 | func (pr *pair) Parent() Pair { 96 | return pr.pnt 97 | } 98 | 99 | func (pr *pair) Key() interface{} { 100 | return pr.key 101 | } 102 | 103 | func (pr *pair) Val() interface{} { 104 | return pr.val 105 | } 106 | 107 | func (pr *pair) Pair() (key, val interface{}) { 108 | return pr.key, pr.val 109 | } 110 | 111 | func (pr *pair) String() string { 112 | var k string 113 | if v, ok := pr.key.(reflect.StructField); ok { 114 | k = v.Name 115 | } else { 116 | k = fmt.Sprintf("%v", pr.key) 117 | } 118 | return fmt.Sprintf("Pair{(%T) %.12s => %.12s (%T)}", 119 | pr.key, k, fmt.Sprintf("%v", pr.val), pr.val) 120 | } 121 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package iter_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | 10 | iter "github.com/cstockton/go-iter" 11 | ) 12 | 13 | func Example() { 14 | 15 | // Iter will walk any structured type, chans, slices, maps, structs. 16 | type Package struct { 17 | Name string 18 | Synopsis string 19 | 20 | // Iter ascends into child structured types as well. 21 | Parent *Package 22 | } 23 | 24 | // Iter will visit each key -> value pair. For structs the key will be the 25 | // reflect.StructField, for maps the element key and the sequence integer for 26 | // arrays and, slices. 27 | pkgs := []Package{ 28 | {"io", "Package io provides basic interfaces to I/O primitives.", &Package{ 29 | "ioutil", "Package ioutil implements some I/O utility functions.", nil}}, 30 | {"hash", "Package hash provides interfaces for hash functions.", nil}, 31 | {"flag", "Package flag implements command-line flag parsing.", nil}} 32 | 33 | fmt.Println("Packages:") 34 | iter.Walk(pkgs, func(el iter.Pair) error { 35 | fmt.Printf(" %v\n", el) 36 | return nil 37 | }) 38 | 39 | // Output: 40 | // Packages: 41 | // Pair{(reflect.StructField) Name => io (string)} 42 | // Pair{(reflect.StructField) Synopsis => Package io p (string)} 43 | // Pair{(reflect.StructField) Name => ioutil (string)} 44 | // Pair{(reflect.StructField) Synopsis => Package iout (string)} 45 | // Pair{(reflect.StructField) Parent => (*iter_test.Package)} 46 | // Pair{(reflect.StructField) Name => hash (string)} 47 | // Pair{(reflect.StructField) Synopsis => Package hash (string)} 48 | // Pair{(reflect.StructField) Parent => (*iter_test.Package)} 49 | // Pair{(reflect.StructField) Name => flag (string)} 50 | // Pair{(reflect.StructField) Synopsis => Package flag (string)} 51 | // Pair{(reflect.StructField) Parent => (*iter_test.Package)} 52 | } 53 | 54 | func ExampleWalk() { 55 | 56 | var res []string 57 | m := map[int]string{1: "a", 2: "b", 3: "c"} 58 | 59 | err := iter.Walk(m, func(el iter.Pair) error { 60 | res = append(res, fmt.Sprintf("%v", el)) 61 | return nil 62 | }) 63 | if err != nil { 64 | fmt.Println(err) 65 | } 66 | 67 | sort.Strings(res) // for test determinism 68 | for _, v := range res { 69 | fmt.Println(v) 70 | } 71 | 72 | // Output: 73 | // Pair{(int) 1 => a (string)} 74 | // Pair{(int) 2 => b (string)} 75 | // Pair{(int) 3 => c (string)} 76 | } 77 | 78 | func ExampleWalk_errors() { 79 | 80 | v := []interface{}{"a", "b", []string{"c", "d"}} 81 | err := iter.Walk(v, func(el iter.Pair) error { 82 | // check for errors 83 | if err := el.Err(); err != nil { 84 | return err 85 | } 86 | 87 | // Halt iteration by returning an error. 88 | if el.Depth() > 1 { 89 | return errors.New("Stopping this walk.") 90 | } 91 | 92 | fmt.Println(el) 93 | return nil 94 | }) 95 | if err == nil { 96 | fmt.Println("expected an error") 97 | } 98 | 99 | // Output: 100 | // Pair{(int) 0 => a (string)} 101 | // Pair{(int) 1 => b (string)} 102 | } 103 | 104 | func Example_recursion() { 105 | 106 | type exampleWalk struct { 107 | Head string 108 | Child *exampleWalk 109 | Tail string 110 | } 111 | trnew := func(pnt *exampleWalk, i int) *exampleWalk { 112 | tr := &exampleWalk{ 113 | Head: fmt.Sprintf("tail #%d", i), 114 | Child: pnt, 115 | Tail: fmt.Sprintf("tail #%d", i), 116 | } 117 | return tr 118 | } 119 | tr := trnew(nil, 4) 120 | for i := 3; i > 0; i-- { 121 | tr = trnew(tr, i) 122 | } 123 | 124 | w := iter.NewWalker(&iter.Iter{ChanRecv: true}) 125 | err := w.Walk(tr, func(el iter.Pair) error { 126 | pad := strings.Repeat(" ", el.Depth()) 127 | k, v := el.Pair() 128 | if sf, ok := k.(reflect.StructField); ok { 129 | fmt.Printf("%v%v -> %v\n", pad, sf.Name, v) 130 | } else { 131 | fmt.Printf("%v%v -> %v\n", pad, k, v) 132 | } 133 | return nil 134 | }) 135 | if err != nil { 136 | fmt.Println(err) 137 | } 138 | 139 | // Output: 140 | // Head -> tail #1 141 | // Head -> tail #2 142 | // Head -> tail #3 143 | // Head -> tail #4 144 | // Child -> 145 | // Tail -> tail #4 146 | // Tail -> tail #3 147 | // Tail -> tail #2 148 | // Tail -> tail #1 149 | 150 | } 151 | -------------------------------------------------------------------------------- /walk_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "bytes" 5 | "container/ring" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | type TestTree struct { 12 | At int 13 | Children []*TestTree 14 | } 15 | 16 | func (t *TestTree) Walk(parent Pair, f func(el Pair) error) error { 17 | return f(NewPair(parent, "foo", "bar", nil)) 18 | } 19 | 20 | func newTestTree() *TestTree { 21 | return new(TestTree).fill(0, 4, 4) 22 | } 23 | 24 | func (t *TestTree) Dump() string { 25 | var buf bytes.Buffer 26 | space := strings.Repeat(" ", t.At) 27 | buf.WriteString(fmt.Sprintf("%sTree(%d)\n", space, t.At)) 28 | for _, c := range t.Children { 29 | buf.WriteString(fmt.Sprintf("%s%v", space, c.Dump())) 30 | } 31 | return buf.String() 32 | } 33 | 34 | func (t *TestTree) String() string { 35 | return fmt.Sprintf("Tree(%d)\n", t.At) 36 | } 37 | 38 | func (t *TestTree) fill(d, c, max int) *TestTree { 39 | t.At = d 40 | d++ 41 | if d >= max { 42 | return t 43 | } 44 | t.Children = make([]*TestTree, c) 45 | for i := 0; i < c; i++ { 46 | t.Children[i] = new(TestTree).fill(d, c, max) 47 | } 48 | return t 49 | } 50 | 51 | func TestDfsWalker(t *testing.T) { 52 | type testDfsWalker struct { 53 | Head string 54 | Child *testDfsWalker 55 | MapEnter string 56 | Map map[string]int 57 | SliceEnter string 58 | Slice []string 59 | ChanEnter string 60 | Chan chan string 61 | Tail string 62 | } 63 | tid := func(ident string, depth int) string { 64 | return fmt.Sprintf("%s|%d", ident, depth) 65 | } 66 | trnew := func(pnt *testDfsWalker, i int) *testDfsWalker { 67 | tr := &testDfsWalker{ 68 | Head: tid("Head", i), 69 | Child: pnt, 70 | MapEnter: tid("MapEnter|1", i), 71 | Map: map[string]int{ 72 | tid("Map", i): i * 2, 73 | tid("Map", i+1): i * 2, 74 | tid("Map", i+2): i * 2}, 75 | SliceEnter: tid("SliceEnter", i), 76 | Slice: []string{tid("Slice", i*10), tid("Slice", i*20), tid("Slice", i*30)}, 77 | ChanEnter: tid("ChanEnter", i), 78 | Chan: make(chan string, 3), 79 | Tail: tid("Tail", i), 80 | } 81 | tr.Chan <- tid("Chan", i*10) 82 | tr.Chan <- tid("Chan", i*20) 83 | tr.Chan <- tid("Chan", i*30) 84 | return tr 85 | } 86 | tr := trnew(nil, 4) 87 | for i := 3; i > 0; i-- { 88 | tr = trnew(tr, i) 89 | } 90 | 91 | w := NewWalker(&Iter{ChanRecv: true}) 92 | t.Run("Walk", func(t *testing.T) { 93 | fns := []func(value interface{}, f func(el Pair) error) error{ 94 | Walk, w.Walk, 95 | } 96 | for _, fn := range fns { 97 | err := fn(tr, func(el Pair) error { 98 | return nil 99 | }) 100 | if err != nil { 101 | t.Fatalf("expected nil err, got: %v", err) 102 | } 103 | } 104 | }) 105 | 106 | t.Run("NoCircular", func(t *testing.T) { 107 | fns := []func(value interface{}, f func(el Pair) error) error{ 108 | Walk, w.Walk, 109 | } 110 | for _, fn := range fns { 111 | i := 0 112 | err := fn(ring.New(10), func(el Pair) error { 113 | i++ 114 | return nil 115 | }) 116 | if err != nil { 117 | t.Fatalf("expected nil err, got: %v", err) 118 | } 119 | if i != 1 { 120 | t.Fatalf("expected exactly 1 visit, got: %v", i) 121 | } 122 | } 123 | }) 124 | 125 | t.Run("NilChan", func(t *testing.T) { 126 | type privateString string 127 | w := NewWalker(invalidIter{}) 128 | i := 0 129 | ch := make(chan string, 1) 130 | ch <- "foo" 131 | 132 | err := w.Walk(ch, func(el Pair) error { 133 | i++ 134 | return nil 135 | }) 136 | if err != nil { 137 | t.Fatalf("expected nil err, got: %v", err) 138 | } 139 | err = w.Walk(map[int]string{1: "123"}, func(el Pair) error { 140 | i++ 141 | return nil 142 | }) 143 | if err != nil { 144 | t.Fatalf("expected nil err, got: %v", err) 145 | } 146 | err = w.Walk([]string{"123"}, func(el Pair) error { 147 | i++ 148 | return nil 149 | }) 150 | if err != nil { 151 | t.Fatalf("expected nil err, got: %v", err) 152 | } 153 | err = w.Walk(struct{ name string }{"123"}, func(el Pair) error { 154 | i++ 155 | return nil 156 | }) 157 | if err != nil { 158 | t.Fatalf("expected nil err, got: %v", err) 159 | } 160 | if i != 0 { 161 | t.Fatalf("expected exactly 0 visits, got: %v", i) 162 | } 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /iter.go: -------------------------------------------------------------------------------- 1 | // Package iter provides primitives for walking arbitrary data structures. 2 | package iter 3 | 4 | import ( 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var ( 10 | defaultIter = Iter{} 11 | defaultWalker = dfsWalker{Iterator: &defaultIter} 12 | ) 13 | 14 | // Iterator is a basic interface for iterating elements of a structured type. It 15 | // serves as backing for other traversal methods. Iterators are safe for use by 16 | // multiple Go routines, though the underlying values received in the iteration 17 | // functions may not be. 18 | type Iterator interface { 19 | IterChan(val reflect.Value, f func(seq int, ch reflect.Value) error) error 20 | IterMap(val reflect.Value, f func(key, val reflect.Value) error) error 21 | IterSlice(val reflect.Value, f func(idx int, val reflect.Value) error) error 22 | IterStruct(val reflect.Value, 23 | f func(field reflect.StructField, val reflect.Value) error) error 24 | } 25 | 26 | // NewIter returns a new Iter. 27 | func NewIter() Iterator { 28 | return &Iter{} 29 | } 30 | 31 | // NewRecoverIter returns the given iterator wrapped so that it will not panic 32 | // under any circumstance, instead returning the panic as an error. 33 | func NewRecoverIter(it Iterator) Iterator { 34 | return &recoverIter{it} 35 | } 36 | 37 | // Iter it a basic implementation of Iterator. It is possible for these methods 38 | // to panic under certain circumstances. If you want to disable panics write it 39 | // in a iter.NewrecoverFnIter(Iter{}). Performance cost for Visiting slices is 40 | // negligible relative to iteration using range. About 3x slower for slices with 41 | // less than 1k elements, for slices with more than 2k elements it will be 42 | // around 2x as slow. Iterating maps is only 2-3 times slower for small maps of 43 | // 100-1k elements. When you start to go above that its deficiencies will start 44 | // to take a linear tax on runtime proportianite to the number of elements. It's 45 | // roughly 100x slower at 32k elements. This is because it loads all map keys 46 | // into memory (implementation of reflect.MapKeys). 47 | type Iter struct { 48 | ChanRecv bool 49 | ChanBlock bool 50 | ExcludeAnonymous bool 51 | ExcludeUnexported bool 52 | } 53 | 54 | // IterMap will visit each key and value of a map. 55 | func (it Iter) IterMap(val reflect.Value, f func( 56 | key, val reflect.Value) error) error { 57 | kind := val.Kind() 58 | if reflect.Map != kind { 59 | return fmt.Errorf("expected map kind, not %s", kind) 60 | } 61 | for _, key := range val.MapKeys() { 62 | element := val.MapIndex(key) 63 | if err := f(key, element); err != nil { 64 | return err 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | // IterSlice will visit each element of an array or slice. Extending the length 71 | // of an Array or Slice during iteration may panic. 72 | func (it Iter) IterSlice(val reflect.Value, f func( 73 | idx int, val reflect.Value) error) error { 74 | kind := val.Kind() 75 | if reflect.Slice != kind && kind != reflect.Array { 76 | return fmt.Errorf("expected array or slice kind, not %s", kind) 77 | } 78 | l := val.Len() 79 | for i := 0; i < l; i++ { 80 | element := val.Index(i) 81 | if err := f(i, element); err != nil { 82 | return err 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | // IterStruct will visit each field and value in a struct. 89 | func (it Iter) IterStruct(val reflect.Value, f func( 90 | field reflect.StructField, val reflect.Value) error) error { 91 | kind := val.Kind() 92 | if reflect.Struct != kind { 93 | return fmt.Errorf("expected struct kind, not %s", kind) 94 | } 95 | typ := val.Type() 96 | for i := 0; i < val.NumField(); i++ { 97 | field := typ.Field(i) 98 | if field.Anonymous && it.ExcludeAnonymous { 99 | continue 100 | } 101 | if len(field.PkgPath) > 0 && it.ExcludeUnexported { 102 | continue 103 | } 104 | element := val.Field(i) 105 | if err := f(field, element); err != nil { 106 | return err 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // IterChan will try to receive values from the given channel only if ChanRecv 113 | // is set to true. If ChanBlock is true IterChan will walk values until the 114 | // channel has been clocked, otherwise it will use the behavior described in 115 | // the reflect packages Value.TryRecv. This means when setting ChanBlock it is 116 | // up to the caller to close the channel to prevent a dead lock. A sequential 117 | // counter for this iterations receives is returned for parity with structured 118 | // types. 119 | func (it Iter) IterChan(val reflect.Value, f func( 120 | seq int, recv reflect.Value) error) error { 121 | if !it.ChanRecv { 122 | return nil 123 | } 124 | kind := val.Kind() 125 | if reflect.Chan != kind { 126 | return fmt.Errorf("expected chan kind, not %s", kind) 127 | } 128 | var ( 129 | recv reflect.Value 130 | ok bool 131 | ) 132 | i := -1 133 | for { 134 | if it.ChanBlock { 135 | recv, ok = val.Recv() 136 | } else { 137 | recv, ok = val.TryRecv() 138 | } 139 | if !ok { 140 | return nil 141 | } 142 | i++ 143 | if err := f(i, recv); err != nil { 144 | return err 145 | } 146 | } 147 | } 148 | 149 | type recoverIter struct { 150 | Iterator 151 | } 152 | 153 | func (it recoverIter) IterMap(val reflect.Value, f func( 154 | key, val reflect.Value) error) (err error) { 155 | return recoverFn(func() error { 156 | return it.Iterator.IterMap(val, f) 157 | }) 158 | } 159 | 160 | func (it recoverIter) IterSlice(val reflect.Value, f func( 161 | idx int, val reflect.Value) error) (err error) { 162 | return recoverFn(func() error { 163 | return it.Iterator.IterSlice(val, f) 164 | }) 165 | } 166 | 167 | func (it recoverIter) IterStruct(val reflect.Value, f func( 168 | field reflect.StructField, val reflect.Value) error) (err error) { 169 | return recoverFn(func() error { 170 | return it.Iterator.IterStruct(val, f) 171 | }) 172 | } 173 | 174 | func (it recoverIter) IterChan(val reflect.Value, f func( 175 | seq int, recv reflect.Value) error) (err error) { 176 | return recoverFn(func() error { 177 | return it.Iterator.IterChan(val, f) 178 | }) 179 | } 180 | -------------------------------------------------------------------------------- /iter_test.go: -------------------------------------------------------------------------------- 1 | package iter 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | type invalidIter struct { 13 | Iterator 14 | zeroValue reflect.Value 15 | zeroStructField reflect.StructField 16 | } 17 | 18 | func (it invalidIter) IterMap(val reflect.Value, f func(key, val reflect.Value) error) (err error) { 19 | return f(it.zeroValue, it.zeroValue) 20 | } 21 | 22 | func (it invalidIter) IterSlice(val reflect.Value, f func(idx int, val reflect.Value) error) (err error) { 23 | return f(0, it.zeroValue) 24 | } 25 | 26 | func (it invalidIter) IterStruct(val reflect.Value, f func(field reflect.StructField, val reflect.Value) error) (err error) { 27 | return f(it.zeroStructField, it.zeroValue) 28 | } 29 | 30 | func (it invalidIter) IterChan(val reflect.Value, f func(seq int, recv reflect.Value) error) (err error) { 31 | return f(0, it.zeroValue) 32 | } 33 | 34 | func tchkstr(t testing.TB, err error, errStr string) error { 35 | if err != nil { 36 | if len(errStr) == 0 { 37 | return err 38 | } 39 | if !strings.Contains(err.Error(), errStr) { 40 | return fmt.Errorf("error did not match:\n exp: %v\n got: %v", errStr, err) 41 | } 42 | } else if len(errStr) > 0 { 43 | return errors.New("expected non-nil err") 44 | } 45 | return nil 46 | } 47 | 48 | func kindLength(k reflect.Kind) bool { 49 | return reflect.Array == k || reflect.Chan == k || 50 | reflect.Map == k || reflect.Slice == k || 51 | reflect.String == k 52 | } 53 | 54 | func TestIteratorInterface(t *testing.T) { 55 | var _ Iterator = Iter{} 56 | var _ Iterator = (*Iter)(nil) 57 | var _ Iterator = recoverIter{} 58 | var _ Iterator = (*recoverIter)(nil) 59 | } 60 | 61 | func TestIterMap(t *testing.T) { 62 | type testIterMapResult struct{ key, val reflect.Value } 63 | type testIterMap struct { 64 | errStr string 65 | it Iterator 66 | give interface{} 67 | res []testIterMapResult 68 | f func(key, val reflect.Value) error 69 | } 70 | tests := make(map[string]*testIterMap) 71 | tnew := func(it Iterator, give interface{}, errStr string) *testIterMap { 72 | tc := &testIterMap{it: it, give: give, errStr: errStr} 73 | tc.f = func(key, val reflect.Value) error { 74 | tc.res = append(tc.res, testIterMapResult{key, val}) 75 | return nil 76 | } 77 | return tc 78 | } 79 | tadderr := func(name string, give interface{}, errStr string) { 80 | tests[name+"UsingIter"] = tnew(&Iter{}, give, errStr) 81 | tests[name+"UsingRecover"] = tnew(&recoverIter{&Iter{}}, give, errStr) 82 | } 83 | tadd := func(name string, give interface{}) { 84 | tadderr(name, give, ``) 85 | } 86 | 87 | tadd("StringInt", map[string]int{"a": 1, "b": 2, "c": 3}) 88 | tadd("IntString", map[int]string{1: "a", 2: "b", 3: "c"}) 89 | tadd("StringString", map[string]string{"a": "1", "b": "2", "c": "3"}) 90 | tadd("IntInt", map[int]int{1: 1, 2: 2, 3: 3}) 91 | tadd("NestedMap", map[string]map[int]int{"m1": {1: 2, 3: 4}, "m2": {3: 4, 5: 6}}) 92 | tadd("NestedSlice", map[string][]int{"m1": {1: 2, 3: 4}, "m2": {3: 4, 5: 6}}) 93 | tadd("Empty", make(map[int]int)) 94 | tadd("NilMap", (map[int]int)(nil)) 95 | 96 | tadderr("Nil", nil, "expected map kind, not invalid") 97 | tadderr("EmptyString", "", "expected map kind, not string") 98 | tadderr("WrongIterable", []string{"1", "2"}, "expected map kind, not slice") 99 | 100 | for name, tc := range tests { 101 | tc := tc 102 | t.Run(fmt.Sprintf("%v", name), func(t *testing.T) { 103 | val := reflect.ValueOf(tc.give) 104 | valKind := val.Kind() 105 | valLen := 0 106 | if kindLength(valKind) { 107 | valLen = val.Len() 108 | } else if valKind == reflect.Struct { 109 | valLen = val.NumField() 110 | } 111 | 112 | if err := tchkstr(t, tc.it.IterMap(val, tc.f), tc.errStr); err != nil { 113 | t.Fatal(err) 114 | } 115 | if len(tc.errStr) > 0 { 116 | return 117 | } 118 | 119 | if len(tc.res) != valLen { 120 | t.Fatalf("expected %d elements, got %d", valLen, len(tc.res)) 121 | } 122 | for _, r := range tc.res { 123 | if !r.key.IsValid() { 124 | t.Fatalf("invalid key in result: key(%v) val(%v)", r.key, r.val) 125 | } 126 | if !r.val.IsValid() { 127 | t.Fatalf("invalid val in result: key(%v) val(%v)", r.key, r.val) 128 | } 129 | if !r.val.CanInterface() { 130 | t.Fatalf("cant interface val in result: key(%v) val(%v)", r.key, r.val) 131 | } 132 | 133 | expVal := val.MapIndex(r.key) 134 | if !expVal.IsValid() { 135 | t.Fatalf("could not find validation key for result: key(%v) val(%v)", r.key, r.val) 136 | } 137 | if !expVal.CanInterface() { 138 | t.Fatalf("invalid validation value (%v) for result: key(%v) val(%v)", expVal, r.key, r.val) 139 | } 140 | 141 | exp := expVal.Interface() 142 | got := r.val.Interface() 143 | if !reflect.DeepEqual(exp, got) { 144 | t.Errorf("DeepEqual failed for key(%v):\n exp: %#v\n got: %#v", r.key, exp, got) 145 | } 146 | } 147 | 148 | if len(tc.res) > 0 { 149 | expErr := errors.New("propagate error") 150 | errf := func(key, val reflect.Value) error { 151 | return expErr 152 | } 153 | if err := tc.it.IterMap(val, errf); err != expErr { 154 | t.Error("error did not propagate") 155 | } 156 | } 157 | }) 158 | } 159 | } 160 | 161 | func TestIterSlice(t *testing.T) { 162 | type testIterSliceResult struct { 163 | idx int 164 | val reflect.Value 165 | } 166 | type testIterSlice struct { 167 | errStr string 168 | it Iterator 169 | give interface{} 170 | res []testIterSliceResult 171 | f func(idx int, val reflect.Value) error 172 | } 173 | tests := make(map[string]*testIterSlice) 174 | tnew := func(it Iterator, give interface{}, errStr string) *testIterSlice { 175 | tc := &testIterSlice{it: it, give: give, errStr: errStr} 176 | tc.f = func(idx int, val reflect.Value) error { 177 | tc.res = append(tc.res, testIterSliceResult{idx, val}) 178 | return nil 179 | } 180 | return tc 181 | } 182 | tadderr := func(name string, give interface{}, errStr string) { 183 | tests[name+"UsingIter"] = tnew(&Iter{}, give, errStr) 184 | tests[name+"UsingNewIter"] = tnew(NewIter(), give, errStr) 185 | tests[name+"UsingRecover"] = tnew(&recoverIter{&Iter{}}, give, errStr) 186 | tests[name+"UsingNewRecover"] = tnew(NewRecoverIter(NewIter()), give, errStr) 187 | } 188 | tadd := func(name string, give interface{}) { 189 | tadderr(name, give, ``) 190 | } 191 | 192 | tadd("Ints", []int{1, 2, 3}) 193 | tadd("IntsArr", [3]int{1, 2, 3}) 194 | tadd("Strings", []string{"a", "b", "c"}) 195 | tadd("StringsArr", [3]string{"a", "b", "c"}) 196 | tadd("StringsJagged", [][]string{{"a", "b", "c"}, {"d", "e", "f"}}) 197 | tadd("StringsArrJagged", [2][3]string{{"a", "b", "c"}, {"d", "e", "f"}}) 198 | 199 | tadd("Empty", []int{}) 200 | tadd("EmptyArr", [0]int{}) 201 | tadd("EmptyArr10", [10]int{}) 202 | tadd("NilSlice", ([]int)(nil)) 203 | 204 | tadderr("Nil", nil, "expected array or slice kind, not invalid") 205 | tadderr("EmptyString", "", "expected array or slice kind, not string") 206 | tadderr("WrongIterable", map[int]string{1: "1", 2: "2"}, "expected array or slice kind, not map") 207 | 208 | for name, tc := range tests { 209 | tc := tc 210 | t.Run(fmt.Sprintf("%v", name), func(t *testing.T) { 211 | val := reflect.ValueOf(tc.give) 212 | valKind := val.Kind() 213 | valLen := 0 214 | if kindLength(valKind) { 215 | valLen = val.Len() 216 | } else if valKind == reflect.Struct { 217 | valLen = val.NumField() 218 | } 219 | 220 | if err := tchkstr(t, tc.it.IterSlice(val, tc.f), tc.errStr); err != nil { 221 | t.Fatal(err) 222 | } 223 | if len(tc.errStr) > 0 { 224 | return 225 | } 226 | 227 | if len(tc.res) != valLen { 228 | t.Fatalf("expected %d elements, got %d", valLen, len(tc.res)) 229 | } 230 | for _, r := range tc.res { 231 | if !r.val.IsValid() { 232 | t.Fatalf("invalid val in result: idx(%v) val(%v)", r.idx, r.val) 233 | } 234 | if !r.val.CanInterface() { 235 | t.Fatalf("cant interface val in result: idx(%v) val(%v)", r.idx, r.val) 236 | } 237 | 238 | expVal := val.Index(r.idx) 239 | if !expVal.IsValid() { 240 | t.Fatalf("could not find validation element for result: idx(%v) val(%v)", r.idx, r.val) 241 | } 242 | if !expVal.CanInterface() { 243 | t.Fatalf("invalid validation value (%v) for result: idx(%v) val(%v)", expVal, r.idx, r.val) 244 | } 245 | 246 | exp := expVal.Interface() 247 | got := r.val.Interface() 248 | if !reflect.DeepEqual(exp, got) { 249 | t.Errorf("DeepEqual failed for index(%v):\n exp: %#v\n got: %#v", r.idx, exp, got) 250 | } 251 | } 252 | 253 | if len(tc.res) > 0 { 254 | expErr := errors.New("propagate error") 255 | errf := func(idx int, val reflect.Value) error { 256 | return expErr 257 | } 258 | if err := tc.it.IterSlice(val, errf); err != expErr { 259 | t.Error("error did not propagate") 260 | } 261 | } 262 | }) 263 | } 264 | } 265 | 266 | func TestIterStruct(t *testing.T) { 267 | type testIterStructResult struct { 268 | field reflect.StructField 269 | val reflect.Value 270 | } 271 | type testIterStructOpts struct { 272 | ExcludeAnonymous bool 273 | ExcludeUnexported bool 274 | } 275 | type testIterStruct struct { 276 | opts testIterStructOpts 277 | errStr string 278 | it Iterator 279 | give interface{} 280 | res []testIterStructResult 281 | f func(field reflect.StructField, val reflect.Value) error 282 | } 283 | tests := make(map[string]*testIterStruct) 284 | tnew := func(it Iterator, give interface{}, errStr string, opts testIterStructOpts) *testIterStruct { 285 | tc := &testIterStruct{it: it, give: give, errStr: errStr, opts: opts} 286 | tc.f = func(field reflect.StructField, val reflect.Value) error { 287 | tc.res = append(tc.res, testIterStructResult{field, val}) 288 | return nil 289 | } 290 | return tc 291 | } 292 | 293 | var it *Iter 294 | tadderr := func(name string, give interface{}, errStr string) { 295 | opts := testIterStructOpts{it.ExcludeAnonymous, it.ExcludeUnexported} 296 | tests[name+"UsingIter"] = tnew(it, give, errStr, opts) 297 | tests[name+"UsingRecover"] = tnew(&recoverIter{it}, give, errStr, opts) 298 | } 299 | tadd := func(name string, give interface{}) { 300 | tadderr(name, give, ``) 301 | } 302 | addtests := func() { 303 | tadd("Ints", struct{ A, B, C int }{1, 2, 3}) 304 | tadd("Mixed", struct { 305 | A, B int 306 | C string 307 | }{1, 2, "A"}) 308 | tadd("IntsField", struct { 309 | A int 310 | B string 311 | C []int 312 | }{1, "A", []int{2, 3}}) 313 | tadd("MapField", struct { 314 | A int 315 | B string 316 | C map[string]int 317 | }{1, "A", map[string]int{"B": 2, "C": 3}}) 318 | 319 | tadd("HeadUnexported", struct{ a, B, C int }{1, 2, 3}) 320 | tadd("MidUnexported", struct{ A, b, C int }{1, 2, 3}) 321 | tadd("TailUnexported", struct{ A, B, c int }{1, 2, 3}) 322 | tadd("AllUnexported", struct{ a, b, c int }{1, 2, 3}) 323 | 324 | var anonymousTest bytes.Buffer 325 | tadd("Anonymous", reflect.ValueOf(anonymousTest)) 326 | 327 | tadd("Empty", struct{}{}) 328 | tadderr("EmptyString", "", "expected struct kind, not string") 329 | tadderr("WrongIterable", map[int]string{1: "1", 2: "2"}, "expected struct kind, not map") 330 | } 331 | 332 | it = &Iter{ExcludeAnonymous: false, ExcludeUnexported: false} 333 | addtests() 334 | it = &Iter{ExcludeAnonymous: true, ExcludeUnexported: false} 335 | addtests() 336 | it = &Iter{ExcludeAnonymous: false, ExcludeUnexported: true} 337 | addtests() 338 | it = &Iter{ExcludeAnonymous: true, ExcludeUnexported: true} 339 | addtests() 340 | 341 | for name, tc := range tests { 342 | tc := tc 343 | s := fmt.Sprintf("%v/ExcludeAnonymous[%v]/ExcludeUnexported[%v]", 344 | name, tc.opts.ExcludeAnonymous, tc.opts.ExcludeUnexported) 345 | t.Run(s, func(t *testing.T) { 346 | val := reflect.ValueOf(tc.give) 347 | valKind := val.Kind() 348 | valLen := 0 349 | if kindLength(valKind) { 350 | valLen = val.Len() 351 | } else if valKind == reflect.Struct { 352 | typ := val.Type() 353 | for i := 0; i < val.NumField(); i++ { 354 | field := typ.Field(i) 355 | if field.Anonymous && tc.opts.ExcludeAnonymous { 356 | continue 357 | } 358 | if len(field.PkgPath) > 0 && tc.opts.ExcludeUnexported { 359 | continue 360 | } 361 | valLen++ 362 | } 363 | } 364 | 365 | if err := tchkstr(t, tc.it.IterStruct(val, tc.f), tc.errStr); err != nil { 366 | t.Fatal(err) 367 | } 368 | if len(tc.errStr) > 0 { 369 | return 370 | } 371 | 372 | if len(tc.res) != valLen { 373 | t.Fatalf("expected %d elements, got %d", valLen, len(tc.res)) 374 | } 375 | for _, r := range tc.res { 376 | if !r.val.IsValid() { 377 | t.Fatalf("invalid val in result: field(%v) val(%v)", r.field, r.val) 378 | } 379 | if !r.val.CanInterface() { 380 | // It's qualified by package path if unexported 381 | if len(r.field.PkgPath) > 0 && !tc.opts.ExcludeUnexported { 382 | t.Logf("qualified by pkg path %v as expected by ExcludeUnexported = false", r.field.PkgPath) 383 | continue 384 | } 385 | 386 | t.Fatalf("cant interface val in result: field(%v) val(%v)", r.field, r.val) 387 | } 388 | 389 | expVal := val.FieldByIndex(r.field.Index) 390 | if !expVal.IsValid() { 391 | t.Fatalf("could not find validation key for result: field(%v) val(%v)", r.field, r.val) 392 | } 393 | if !expVal.CanInterface() { 394 | t.Fatalf("invalid validation value (%v) for result: field(%v) val(%v)", expVal, r.field, r.val) 395 | } 396 | 397 | exp := expVal.Interface() 398 | got := r.val.Interface() 399 | if !reflect.DeepEqual(exp, got) { 400 | t.Errorf("DeepEqual failed for field(%v):\n exp: %#v\n got: %#v", r.field.Name, exp, got) 401 | } 402 | } 403 | 404 | if len(tc.res) > 0 { 405 | expErr := errors.New("propagate error") 406 | errf := func(field reflect.StructField, val reflect.Value) error { 407 | return expErr 408 | } 409 | if err := tc.it.IterStruct(val, errf); err != expErr { 410 | t.Error("error did not propagate") 411 | } 412 | } 413 | }) 414 | } 415 | } 416 | 417 | func TestIterChan(t *testing.T) { 418 | type testIterChanResult struct { 419 | val reflect.Value 420 | } 421 | type testIterChanOpts struct { 422 | ChanBlock bool 423 | ChanRecv bool 424 | } 425 | type testIterChan struct { 426 | opts testIterChanOpts 427 | errStr string 428 | it Iterator 429 | chf func() interface{} 430 | give interface{} 431 | res []testIterChanResult 432 | gof func(ch, give interface{}) 433 | f func(seq int, recv reflect.Value) error 434 | } 435 | tests := make(map[string]*testIterChan) 436 | tnew := func(it Iterator, chf func() interface{}, give interface{}, gof func(ch, give interface{}), errStr string, opts testIterChanOpts) *testIterChan { 437 | tc := &testIterChan{it: it, chf: chf, give: give, errStr: errStr, gof: gof, opts: opts} 438 | tc.f = func(seq int, recv reflect.Value) error { 439 | tc.res = append(tc.res, testIterChanResult{recv}) 440 | return nil 441 | } 442 | return tc 443 | } 444 | 445 | var it *Iter 446 | tadderr := func(name string, chf func() interface{}, give interface{}, gof func(ch, give interface{}), errStr string) { 447 | opts := testIterChanOpts{it.ChanBlock, it.ChanRecv} 448 | tests[name+"UsingIter"] = tnew(it, chf, give, gof, errStr, opts) 449 | tests[name+"UsingRecover"] = tnew(&recoverIter{it}, chf, give, gof, errStr, opts) 450 | } 451 | tadd := func(name string, chf func() interface{}, give interface{}, gof func(ch, give interface{})) { 452 | tadderr(name, chf, give, gof, ``) 453 | } 454 | 455 | it = &Iter{ChanBlock: false, ChanRecv: true} 456 | { 457 | give := []string{"A", "B", "C"} 458 | chf := func() interface{} { 459 | ch := make(chan string, 3) 460 | for _, v := range give { 461 | ch <- v 462 | } 463 | return ch 464 | } 465 | tadd("Strings", chf, give, func(ch, give interface{}) {}) 466 | } 467 | { 468 | give := []int{1, 2, 3} 469 | chf := func() interface{} { 470 | ch := make(chan int, 3) 471 | for _, v := range give { 472 | ch <- v 473 | } 474 | return ch 475 | } 476 | tadd("Ints", chf, give, func(ch, give interface{}) {}) 477 | } 478 | { 479 | type StructPayload struct{ A, B int } 480 | give := []StructPayload{{1, 2}, {3, 4}, {5, 6}} 481 | chf := func() interface{} { 482 | ch := make(chan StructPayload, 3) 483 | for _, v := range give { 484 | ch <- v 485 | } 486 | return ch 487 | } 488 | tadd("Structs", chf, give, func(ch, give interface{}) {}) 489 | } 490 | 491 | it = &Iter{ChanBlock: true, ChanRecv: true} 492 | { 493 | type StructPayload struct{ A, B int } 494 | give := []StructPayload{{1, 2}, {3, 4}, {5, 6}} 495 | chf := func() interface{} { 496 | return make(chan StructPayload) 497 | } 498 | gof := func(inch, give interface{}) { 499 | ch := inch.(chan StructPayload) 500 | for _, v := range give.([]StructPayload) { 501 | ch <- v 502 | } 503 | close(ch) 504 | } 505 | tadd("StructsBlock", chf, give, gof) 506 | } 507 | 508 | it = &Iter{ChanBlock: false, ChanRecv: true} 509 | { 510 | chf := func() interface{} { 511 | return "" 512 | } 513 | tadderr("NotChan", chf, nil, func(ch, give interface{}) {}, 514 | `expected chan kind, not string`) 515 | } 516 | { 517 | chf := func() interface{} { 518 | return []string{""} 519 | } 520 | tadderr("ChanIterable", chf, nil, func(ch, give interface{}) {}, 521 | `expected chan kind, not slice`) 522 | } 523 | { 524 | chf := func() interface{} { 525 | return nil 526 | } 527 | tadderr("NilChan", chf, nil, func(ch, give interface{}) {}, 528 | `expected chan kind, not invalid`) 529 | } 530 | { 531 | chf := func() interface{} { 532 | return func() {} 533 | } 534 | tadderr("NilChan", chf, nil, func(ch, give interface{}) {}, 535 | `expected chan kind, not func`) 536 | } 537 | 538 | t.Run("ChanReceive[false]", func(t *testing.T) { 539 | it = &Iter{} 540 | ch := make(chan string) 541 | it.IterChan(reflect.ValueOf(ch), func(seq int, val reflect.Value) error { 542 | return nil 543 | }) 544 | }) 545 | 546 | for name, tc := range tests { 547 | tc := tc 548 | s := fmt.Sprintf("%v/ChanBlock[%v]/ChanRecv[%v]", 549 | name, tc.opts.ChanBlock, tc.opts.ChanRecv) 550 | 551 | t.Run(s, func(t *testing.T) { 552 | val := reflect.ValueOf(tc.give) 553 | valKind := val.Kind() 554 | valLen := 0 555 | 556 | if kindLength(valKind) { 557 | valLen = val.Len() 558 | } else if valKind == reflect.Struct { 559 | valLen++ 560 | } 561 | 562 | ch := tc.chf() 563 | chv := reflect.ValueOf(ch) 564 | go tc.gof(ch, tc.give) 565 | 566 | if err := tchkstr(t, tc.it.IterChan(chv, tc.f), tc.errStr); err != nil { 567 | t.Fatal(err) 568 | } 569 | if len(tc.errStr) > 0 { 570 | return 571 | } 572 | 573 | if len(tc.res) != valLen { 574 | t.Fatalf("expected %d elements, got %d", valLen, len(tc.res)) 575 | } 576 | for i, r := range tc.res { 577 | if !r.val.IsValid() { 578 | t.Fatalf("invalid val in %T: val(%v)", ch, r.val) 579 | } 580 | if !r.val.CanInterface() { 581 | t.Fatalf("cant interface val in %T: val(%v)", ch, r.val) 582 | } 583 | 584 | expVal := val.Index(i) 585 | if !expVal.IsValid() { 586 | t.Fatalf("could not find validation element in %T: val(%v)", ch, r.val) 587 | } 588 | if !expVal.CanInterface() { 589 | t.Fatalf("invalid validation value (%v) in %T: val(%v)", expVal, ch, r.val) 590 | } 591 | 592 | exp := expVal.Interface() 593 | got := r.val.Interface() 594 | if !reflect.DeepEqual(exp, got) { 595 | t.Errorf("DeepEqual failed in %T:\n exp: %#v\n got: %#v", ch, exp, got) 596 | } 597 | } 598 | 599 | ch = tc.chf() 600 | chv = reflect.ValueOf(ch) 601 | go tc.gof(ch, tc.give) 602 | 603 | if len(tc.res) > 0 { 604 | expErr := errors.New("propagate error") 605 | errf := func(seq int, val reflect.Value) error { 606 | if seq == valLen-1 { // drain channel 607 | return expErr 608 | } 609 | return nil 610 | } 611 | if err := tc.it.IterChan(chv, errf); err != expErr { 612 | t.Error("error did not propagate") 613 | } 614 | } 615 | }) 616 | } 617 | } 618 | --------------------------------------------------------------------------------