• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-fuego / fuego / 14632450843

24 Apr 2025 02:59AM UTC coverage: 83.374%. Remained the same
14632450843

push

github

dylanhitt
chore: ensure proper context logging

2713 of 3254 relevant lines covered (83.37%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.46
/errors.go
1
package fuego
2

3
import (
4
        "context"
5
        "errors"
6
        "log/slog"
7
        "net/http"
8
        "strconv"
9
        "strings"
10
)
11

12
// ErrorWithStatus is an interface that can be implemented by an error to provide
13
// a status code
14
type ErrorWithStatus interface {
15
        error
16
        StatusCode() int
17
}
18

19
// ErrorWithDetail is an interface that can be implemented by an error to provide
20
// an additional detail message about the error
21
type ErrorWithDetail interface {
22
        error
23
        DetailMsg() string
24
}
25

26
// HTTPError is the error response used by the serialization part of the framework.
27
type HTTPError struct {
28
        // Developer readable error message. Not shown to the user to avoid security leaks.
29
        Err error `json:"-" xml:"-" yaml:"-"`
30
        // URL of the error type. Can be used to lookup the error in a documentation
31
        Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" description:"URL of the error type. Can be used to lookup the error in a documentation"`
32
        // Short title of the error
33
        Title string `json:"title,omitempty" xml:"title,omitempty" yaml:"title,omitempty" description:"Short title of the error"`
34
        // HTTP status code. If using a different type than [HTTPError], for example [BadRequestError], this will be automatically overridden after Fuego error handling.
35
        Status int `json:"status,omitempty" xml:"status,omitempty" yaml:"status,omitempty" description:"HTTP status code" example:"403"`
36
        // Human readable error message
37
        Detail   string      `json:"detail,omitempty" xml:"detail,omitempty" yaml:"detail,omitempty" description:"Human readable error message"`
38
        Instance string      `json:"instance,omitempty" xml:"instance,omitempty" yaml:"instance,omitempty"`
39
        Errors   []ErrorItem `json:"errors,omitempty" xml:"errors,omitempty" yaml:"errors,omitempty"`
40
}
41

42
type ErrorItem struct {
43
        More   map[string]any `json:"more,omitempty" xml:"more,omitempty" description:"Additional information about the error"`
44
        Name   string         `json:"name" xml:"name" description:"For example, name of the parameter that caused the error"`
45
        Reason string         `json:"reason" xml:"reason" description:"Human readable error message"`
46
}
47

48
// PublicError returns a human readable error message.
49
// It ignores the underlying error for security and only returns the status code, title and detail.
50
func (e HTTPError) PublicError() string {
1✔
51
        var msgBuilder strings.Builder
1✔
52

1✔
53
        code := e.StatusCode()
1✔
54
        msgBuilder.WriteString(strconv.Itoa(code))
1✔
55

1✔
56
        title := e.Title
1✔
57
        if title == "" {
2✔
58
                title = http.StatusText(code)
1✔
59
                if title == "" {
1✔
60
                        title = "HTTP Error"
×
61
                }
×
62
        }
63
        msgBuilder.WriteString(" ")
1✔
64
        msgBuilder.WriteString(title)
1✔
65

1✔
66
        if e.Detail != "" {
2✔
67
                msgBuilder.WriteString(" (")
1✔
68
                msgBuilder.WriteString(e.Detail)
1✔
69
                msgBuilder.WriteString(")")
1✔
70
        }
1✔
71

72
        return msgBuilder.String()
1✔
73
}
74

75
func (e HTTPError) Error() string {
1✔
76
        msg := e.PublicError()
1✔
77

1✔
78
        if e.Err != nil {
2✔
79
                msg = msg + ": " + e.Err.Error()
1✔
80
        }
1✔
81

82
        return msg
1✔
83
}
84

85
func (e HTTPError) StatusCode() int {
1✔
86
        if e.Status == 0 {
2✔
87
                return http.StatusInternalServerError
1✔
88
        }
1✔
89
        return e.Status
1✔
90
}
91

92
func (e HTTPError) DetailMsg() string {
1✔
93
        return e.Detail
1✔
94
}
1✔
95

96
func (e HTTPError) Unwrap() error { return e.Err }
1✔
97

98
// BadRequestError is an error used to return a 400 status code.
99
type BadRequestError HTTPError
100

101
var _ ErrorWithStatus = BadRequestError{}
102

103
func (e BadRequestError) Error() string {
×
104
        e.Status = http.StatusBadRequest
×
105
        return HTTPError(e).Error()
×
106
}
×
107

108
func (e BadRequestError) StatusCode() int { return http.StatusBadRequest }
1✔
109

110
func (e BadRequestError) Unwrap() error { return HTTPError(e) }
1✔
111

112
// NotFoundError is an error used to return a 404 status code.
113
type NotFoundError HTTPError
114

115
var _ ErrorWithStatus = NotFoundError{}
116

117
func (e NotFoundError) Error() string {
1✔
118
        e.Status = http.StatusNotFound
1✔
119
        return HTTPError(e).Error()
1✔
120
}
1✔
121

122
func (e NotFoundError) StatusCode() int { return http.StatusNotFound }
1✔
123

124
func (e NotFoundError) Unwrap() error { return HTTPError(e) }
1✔
125

126
// UnauthorizedError is an error used to return a 401 status code.
127
type UnauthorizedError HTTPError
128

129
var _ ErrorWithStatus = UnauthorizedError{}
130

131
func (e UnauthorizedError) Error() string {
1✔
132
        e.Status = http.StatusUnauthorized
1✔
133
        return HTTPError(e).Error()
1✔
134
}
1✔
135

136
func (e UnauthorizedError) StatusCode() int { return http.StatusUnauthorized }
1✔
137

138
func (e UnauthorizedError) Unwrap() error { return HTTPError(e) }
1✔
139

140
// ForbiddenError is an error used to return a 403 status code.
141
type ForbiddenError HTTPError
142

143
var _ ErrorWithStatus = ForbiddenError{}
144

145
func (e ForbiddenError) Error() string {
1✔
146
        e.Status = http.StatusForbidden
1✔
147
        return HTTPError(e).Error()
1✔
148
}
1✔
149

150
func (e ForbiddenError) StatusCode() int { return http.StatusForbidden }
1✔
151

152
func (e ForbiddenError) Unwrap() error { return HTTPError(e) }
1✔
153

154
// ConflictError is an error used to return a 409 status code.
155
type ConflictError HTTPError
156

157
var _ ErrorWithStatus = ConflictError{}
158

159
func (e ConflictError) Error() string {
1✔
160
        e.Status = http.StatusConflict
1✔
161
        return HTTPError(e).Error()
1✔
162
}
1✔
163

164
func (e ConflictError) StatusCode() int { return http.StatusConflict }
1✔
165

166
func (e ConflictError) Unwrap() error { return HTTPError(e) }
1✔
167

168
// InternalServerError is an error used to return a 500 status code.
169
type InternalServerError = HTTPError
170

171
// NotAcceptableError is an error used to return a 406 status code.
172
type NotAcceptableError HTTPError
173

174
var _ ErrorWithStatus = NotAcceptableError{}
175

176
func (e NotAcceptableError) Error() string {
×
177
        e.Status = http.StatusNotAcceptable
×
178
        return HTTPError(e).Error()
×
179
}
×
180

181
func (e NotAcceptableError) StatusCode() int { return http.StatusNotAcceptable }
×
182

183
func (e NotAcceptableError) Unwrap() error { return HTTPError(e) }
×
184

185
// ErrorHandler is the default error handler used by the framework.
186
// If the error is an [HTTPError] that error is returned.
187
// If the error adheres to the [ErrorWithStatus] interface
188
// the error is transformed to a [HTTPError] using [HandleHTTPError].
189
// If the error is not an [HTTPError] nor does it adhere to an
190
// interface the error is returned as is.
191
func ErrorHandler(ctx context.Context, err error) error {
1✔
192
        var errorStatus ErrorWithStatus
1✔
193
        switch {
1✔
194
        case errors.As(err, &HTTPError{}),
195
                errors.As(err, &errorStatus):
1✔
196
                return HandleHTTPError(ctx, err)
1✔
197
        }
198

199
        slog.ErrorContext(ctx, "Error in controller", "error", err.Error())
1✔
200

1✔
201
        return err
1✔
202
}
203

204
// HandleHTTPError is the core logic
205
// of handling fuego [HTTPError]'s. This
206
// function takes any error and coerces it into a fuego HTTPError.
207
// This can be used override the default handler:
208
//
209
//        engine := fuego.NewEngine(
210
//                WithErrorHandler(HandleHTTPError),
211
//        )
212
//
213
// or
214
//
215
//        server := fuego.NewServer(
216
//                fuego.WithEngineOptions(
217
//                        fuego.WithErrorHandler(HandleHTTPError),
218
//                ),
219
//        )
220
func HandleHTTPError(ctx context.Context, err error) error {
1✔
221
        errResponse := HTTPError{
1✔
222
                Err: err,
1✔
223
        }
1✔
224

1✔
225
        var errorInfo HTTPError
1✔
226
        if errors.As(err, &errorInfo) {
2✔
227
                errResponse = errorInfo
1✔
228
        }
1✔
229

230
        // Check status code
231
        var errorStatus ErrorWithStatus
1✔
232
        if errors.As(err, &errorStatus) {
2✔
233
                errResponse.Status = errorStatus.StatusCode()
1✔
234
        }
1✔
235

236
        // Check for detail
237
        var errorDetail ErrorWithDetail
1✔
238
        if errors.As(err, &errorDetail) {
2✔
239
                errResponse.Detail = errorDetail.DetailMsg()
1✔
240
        }
1✔
241

242
        if errResponse.Title == "" {
2✔
243
                errResponse.Title = http.StatusText(errResponse.Status)
1✔
244
        }
1✔
245

246
        slog.ErrorContext(ctx, "Error "+errResponse.Title, "status", errResponse.StatusCode(), "detail", errResponse.DetailMsg(), "error", errResponse.Err)
1✔
247

1✔
248
        return errResponse
1✔
249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc