Repository navigation
Expand file tree
/
Copy patherrors.go
More file actions
152 lines (143 loc) · 4.35 KB
/
Copy patherrors.go
File metadata and controls
152 lines (143 loc) · 4.35 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
150
151
152
package codeatlas
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
// Error codes of the API error envelope (plus the OAuth codes the token and
// registration endpoints return, such as "invalid_client").
const (
CodeBadRequest = "bad_request"
CodeConflict = "conflict"
CodeForbidden = "forbidden"
CodeInvalidClient = "invalid_client"
CodeInvalidToken = "invalid_token"
CodeMethodNotAllowed = "method_not_allowed"
CodeNotFound = "not_found"
CodePayloadTooLarge = "payload_too_large"
CodeRateLimited = "rate_limited"
CodeUnauthorized = "unauthorized"
CodeUnprocessable = "unprocessable"
CodeUnsupportedMedia = "unsupported_media_type"
)
// APIError is returned for every non-2xx response. It mirrors the API error
// envelope {"error": {"code", "message", "hint"}} and also understands OAuth
// errors (RFC 6749 / 7591).
type APIError struct {
// Status is the HTTP status code.
Status int
// Code is the stable machine-readable error code, e.g. "not_found".
Code string
// Message is the human-readable description.
Message string
// Hint says how to fix the request. Empty for OAuth errors.
Hint string
// RetryAfter is the Retry-After wait on a 429, zero otherwise.
RetryAfter time.Duration
// RateLimit is the rate-limit state sent with the failed response.
RateLimit RateLimit
}
func (e *APIError) Error() string {
s := fmt.Sprintf("codeatlas: %d %s: %s", e.Status, e.Code, e.Message)
if e.Hint != "" {
s += " (" + e.Hint + ")"
}
return s
}
func newAPIError(status int, body []byte) *APIError {
var env struct {
Error json.RawMessage `json:"error"`
ErrorDescription string `json:"error_description"`
}
e := &APIError{Status: status, Code: "unknown_error", Message: "HTTP " + strconv.Itoa(status)}
if json.Unmarshal(body, &env) != nil || len(env.Error) == 0 {
return e
}
var oauth string
if json.Unmarshal(env.Error, &oauth) == nil {
e.Code = oauth
e.Message = oauth
if env.ErrorDescription != "" {
e.Message = env.ErrorDescription
}
return e
}
var api struct {
Code string `json:"code"`
Message string `json:"message"`
Hint string `json:"hint"`
}
if json.Unmarshal(env.Error, &api) == nil {
if api.Code != "" {
e.Code = api.Code
}
if api.Message != "" {
e.Message = api.Message
}
e.Hint = api.Hint
}
return e
}
// RateLimit is the quota state from the IETF draft-ietf-httpapi-ratelimit-headers
// headers (RateLimit and RateLimit-Policy), falling back to the legacy
// X-RateLimit-* headers. The zero value means the response had none.
type RateLimit struct {
// Policy is the name of the policy that applies, e.g. "read".
Policy string
// Limit is the quota per window (RateLimit-Policy q, or X-RateLimit-Limit).
Limit int
// Window is the window length (RateLimit-Policy w); zero if unknown.
Window time.Duration
// Remaining is the requests left in the window.
Remaining int
// Reset is the time until the window resets.
Reset time.Duration
}
// parseRateLimit reads the rate-limit headers of a response.
func parseRateLimit(h http.Header) RateLimit {
var rl RateLimit
if v := h.Get("RateLimit-Policy"); v != "" {
name, params := parseStructured(v)
rl.Policy = name
rl.Limit = atoi(params["q"])
rl.Window = time.Duration(atoi(params["w"])) * time.Second
}
if v := h.Get("RateLimit"); v != "" {
name, params := parseStructured(v)
if rl.Policy == "" {
rl.Policy = name
}
rl.Remaining = atoi(params["r"])
rl.Reset = time.Duration(atoi(params["t"])) * time.Second
}
if rl.Limit == 0 {
rl.Limit = atoi(h.Get("X-RateLimit-Limit"))
}
if rl.Remaining == 0 && h.Get("RateLimit") == "" {
rl.Remaining = atoi(h.Get("X-RateLimit-Remaining"))
}
if rl.Reset == 0 && h.Get("RateLimit") == "" {
rl.Reset = time.Duration(atoi(h.Get("X-RateLimit-Reset"))) * time.Second
}
return rl
}
// parseStructured parses `"name";k=v;k2=v2`, the shape of the RateLimit headers.
func parseStructured(v string) (name string, params map[string]string) {
parts := strings.Split(v, ";")
name = strings.Trim(strings.TrimSpace(parts[0]), `"`)
params = make(map[string]string, len(parts)-1)
for _, p := range parts[1:] {
k, val, ok := strings.Cut(strings.TrimSpace(p), "=")
if ok {
params[k] = strings.Trim(val, `"`)
}
}
return name, params
}
func atoi(s string) int {
n, _ := strconv.Atoi(strings.TrimSpace(s))
return n
}