-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
errors.go
186 lines (137 loc) · 5.72 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
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
175
176
177
178
179
180
181
182
183
184
185
186
package fuego
import (
"errors"
"fmt"
"log/slog"
"net/http"
)
// ErrorWithStatus is an interface that can be implemented by an error to provide
// a status code
type ErrorWithStatus interface {
error
StatusCode() int
}
// ErrorWithDetail is an interface that can be implemented by an error to provide
// an additional detail message about the error
type ErrorWithDetail interface {
error
DetailMsg() string
}
// HTTPError is the error response used by the serialization part of the framework.
type HTTPError struct {
// Developer readable error message. Not shown to the user to avoid security leaks.
Err error `json:"-" xml:"-"`
// URL of the error type. Can be used to lookup the error in a documentation
Type string `json:"type,omitempty" xml:"type,omitempty" description:"URL of the error type. Can be used to lookup the error in a documentation"`
// Short title of the error
Title string `json:"title,omitempty" xml:"title,omitempty" description:"Short title of the error"`
// HTTP status code. If using a different type than [HTTPError], for example [BadRequestError], this will be automatically overridden after Fuego error handling.
Status int `json:"status,omitempty" xml:"status,omitempty" description:"HTTP status code" example:"403"`
// Human readable error message
Detail string `json:"detail,omitempty" xml:"detail,omitempty" description:"Human readable error message"`
Instance string `json:"instance,omitempty" xml:"instance,omitempty"`
Errors []ErrorItem `json:"errors,omitempty" xml:"errors,omitempty"`
}
type ErrorItem struct {
Name string `json:"name" xml:"name" description:"For example, name of the parameter that caused the error"`
Reason string `json:"reason" xml:"reason" description:"Human readable error message"`
More map[string]any `json:"more,omitempty" xml:"more,omitempty" description:"Additional information about the error"`
}
func (e HTTPError) Error() string {
title := e.Title
code := e.StatusCode()
if title == "" {
title = http.StatusText(code)
if title == "" {
title = "HTTP Error"
}
}
return fmt.Sprintf("%d %s: %s", code, title, e.DetailMsg())
}
func (e HTTPError) StatusCode() int {
if e.Status == 0 {
return http.StatusInternalServerError
}
return e.Status
}
func (e HTTPError) DetailMsg() string {
return e.Detail
}
func (e HTTPError) Unwrap() error { return e.Err }
// BadRequestError is an error used to return a 400 status code.
type BadRequestError HTTPError
var _ ErrorWithStatus = BadRequestError{}
func (e BadRequestError) Error() string { return e.Err.Error() }
func (e BadRequestError) StatusCode() int { return http.StatusBadRequest }
func (e BadRequestError) Unwrap() error { return HTTPError(e) }
// NotFoundError is an error used to return a 404 status code.
type NotFoundError HTTPError
var _ ErrorWithStatus = NotFoundError{}
func (e NotFoundError) Error() string { return e.Err.Error() }
func (e NotFoundError) StatusCode() int { return http.StatusNotFound }
func (e NotFoundError) Unwrap() error { return HTTPError(e) }
// UnauthorizedError is an error used to return a 401 status code.
type UnauthorizedError HTTPError
var _ ErrorWithStatus = UnauthorizedError{}
func (e UnauthorizedError) Error() string { return e.Err.Error() }
func (e UnauthorizedError) StatusCode() int { return http.StatusUnauthorized }
func (e UnauthorizedError) Unwrap() error { return HTTPError(e) }
// ForbiddenError is an error used to return a 403 status code.
type ForbiddenError HTTPError
var _ ErrorWithStatus = ForbiddenError{}
func (e ForbiddenError) Error() string { return e.Err.Error() }
func (e ForbiddenError) StatusCode() int { return http.StatusForbidden }
func (e ForbiddenError) Unwrap() error { return HTTPError(e) }
// ConflictError is an error used to return a 409 status code.
type ConflictError HTTPError
var _ ErrorWithStatus = ConflictError{}
func (e ConflictError) Error() string { return e.Err.Error() }
func (e ConflictError) StatusCode() int { return http.StatusConflict }
func (e ConflictError) Unwrap() error { return HTTPError(e) }
// InternalServerError is an error used to return a 500 status code.
type InternalServerError = HTTPError
// NotAcceptableError is an error used to return a 406 status code.
type NotAcceptableError HTTPError
var _ ErrorWithStatus = NotAcceptableError{}
func (e NotAcceptableError) Error() string { return e.Err.Error() }
func (e NotAcceptableError) StatusCode() int { return http.StatusNotAcceptable }
func (e NotAcceptableError) Unwrap() error { return HTTPError(e) }
// ErrorHandler is the default error handler used by the framework.
// If the error is an [HTTPError] that error is returned.
// If the error adheres to the [ErrorWithStatus] and/or [ErrorWithDetail] interface
// the error is transformed to a [HTTPError].
// If the error is not an [HTTPError] nor does it adhere to an
// interface the error is returned as is.
func ErrorHandler(err error) error {
var errorStatus ErrorWithStatus
switch {
case errors.As(err, &HTTPError{}),
errors.As(err, &errorStatus):
return handleHTTPError(err)
}
return err
}
func handleHTTPError(err error) HTTPError {
errResponse := HTTPError{
Err: err,
}
var errorInfo HTTPError
if errors.As(err, &errorInfo) {
errResponse = errorInfo
}
// Check status code
var errorStatus ErrorWithStatus
if errors.As(err, &errorStatus) {
errResponse.Status = errorStatus.StatusCode()
}
// Check for detail
var errorDetail ErrorWithDetail
if errors.As(err, &errorDetail) {
errResponse.Detail = errorDetail.DetailMsg()
}
if errResponse.Title == "" {
errResponse.Title = http.StatusText(errResponse.Status)
}
slog.Error("Error "+errResponse.Title, "status", errResponse.StatusCode(), "detail", errResponse.DetailMsg(), "error", errResponse.Err)
return errResponse
}