-
Notifications
You must be signed in to change notification settings - Fork 5
/
errors.go
139 lines (112 loc) · 3.94 KB
/
errors.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
package httputil
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/bir/iken/errs"
"github.com/bir/iken/logctx"
"github.com/bir/iken/validation"
)
// ErrorHandlerFunc is useful to standardize the exception management of
// requests.
type ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
// StatusContextCancelled - reported when the context is cancelled. Most likely caused by lost connections.
const StatusContextCancelled = 499
type ClientValidationError struct {
Code int `json:"code,omitempty"`
Message string `json:"message"`
Fields map[string][]string `json:"fields,omitempty"`
}
// CustomResponseError - respond with a custom status Code and optional Body.
// content-type is text/plain if Body is a string, otherwise application/json is used.
type CustomResponseError struct {
Code int
Body any
Source error
}
func (e CustomResponseError) Error() string {
if e.Source != nil {
return e.Source.Error()
}
return http.StatusText(e.Code)
}
// ErrorHandler provides some standard handling for errors in an http request
// flow.
//
// Maps:
// json.SyntaxError to "BadRequest", body is the JSON string of the error
// message.
// validation.Errors to "BadRequest", body is the JSON of the error
// object (map of field name to list of errors).
// AuthError to "Forbidden" or "Unauthorized" as defined by the err instance. In addition
// ErrBasicAuthenticate issues a basic auth challenge using default realm of "Restricted".
// To override handle in your custom error handlers instead.
//
// Unhandled errors are added to the ctx and return "Internal Server Error" with
// the request ID to aid with troubleshooting.
func ErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
}
logctx.AddStrToContext(r.Context(), LogErrorMessage, err.Error())
if stack := errs.MarshalStack(err); stack != nil {
logctx.AddToContext(r.Context(), LogStack, stack)
}
var (
customErr CustomResponseError
validationErrs *validation.Errors
validationErr validation.Error
)
switch {
case errors.Is(err, context.Canceled):
http.Error(w, "canceled", StatusContextCancelled)
case errors.Is(err, ErrNotFound):
HTTPError(w, http.StatusNotFound)
case errors.Is(err, ErrForbidden):
HTTPError(w, http.StatusForbidden)
case errors.Is(err, ErrBasicAuthenticate):
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
HTTPError(w, http.StatusUnauthorized)
case errors.Is(err, ErrUnauthorized):
HTTPError(w, http.StatusUnauthorized)
case errors.As(err, &customErr):
if customErr.Body != nil {
if s, ok := customErr.Body.(string); ok {
http.Error(w, s, customErr.Code)
} else {
JSONWrite(w, r, customErr.Code, customErr.Body)
}
} else {
HTTPError(w, customErr.Code)
}
case errors.As(err, &validationErrs):
JSONWrite(w, r, http.StatusBadRequest,
ClientValidationError{http.StatusBadRequest, "validation errors", validationErrs.Fields()})
case errors.As(err, &validationErr):
JSONWrite(w, r, http.StatusBadRequest,
ClientValidationError{http.StatusBadRequest, validationErr.UserError(), nil})
default:
HTTPInternalServerError(w, r)
}
}
const (
// LogErrorMessage is used to report internal errors to the logging service.
LogErrorMessage = "error.message"
// LogStack is used to report available error stacks to logging.
LogStack = "error.stack"
RequestIDHeader = "X-Request-Id"
// InternalErrorFormat is the default error message returned for unhandled errors if the request ID is available.
InternalErrorFormat = "Internal Server Error: Request %q"
)
func HTTPInternalServerError(w http.ResponseWriter, r *http.Request) {
reqID := r.Header.Get(RequestIDHeader)
if reqID != "" {
http.Error(w, fmt.Sprintf(InternalErrorFormat, reqID), http.StatusInternalServerError)
} else {
HTTPError(w, http.StatusInternalServerError)
}
}
func HTTPError(w http.ResponseWriter, code int) {
http.Error(w, http.StatusText(code), code)
}