-
Notifications
You must be signed in to change notification settings - Fork 67
/
client.go
127 lines (105 loc) 路 2.9 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
package resourcemanager
import (
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
)
type Verb string
type client struct {
client *HTTPClient
resourceName string
resourceNamePlural string
deleteSuccessMsg string
tableConfig TableConfig
}
type HTTPClient struct {
client http.Client
baseURL string
extraHeaders http.Header
}
func NewHTTPClient(baseURL string, extraHeaders http.Header) *HTTPClient {
return &HTTPClient{
client: http.Client{
// this function avoids blindly followin redirects.
// the problem with redirects is that they don't guarantee to preserve the method, body, headers, etc.
// This can hide issues when developing, because the client will follow the redirect and the request
// will succeed, but the server will not receive the request that the user intended to send.
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
baseURL: baseURL,
extraHeaders: extraHeaders,
}
}
func (c HTTPClient) url(resourceName string, extra ...string) *url.URL {
urlStr := c.baseURL + path.Join("/api", resourceName, strings.Join(extra, "/"))
url, _ := url.Parse(urlStr)
return url
}
func (c HTTPClient) do(req *http.Request) (*http.Response, error) {
for k, v := range c.extraHeaders {
req.Header[k] = v
}
return c.client.Do(req)
}
type options func(c *client)
func WithDeleteEnabled(deleteSuccessMssg string) options {
return func(c *client) {
c.deleteSuccessMsg = deleteSuccessMssg
}
}
func WithTableConfig(tableConfig TableConfig) options {
return func(c *client) {
c.tableConfig = tableConfig
}
}
// NewClient creates a new client for a resource managed by the resourceamanger.
// The tableConfig parameter configures how the table view should be rendered.
// This configuration work both for a single resource from a Get, or a ResourceList from a List
func NewClient(
httpClient *HTTPClient,
resourceName, resourceNamePlural string,
opts ...options) client {
c := client{
client: httpClient,
resourceName: resourceName,
resourceNamePlural: resourceNamePlural,
}
for _, opt := range opts {
opt(&c)
}
return c
}
type requestError struct {
Code int `json:"code"`
Message string `json:"error"`
}
func (e requestError) Error() string {
return e.Message
}
func isSuccessResponse(resp *http.Response) bool {
// successfull http status codes are 2xx
return resp.StatusCode >= 200 && resp.StatusCode < 300
}
func parseRequestError(resp *http.Response, format Format) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("cannot read response body: %w", err)
}
if len(body) == 0 {
return requestError{
Code: resp.StatusCode,
Message: resp.Status,
}
}
var reqErr requestError
err = format.Unmarshal(body, &reqErr)
if err != nil {
return fmt.Errorf("cannot parse response body: %w", err)
}
return reqErr
}