-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
149 lines (121 loc) · 3.76 KB
/
errors.go
File metadata and controls
149 lines (121 loc) · 3.76 KB
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
package publicdotcom
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
// APIError is the base error type for all Public.com API errors. More specific
// error types embed it: [AuthenticationError], [RateLimitError],
// [ValidationError], [NotFoundError], and [ServerError].
// Use errors.As to check for specific error types.
type APIError struct {
StatusCode int
Body string
Code string // API error code, if present (e.g. "user_api_auth.personal.invalid_secret")
}
func (e *APIError) Error() string {
if e.Code != "" {
return fmt.Sprintf("public.com API error %d (%s): %s", e.StatusCode, e.Code, e.Body)
}
return fmt.Sprintf("public.com API error %d: %s", e.StatusCode, e.Body)
}
// AuthenticationError is returned for 401 Unauthorized responses.
type AuthenticationError struct {
APIError
}
func (e *AuthenticationError) Error() string {
return fmt.Sprintf("authentication failed: %s", e.APIError.Error())
}
func (e *AuthenticationError) Unwrap() error { return &e.APIError }
// RateLimitError is returned for 429 Too Many Requests responses.
// RetryAfter indicates when the request can be retried, if the server
// provided a Retry-After header.
type RateLimitError struct {
APIError
RetryAfter time.Duration
}
func (e *RateLimitError) Error() string {
if e.RetryAfter > 0 {
return fmt.Sprintf("rate limited (retry after %s): %s", e.RetryAfter, e.APIError.Error())
}
return fmt.Sprintf("rate limited: %s", e.APIError.Error())
}
func (e *RateLimitError) Unwrap() error { return &e.APIError }
// ValidationError is returned for 400 Bad Request responses.
type ValidationError struct {
APIError
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed: %s", e.APIError.Error())
}
func (e *ValidationError) Unwrap() error { return &e.APIError }
// NotFoundError is returned for 404 Not Found responses.
type NotFoundError struct {
APIError
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("not found: %s", e.APIError.Error())
}
func (e *NotFoundError) Unwrap() error { return &e.APIError }
// ServerError is returned for 5xx responses.
type ServerError struct {
APIError
}
func (e *ServerError) Error() string {
return fmt.Sprintf("server error: %s", e.APIError.Error())
}
func (e *ServerError) Unwrap() error { return &e.APIError }
// readAPIError reads the response body and returns a typed error based on
// the HTTP status code.
func readAPIError(resp *http.Response) error {
body, _ := io.ReadAll(resp.Body)
bodyStr := string(body)
// Try to extract an error code from the response body.
var code string
var parsed struct {
Code string `json:"code"`
}
if json.Unmarshal(body, &parsed) == nil && parsed.Code != "" {
code = parsed.Code
}
base := APIError{
StatusCode: resp.StatusCode,
Body: bodyStr,
Code: code,
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return &AuthenticationError{APIError: base}
case resp.StatusCode == http.StatusTooManyRequests:
retryAfter := parseRetryAfter(resp.Header.Get("Retry-After"))
return &RateLimitError{APIError: base, RetryAfter: retryAfter}
case resp.StatusCode == http.StatusBadRequest:
return &ValidationError{APIError: base}
case resp.StatusCode == http.StatusNotFound:
return &NotFoundError{APIError: base}
case resp.StatusCode >= 500:
return &ServerError{APIError: base}
default:
return &base
}
}
// parseRetryAfter parses the Retry-After header value, which can be
// either a number of seconds or an HTTP-date.
func parseRetryAfter(value string) time.Duration {
if value == "" {
return 0
}
if secs, err := strconv.Atoi(value); err == nil {
return time.Duration(secs) * time.Second
}
if t, err := http.ParseTime(value); err == nil {
d := time.Until(t)
if d > 0 {
return d
}
}
return 0
}