-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttp_errors.go
More file actions
174 lines (161 loc) · 4.69 KB
/
http_errors.go
File metadata and controls
174 lines (161 loc) · 4.69 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package goauth
import (
"errors"
"net/http"
"time"
)
// HTTPStatusForError maps typed goauth errors to an HTTP status code.
// Fallthrough defaults to 500 for unknown error types.
func HTTPStatusForError(err error) int {
if err == nil {
return http.StatusOK
}
// Specific sentinels first
switch {
case errors.Is(err, ErrMissingToken), errors.Is(err, ErrInvalidToken), errors.Is(err, ErrExpiredToken):
return http.StatusUnauthorized
case errors.Is(err, ErrTokenRevoked):
return http.StatusUnauthorized
case errors.Is(err, ErrTokenTypeMismatch):
return http.StatusUnauthorized
case errors.Is(err, ErrStrategyNotFound):
return http.StatusNotFound
case errors.Is(err, ErrSessionNotFound):
return http.StatusNotFound
case errors.Is(err, ErrTokenIssuerUnset), errors.Is(err, ErrKeyProviderUnset), errors.Is(err, ErrSessionStoreUnset):
return http.StatusInternalServerError
case errors.Is(err, ErrRateLimitExceeded):
return http.StatusTooManyRequests
}
// Category types next
var (
cred *CredentialError
tok *TokenError
cfg *ConfigError
nf *NotFoundError
inr *InternalError
rl *RateLimitError
val *ValidationError
sess *SessionError
)
switch {
case errors.As(err, &val):
return http.StatusBadRequest
case errors.As(err, &rl):
return http.StatusTooManyRequests
case errors.As(err, &cred):
return http.StatusUnauthorized
case errors.As(err, &tok):
return http.StatusUnauthorized
case errors.As(err, &sess):
return http.StatusUnauthorized
case errors.As(err, &cfg):
return http.StatusInternalServerError
case errors.As(err, &nf):
return http.StatusNotFound
case errors.As(err, &inr):
return http.StatusInternalServerError
default:
return http.StatusInternalServerError
}
}
// ErrorCodeForError returns a stable, client-facing error code string.
// Use alongside HTTPStatusForError to build consistent error responses.
func ErrorCodeForError(err error) string {
if err == nil {
return "ok"
}
// Specific sentinels first
switch {
case errors.Is(err, ErrMissingToken):
return "token_missing"
case errors.Is(err, ErrInvalidToken):
return "token_invalid"
case errors.Is(err, ErrExpiredToken):
return "token_expired"
case errors.Is(err, ErrTokenRevoked):
return "token_revoked"
case errors.Is(err, ErrTokenTypeMismatch):
return "token_type_mismatch"
case errors.Is(err, ErrStrategyNotFound):
return "strategy_not_found"
case errors.Is(err, ErrSessionNotFound):
return "session_not_found"
case errors.Is(err, ErrTokenIssuerUnset):
return "config_token_issuer_unset"
case errors.Is(err, ErrKeyProviderUnset):
return "config_key_provider_unset"
case errors.Is(err, ErrSessionStoreUnset):
return "config_session_store_unset"
case errors.Is(err, ErrRateLimitExceeded):
return "rate_limit_exceeded"
case errors.Is(err, ErrUserNotFound):
return "user_not_found"
}
// Category types next
var (
cred *CredentialError
tok *TokenError
cfg *ConfigError
nf *NotFoundError
inr *InternalError
rl *RateLimitError
val *ValidationError
sess *SessionError
)
switch {
case errors.As(err, &val):
return "validation_error"
case errors.As(err, &rl):
return "rate_limit_error"
case errors.As(err, &cred):
return "invalid_credentials"
case errors.As(err, &tok):
return "token_error"
case errors.As(err, &sess):
return "session_error"
case errors.As(err, &cfg):
return "config_error"
case errors.As(err, &nf):
return "not_found"
case errors.As(err, &inr):
return "internal_error"
default:
return "internal_error"
}
}
// ErrorResponse represents a structured HTTP error response
type ErrorResponse struct {
Status int `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
Fields map[string]string `json:"fields,omitempty"` // For validation errors
RetryAfter int `json:"retry_after,omitempty"` // For rate limit errors (seconds)
}
// ErrorResponseForError creates a structured error response from an error
func ErrorResponseForError(err error) ErrorResponse {
resp := ErrorResponse{
Status: HTTPStatusForError(err),
Code: ErrorCodeForError(err),
Message: err.Error(),
}
// Add extra fields for specific error types
var val *ValidationError
if errors.As(err, &val) && val.Fields != nil {
resp.Fields = val.Fields
}
var rl *RateLimitError
if errors.As(err, &rl) && rl.RetryAfter > 0 {
resp.RetryAfter = int(rl.RetryAfter / time.Second)
}
return resp
}
// RetryAfterForError returns the Retry-After header value for rate limit errors
// Returns 0 if the error is not a rate limit error
func RetryAfterForError(err error) time.Duration {
var rl *RateLimitError
if errors.As(err, &rl) {
return rl.RetryAfter
}
return 0
}