-
Notifications
You must be signed in to change notification settings - Fork 1
/
api_client_iter.go
144 lines (127 loc) · 2.9 KB
/
api_client_iter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//go:build go1.23
package flareio
import (
"bytes"
"encoding/json"
"fmt"
"io"
"iter"
"net/http"
"net/url"
)
// IterResult contains results for a given page.
type IterResult struct {
// Response associated with the fetched page.
//
// The response's body must be closed.
Response *http.Response
// Next is the token to be used to fetch the next page.
Next string
}
func getIterResult(
fetchPage func(from string) (*http.Response, error),
cursor string,
) (*IterResult, error) {
response, err := fetchPage(cursor)
if err != nil {
return nil, fmt.Errorf("failed to fetch next page: %w", err)
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"got http status code %d status while fetching next page: %s",
response.StatusCode,
body,
)
}
type ResponseWithNext struct {
Next string `json:"next"`
}
var responseWithNext ResponseWithNext
if err := json.Unmarshal(body, &responseWithNext); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
response.Body = io.NopCloser(bytes.NewReader(body))
iterResult := &IterResult{
Response: response,
Next: responseWithNext.Next,
}
return iterResult, nil
}
func createPagingIterator(
fetchPage func(from string) (*http.Response, error),
) iter.Seq2[*IterResult, error] {
cursor := ""
return func(yield func(*IterResult, error) bool) {
for {
iterResult, err := getIterResult(
fetchPage,
cursor,
)
if err != nil {
yield(nil, err)
return
}
cursor = iterResult.Next
if !yield(iterResult, err) {
return
}
if cursor == "" {
return
}
}
}
}
// IterGet allows to iterate over responses for an API endpoint that
// supports the Flare standard paging pattern.
func (client *ApiClient) IterGet(
path string,
params *url.Values,
) iter.Seq2[*IterResult, error] {
return createPagingIterator(
func(cursor string) (*http.Response, error) {
if cursor != "" {
if params == nil {
params = &url.Values{}
}
params.Set("from", cursor)
}
return client.Get(
path,
params,
)
},
)
}
// IterPostJson allows to iterate over responses for an API endpoint that
// supports the Flare standard paging pattern.
func (client *ApiClient) IterPostJson(
path string,
params *url.Values,
body map[string]interface{},
) iter.Seq2[*IterResult, error] {
return createPagingIterator(
func(cursor string) (*http.Response, error) {
if cursor != "" {
if body == nil {
body = make(map[string]interface{})
}
body["from"] = cursor
}
encodedJson, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal body to JSON: %w", err)
}
return client.Post(
path,
params,
"application/json",
bytes.NewReader(encodedJson),
)
},
)
}