Skip to content

Commit 7be4d65

Browse files
authored
feat: add paginated methods (#15)
1 parent 36d996e commit 7be4d65

File tree

8 files changed

+168
-24
lines changed

8 files changed

+168
-24
lines changed

domains.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,21 @@ func (s *DomainsService) Create(ctx context.Context, domainName string) (*Domain
6969
// GetAll listing domains.
7070
// https://desec.readthedocs.io/en/latest/dns/domains.html#listing-domains
7171
func (s *DomainsService) GetAll(ctx context.Context) ([]Domain, error) {
72-
return s.getAll(ctx, nil)
72+
domains, _, err := s.GetAllPaginated(ctx, "")
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
return domains, nil
78+
}
79+
80+
// GetAllPaginated listing domains.
81+
// https://desec.readthedocs.io/en/latest/dns/domains.html#listing-domains
82+
func (s *DomainsService) GetAllPaginated(ctx context.Context, cursor string) ([]Domain, *Cursors, error) {
83+
queryValues := url.Values{}
84+
queryValues.Set("cursor", cursor)
85+
86+
return s.getAll(ctx, queryValues)
7387
}
7488

7589
// GetResponsible returns the responsible domain for a given DNS query name.
@@ -78,7 +92,7 @@ func (s *DomainsService) GetResponsible(ctx context.Context, domainName string)
7892
queryValues := url.Values{}
7993
queryValues.Set("owns_qname", domainName)
8094

81-
domains, err := s.getAll(ctx, queryValues)
95+
domains, _, err := s.getAll(ctx, queryValues)
8296
if err != nil {
8397
return nil, err
8498
}
@@ -92,15 +106,15 @@ func (s *DomainsService) GetResponsible(ctx context.Context, domainName string)
92106

93107
// getAll listing domains.
94108
// https://desec.readthedocs.io/en/latest/dns/domains.html#listing-domains
95-
func (s *DomainsService) getAll(ctx context.Context, query url.Values) ([]Domain, error) {
109+
func (s *DomainsService) getAll(ctx context.Context, query url.Values) ([]Domain, *Cursors, error) {
96110
endpoint, err := s.client.createEndpoint("domains")
97111
if err != nil {
98-
return nil, fmt.Errorf("failed to create endpoint: %w", err)
112+
return nil, nil, fmt.Errorf("failed to create endpoint: %w", err)
99113
}
100114

101115
req, err := s.client.newRequest(ctx, http.MethodGet, endpoint, nil)
102116
if err != nil {
103-
return nil, err
117+
return nil, nil, err
104118
}
105119

106120
if len(query) > 0 {
@@ -109,22 +123,27 @@ func (s *DomainsService) getAll(ctx context.Context, query url.Values) ([]Domain
109123

110124
resp, err := s.client.httpClient.Do(req)
111125
if err != nil {
112-
return nil, fmt.Errorf("failed to call API: %w", err)
126+
return nil, nil, fmt.Errorf("failed to call API: %w", err)
113127
}
114128

115129
defer func() { _ = resp.Body.Close() }()
116130

117131
if resp.StatusCode != http.StatusOK {
118-
return nil, handleError(resp)
132+
return nil, nil, handleError(resp)
133+
}
134+
135+
cursors, err := parseCursor(resp.Header)
136+
if err != nil {
137+
return nil, nil, err
119138
}
120139

121140
var domains []Domain
122141
err = handleResponse(resp, &domains)
123142
if err != nil {
124-
return nil, err
143+
return nil, nil, err
125144
}
126145

127-
return domains, nil
146+
return domains, cursors, nil
128147
}
129148

130149
// Get retrieving a specific domain.

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module github.com/nrdcg/desec
22

3-
go 1.21
3+
go 1.22
44

55
require (
66
github.com/hashicorp/go-retryablehttp v0.7.7
7+
github.com/peterhellberg/link v1.2.0
78
github.com/stretchr/testify v1.10.0
89
)
910

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
1212
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
1313
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
1414
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
15+
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
16+
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
1517
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1618
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1719
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=

pagination.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package desec
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
7+
"github.com/peterhellberg/link"
8+
)
9+
10+
// Cursors allows to retrieve the next (or previous) page.
11+
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#pagination
12+
type Cursors struct {
13+
First string
14+
Prev string
15+
Next string
16+
}
17+
18+
func parseCursor(h http.Header) (*Cursors, error) {
19+
links := link.ParseHeader(h)
20+
21+
c := &Cursors{}
22+
23+
for s, l := range links {
24+
uri, err := url.ParseRequestURI(l.URI)
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
query := uri.Query()
30+
31+
switch s {
32+
case "first":
33+
c.First = query.Get("cursor")
34+
case "prev":
35+
c.Prev = query.Get("cursor")
36+
case "next":
37+
c.Next = query.Get("cursor")
38+
}
39+
}
40+
41+
return c, nil
42+
}

pagination_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package desec
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func Test_parseCursor(t *testing.T) {
11+
testCases := []struct {
12+
desc string
13+
header string
14+
expected *Cursors
15+
}{
16+
{
17+
desc: "all cursors",
18+
header: `<https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=>; rel="first", <https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:prev_cursor>; rel="prev", <https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:next_cursor>; rel="next"`,
19+
expected: &Cursors{First: "", Prev: ":prev_cursor", Next: ":next_cursor"},
20+
},
21+
{
22+
desc: "first page",
23+
header: `<https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=>; rel="first", <https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:next_cursor>; rel="next"`,
24+
expected: &Cursors{First: "", Prev: "", Next: ":next_cursor"},
25+
},
26+
{
27+
desc: "last page",
28+
header: `<https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=>; rel="first", <https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:prev_cursor>; rel="prev"`,
29+
expected: &Cursors{First: "", Prev: ":prev_cursor", Next: ""},
30+
},
31+
{
32+
desc: "empty",
33+
header: ``,
34+
expected: &Cursors{First: "", Prev: "", Next: ""},
35+
},
36+
}
37+
38+
for _, test := range testCases {
39+
t.Run(test.desc, func(t *testing.T) {
40+
t.Parallel()
41+
42+
h := http.Header{}
43+
h.Set("Link", test.header)
44+
45+
cursor, err := parseCursor(h)
46+
require.NoError(t, err)
47+
48+
require.Equal(t, test.expected, cursor)
49+
})
50+
}
51+
}

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func main() {
7777
panic(err)
7878
}
7979

80-
domains, err := client.Domains.GetAll(context.Background())
80+
domains, err := client.Domains.GetAllPagined(context.Background())
8181
if err != nil {
8282
panic(err)
8383
}

records.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7+
"net/url"
78
"time"
89
)
910

@@ -62,48 +63,77 @@ type RecordsService struct {
6263
// GetAll retrieving all RRSets in a zone.
6364
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#retrieving-all-rrsets-in-a-zone
6465
func (s *RecordsService) GetAll(ctx context.Context, domainName string, filter *RRSetFilter) ([]RRSet, error) {
65-
endpoint, err := s.client.createEndpoint("domains", domainName, "rrsets")
66+
rrSets, _, err := s.GetAllPaginated(ctx, domainName, filter, "")
6667
if err != nil {
67-
return nil, fmt.Errorf("failed to create endpoint: %w", err)
68+
return nil, err
6869
}
6970

70-
if filter != nil {
71-
query := endpoint.Query()
71+
return rrSets, nil
72+
}
73+
74+
// GetAllPaginated retrieving all RRSets in a zone.
75+
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#retrieving-all-rrsets-in-a-zone
76+
func (s *RecordsService) GetAllPaginated(ctx context.Context, domainName string, filter *RRSetFilter, cursor string) ([]RRSet, *Cursors, error) {
77+
queryValues := url.Values{}
7278

79+
if filter != nil {
7380
if filter.Type != IgnoreFilter {
74-
query.Set("type", filter.Type)
81+
queryValues.Set("type", filter.Type)
7582
}
7683

7784
if filter.SubName != IgnoreFilter {
78-
query.Set("subname", filter.SubName)
85+
queryValues.Set("subname", filter.SubName)
7986
}
87+
}
88+
89+
queryValues.Set("cursor", cursor)
90+
91+
rrSets, cursors, err := s.getAll(ctx, domainName, queryValues)
92+
if err != nil {
93+
return nil, nil, err
94+
}
8095

81-
endpoint.RawQuery = query.Encode()
96+
return rrSets, cursors, nil
97+
}
98+
99+
func (s *RecordsService) getAll(ctx context.Context, domainName string, query url.Values) ([]RRSet, *Cursors, error) {
100+
endpoint, err := s.client.createEndpoint("domains", domainName, "rrsets")
101+
if err != nil {
102+
return nil, nil, fmt.Errorf("failed to create endpoint: %w", err)
82103
}
83104

84105
req, err := s.client.newRequest(ctx, http.MethodGet, endpoint, nil)
85106
if err != nil {
86-
return nil, err
107+
return nil, nil, err
108+
}
109+
110+
if len(query) > 0 {
111+
req.URL.RawQuery = query.Encode()
87112
}
88113

89114
resp, err := s.client.httpClient.Do(req)
90115
if err != nil {
91-
return nil, fmt.Errorf("failed to call API: %w", err)
116+
return nil, nil, fmt.Errorf("failed to call API: %w", err)
92117
}
93118

94119
defer func() { _ = resp.Body.Close() }()
95120

96121
if resp.StatusCode != http.StatusOK {
97-
return nil, handleError(resp)
122+
return nil, nil, handleError(resp)
123+
}
124+
125+
cursors, err := parseCursor(resp.Header)
126+
if err != nil {
127+
return nil, nil, err
98128
}
99129

100130
var rrSets []RRSet
101131
err = handleResponse(resp, &rrSets)
102132
if err != nil {
103-
return nil, err
133+
return nil, nil, err
104134
}
105135

106-
return rrSets, nil
136+
return rrSets, cursors, nil
107137
}
108138

109139
// Create creates a new RRSet.

token_policies_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ func TestTokenPoliciesService_Create(t *testing.T) {
146146
}
147147

148148
for _, test := range testCases {
149-
test := test
150149
t.Run(test.desc, func(t *testing.T) {
151150
t.Parallel()
152151

0 commit comments

Comments
 (0)