Skip to content

Commit b76ded2

Browse files
committed
Add the paginator utility for retrieving Voice objects
1 parent dcc3e63 commit b76ded2

File tree

3 files changed

+153
-20
lines changed

3 files changed

+153
-20
lines changed

voice/callflow.go

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,12 @@ package voice
33
import (
44
"encoding/json"
55
"fmt"
6+
"reflect"
67
"time"
78

89
"github.com/messagebird/go-rest-api"
910
)
1011

11-
// CallFlowList is a single page from the total collection of call flows.
12-
type CallFlowList struct {
13-
Items []CallFlow `json:"data"`
14-
Pagination struct {
15-
TotalCount int `json:"totalCount"`
16-
PageCount int `json:"pageCount"`
17-
CurrentPage int `json:"currentPage"`
18-
PerPage int `json:"perPage"`
19-
} `json:"pagination"`
20-
}
21-
2212
// A CallFlow describes the flow of operations (steps) to be executed when
2313
// handling an incoming call.
2414
type CallFlow struct {
@@ -119,15 +109,9 @@ func CallFlowByID(client *messagebird.Client, id string) (*CallFlow, error) {
119109
return callflow, err
120110
}
121111

122-
// CallFlows lists the callflows on the specified page.
123-
//
124-
// Page indices start at 1.
125-
//
126-
// Typically, a page contains 10 callflows.
127-
func CallFlows(client *messagebird.Client, page int) (*CallFlowList, error) {
128-
list := &CallFlowList{}
129-
err := client.Request(list, "GET", fmt.Sprintf("call-flow/?page=%d", page), nil)
130-
return list, err
112+
// CallFlows returns a Paginator which iterates over all CallFlows.
113+
func CallFlows(client *messagebird.Client) *Paginator {
114+
return newPaginator(client, "call-flow/", reflect.TypeOf(CallFlow{}))
131115
}
132116

133117
// CreateCallFlow creates the callflow remotely.

voice/paginator.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package voice
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"reflect"
7+
8+
messagebird "github.com/messagebird/go-rest-api"
9+
)
10+
11+
// A Paginator is used to stream the contents of a collection of some type from
12+
// the MessageBird API.
13+
//
14+
// Paginators are single use and can therefore not be reset.
15+
type Paginator struct {
16+
endpoint string
17+
nextPage int
18+
structType reflect.Type
19+
client *messagebird.Client
20+
}
21+
22+
// newPaginator creates a new paginator.
23+
//
24+
// endpoint is called with the `page` query parameter until no more pages are
25+
// available.
26+
//
27+
// typ is the non-pointer type of a single element returned by a page.
28+
func newPaginator(client *messagebird.Client, endpoint string, typ reflect.Type) *Paginator {
29+
return &Paginator{
30+
endpoint: endpoint,
31+
nextPage: 1, // Page indices start at 1.
32+
structType: typ,
33+
client: client,
34+
}
35+
}
36+
37+
// NextPage queries the next page from the MessageBird API.
38+
//
39+
// The interface{} contains a slice of the type this paginator handles.
40+
//
41+
// When no more items are available, an empty slice and io.EOF are returned.
42+
// If another kind of error occurs, nil and and the error are returned.
43+
func (pag *Paginator) NextPage() (interface{}, error) {
44+
type pagination struct {
45+
TotalCount int `json:"totalCount"`
46+
PageCount int `json:"pageCount"`
47+
CurrentPage int `json:"currentPage"`
48+
PerPage int `json:"perPage"`
49+
}
50+
rawType := reflect.StructOf([]reflect.StructField{
51+
{
52+
Name: "Data",
53+
Type: reflect.SliceOf(pag.structType),
54+
Tag: "json:\"data\"",
55+
},
56+
{
57+
Name: "Pagination",
58+
Type: reflect.TypeOf(pagination{}),
59+
Tag: "json:\"pagination\"",
60+
},
61+
})
62+
rawVal := reflect.New(rawType)
63+
64+
if err := pag.client.Request(rawVal.Interface(), "GET", fmt.Sprintf("%s?page=%d", pag.endpoint, pag.nextPage), nil); err != nil {
65+
return nil, err
66+
}
67+
68+
data := rawVal.Elem().FieldByName("Data").Interface()
69+
pageInfo := rawVal.Elem().FieldByName("Pagination").Interface().(pagination)
70+
71+
// If no more items are available, a page with 0 elements is returned.
72+
if pag.nextPage > pageInfo.PageCount {
73+
return data, io.EOF
74+
}
75+
76+
pag.nextPage++
77+
return data, nil
78+
}
79+
80+
// Stream creates a channel which streams the contents of all remaining pages
81+
// ony by one.
82+
//
83+
// The Paginator is consumed in the process, meaning that after elements have
84+
// been received, NextPage will return EOF. It is invalid to mix calls to
85+
// NextPage an Stream, even after the stream channel was closed.
86+
//
87+
// If an error occurs, the next item sent over the channel will be an error
88+
// instead of a regular value. The channel is closed directly after this.
89+
func (pag *Paginator) Stream() <-chan interface{} {
90+
out := make(chan interface{})
91+
go func() {
92+
defer close(out)
93+
for {
94+
page, err := pag.NextPage()
95+
if err != nil {
96+
if err != io.EOF {
97+
out <- err
98+
}
99+
break
100+
}
101+
v := reflect.ValueOf(page)
102+
for i, l := 0, v.Len(); i < l; i++ {
103+
out <- v.Index(i).Interface()
104+
}
105+
}
106+
}()
107+
return out
108+
}

voice/paginator_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package voice
2+
3+
import (
4+
"net/http"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func TestPaginatorStream(t *testing.T) {
10+
type myStruct struct {
11+
Val int
12+
}
13+
mbClient, stop := testClient(http.StatusOK, []byte(`{
14+
"data": [
15+
{ "Val": 1 },
16+
{ "Val": 2 },
17+
{ "Val": 3 }
18+
],
19+
"pagination": {
20+
"totalCount": 3,
21+
"pageCount": 1,
22+
"currentPage": 1,
23+
"perPage": 10
24+
}
25+
}`))
26+
defer stop()
27+
28+
pag := newPaginator(mbClient, "", reflect.TypeOf(myStruct{}))
29+
30+
i := 0
31+
for val := range pag.Stream() {
32+
t.Logf("%d, %v", i+1, val)
33+
if v, ok := val.(myStruct); !ok || v.Val != i+1 {
34+
t.Fatalf("unexpected item at index %d: %#v", i, val)
35+
}
36+
i++
37+
}
38+
if i != 3 {
39+
t.Fatalf("unexpected number of elements: %d", i)
40+
}
41+
}

0 commit comments

Comments
 (0)