-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
176 lines (144 loc) · 4.05 KB
/
client.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package pot
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"sync"
)
// Unique is an interface that is used to identify a model.
type Unique interface {
Key() string
}
// Client is a simple interface that calls the Pot API server.
// It is intended to be used in cases where the Pot Server runs
// separately and a go application wants to connect to it.
//
// Client is typed for a single model type, which is used to
// decode the response from the Pot API server.
type Client[T Unique] struct {
// BaseURL is the base URL of the Pot API server.
BaseURL string
// ownedPathGenerations caches the last generations for paths that were requested with the
// NoRewrite() option. This tracks the generation of objects to assert ownership of the
// current client.
ownedPathGenerations map[string]int64
// ownedPathGenerationsMux is a mutex that protects the ownedPathGenerations map.
ownedPathGenerationsMux sync.Mutex
// client is the HTTP client used to make requests to the Pot API server.
client *http.Client
}
// NewClient creates a new APIClient.
func NewClient[T Unique](baseURL string) *Client[T] {
if baseURL[len(baseURL)-1] != '/' {
baseURL += "/"
}
return &Client[T]{
BaseURL: baseURL,
ownedPathGenerations: map[string]int64{},
client: http.DefaultClient,
}
}
// ListPaths lists all available paths on the bucket
func (c *Client[T]) ListPaths(urlPath string) (*ListPathsResponse, error) {
respObj := ListPathsResponse{}
req, err := http.NewRequest(http.MethodGet, c.BaseURL+urlPath+":list", nil)
if err != nil {
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
if err := json.NewDecoder(resp.Body).Decode(&respObj); err != nil {
return nil, err
}
return &respObj, nil
}
// Get calls the GET method on the Pot API server.
func (c *Client[T]) Get(urlPath string) (map[string]T, error) {
content := map[string]T{}
resp, err := c.client.Get(c.BaseURL + urlPath)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&content); err != nil {
return nil, err
}
return content, nil
}
// Create calls the POST method on the Pot API server.
func (c *Client[T]) Create(urlPath string, obj []T, co ...CallOpt) (*CreateResponse, error) {
opts := &CallOpts{}
for _, opt := range co {
opt(opts)
}
content := map[string]T{}
for _, o := range obj {
content[o.Key()] = o
}
b, err := json.Marshal(content)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, c.BaseURL+urlPath, bytes.NewReader(b))
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Set("batch", "true")
if opts.norewrite {
q.Set("norewrite", opts.norewriteDuration.String())
if generation, ok := c.ownedPathGenerations[urlPath]; ok {
q.Set("generation", strconv.FormatInt(generation, 10))
}
}
req.URL.RawQuery = q.Encode()
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusLocked {
return nil, ErrNoRewriteViolated
}
if resp.StatusCode != http.StatusCreated {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
bodyString := string(bodyBytes)
return nil, fmt.Errorf("unexpected status code: %v, body: %s", resp.StatusCode, bodyString)
}
var respContent CreateResponse
if err := json.NewDecoder(resp.Body).Decode(&respContent); err != nil {
return nil, err
}
if respContent.Generation != 0 {
c.ownedPathGenerationsMux.Lock()
c.ownedPathGenerations[urlPath] = respContent.Generation
c.ownedPathGenerationsMux.Unlock()
}
return &respContent, nil
}
// Remove calls the DELETE method on the Pot API server.
func (c *Client[T]) Remove(urlPath string, keys ...string) error {
req, err := http.NewRequest(http.MethodDelete, c.BaseURL+urlPath, nil)
if err != nil {
return err
}
q := req.URL.Query()
for _, key := range keys {
q.Add("key", key)
}
req.URL.RawQuery = q.Encode()
resp, err := c.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}