Skip to content

Commit e981087

Browse files
committed
VERCEL: implement ratelimiter, refactor api client
1 parent 96ffaa5 commit e981087

File tree

5 files changed

+460
-298
lines changed

5 files changed

+460
-298
lines changed

providers/vercel/api.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package vercel
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
vercelClient "github.com/vercel/terraform-provider-vercel/client"
9+
)
10+
11+
// DnsRecord is a helper struct to unmarshal the JSON response.
12+
// It embeds vercelClient.DNSRecord to reuse the upstream type,
13+
// but adds fields to handle API inconsistencies (type vs recordType, mxPriority).
14+
type DnsRecord struct {
15+
vercelClient.DNSRecord
16+
Type string `json:"type"`
17+
// Normally MXPriority would be uint16 type, but since vercelClient.DNSRecord uses int64, we'd better be consistent here
18+
// Later in GetZoneRecords we do a `uint16OrZero` to ensure the type is correct
19+
MXPriority int64 `json:"mxPriority"`
20+
}
21+
22+
// pagination represents the pagination object in Vercel API responses.
23+
type pagination struct {
24+
Count int64 `json:"count"`
25+
Next *int64 `json:"next"`
26+
Prev *int64 `json:"prev"`
27+
}
28+
29+
// listResponse represents the response from the Vercel List DNS Records API.
30+
type listResponse struct {
31+
Records []DnsRecord `json:"records"`
32+
Pagination pagination `json:"pagination"`
33+
}
34+
35+
// Vercel API limit is max 100
36+
const vercelApiPaginationLimit = 100
37+
38+
// ListDNSRecords retrieves all DNS records for a domain, handling pagination.
39+
func (c *vercelProvider) ListDNSRecords(ctx context.Context, domain string) ([]DnsRecord, error) {
40+
var allRecords []DnsRecord
41+
var nextTimestamp int64
42+
43+
for {
44+
url := fmt.Sprintf("https://api.vercel.com/v4/domains/%s/records?limit=%d", domain, vercelApiPaginationLimit)
45+
if c.teamID != "" {
46+
url += fmt.Sprintf("&teamId=%s", c.teamID)
47+
}
48+
if nextTimestamp != 0 {
49+
url += fmt.Sprintf("&until=%d", nextTimestamp)
50+
}
51+
52+
var result listResponse
53+
err := c.doRequest(clientRequest{
54+
ctx: ctx,
55+
method: "GET",
56+
url: url,
57+
}, &result, c.listLimiter)
58+
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to list DNS records: %w", err)
61+
}
62+
63+
for _, r := range result.Records {
64+
// The official SDK expects 'recordType' but the API returns 'type'.
65+
// We explicitly map it here to fix the discrepancy.
66+
r.RecordType = r.Type
67+
// Ensure Domain field is set (it might not be in the record object itself)
68+
if r.Domain == "" {
69+
r.Domain = domain
70+
}
71+
if r.TeamID == "" {
72+
r.TeamID = c.teamID
73+
}
74+
75+
allRecords = append(allRecords, r)
76+
}
77+
78+
if result.Pagination.Next == nil {
79+
break
80+
}
81+
nextTimestamp = *result.Pagination.Next
82+
}
83+
84+
return allRecords, nil
85+
}
86+
87+
// httpsRecord structure for Vercel API
88+
type httpsRecord struct {
89+
Priority int64 `json:"priority"`
90+
Target string `json:"target"`
91+
Params string `json:"params,omitempty"`
92+
}
93+
94+
// createDNSRecordRequest embeds the official SDK request but adds HTTPS support
95+
type createDNSRecordRequest struct {
96+
vercelClient.CreateDNSRecordRequest
97+
HTTPS *httpsRecord `json:"https,omitempty"`
98+
}
99+
100+
// CreateDNSRecord creates a DNS record.
101+
func (c *vercelProvider) CreateDNSRecord(ctx context.Context, req createDNSRecordRequest) (*vercelClient.DNSRecord, error) {
102+
url := fmt.Sprintf("https://api.vercel.com/v4/domains/%s/records", req.Domain)
103+
if c.teamID != "" {
104+
url += fmt.Sprintf("?teamId=%s", c.teamID)
105+
}
106+
107+
var response struct {
108+
RecordID string `json:"uid"`
109+
}
110+
111+
payloadJSON, err := json.Marshal(req)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
err = c.doRequest(clientRequest{
117+
ctx: ctx,
118+
method: "POST",
119+
url: url,
120+
body: string(payloadJSON),
121+
}, &response, c.createLimiter)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
return &vercelClient.DNSRecord{ID: response.RecordID}, nil
127+
}
128+
129+
// updateDNSRecordRequest embeds the official SDK request but adds HTTPS support
130+
type updateDNSRecordRequest struct {
131+
vercelClient.UpdateDNSRecordRequest
132+
HTTPS *httpsRecord `json:"https,omitempty"`
133+
}
134+
135+
// UpdateDNSRecord updates a DNS record.
136+
func (c *vercelProvider) UpdateDNSRecord(ctx context.Context, recordID string, req updateDNSRecordRequest) (*vercelClient.DNSRecord, error) {
137+
url := fmt.Sprintf("https://api.vercel.com/v4/domains/records/%s", recordID)
138+
if c.teamID != "" {
139+
url += fmt.Sprintf("?teamId=%s", c.teamID)
140+
}
141+
142+
var result vercelClient.DNSRecord
143+
payloadJSON, err := json.Marshal(req)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
err = c.doRequest(clientRequest{
149+
ctx: ctx,
150+
method: "PATCH",
151+
url: url,
152+
body: string(payloadJSON),
153+
}, &result, c.updateLimiter)
154+
155+
return &result, err
156+
}
157+
158+
// DeleteDNSRecord deletes a DNS record.
159+
func (c *vercelProvider) DeleteDNSRecord(ctx context.Context, domain string, recordID string) error {
160+
url := fmt.Sprintf("https://api.vercel.com/v2/domains/%s/records/%s", domain, recordID)
161+
if c.teamID != "" {
162+
url += fmt.Sprintf("?teamId=%s", c.teamID)
163+
}
164+
165+
return c.doRequest(clientRequest{
166+
ctx: ctx,
167+
method: "DELETE",
168+
url: url,
169+
}, nil, c.deleteLimiter)
170+
}

providers/vercel/httpsRecord.go

Lines changed: 0 additions & 102 deletions
This file was deleted.

providers/vercel/listVercelDnsRecords.go

Lines changed: 0 additions & 90 deletions
This file was deleted.

0 commit comments

Comments
 (0)