├── 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 |
[](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 |
--------------------------------------------------------------------------------