Skip to content

Commit 1d443c5

Browse files
committed
Reimplement server.Error for composite handlers only
1 parent 7dbd7a7 commit 1d443c5

File tree

4 files changed

+158
-2
lines changed

4 files changed

+158
-2
lines changed

pkg/server/error.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2024 SeatGeek, Inc.
2+
//
3+
// Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms.
4+
5+
package server
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
)
11+
12+
// Error is a custom error type that includes an HTTP status code
13+
type Error struct {
14+
Code int
15+
Reason error
16+
}
17+
18+
func (h *Error) Error() string {
19+
if h.Reason != nil {
20+
return fmt.Sprintf("internal %d: %v", h.Code, h.Reason.Error())
21+
}
22+
return fmt.Sprintf("internal %d: something happened, perhaps", h.Code)
23+
}
24+
25+
func (h *Error) Is(target error) bool {
26+
var err *Error
27+
if ok := errors.As(target, &err); !ok {
28+
return false
29+
}
30+
31+
return err.Code == h.Code && (errors.Is(err.Reason, h.Reason) || err.Reason.Error() == h.Reason.Error())
32+
}

pkg/server/error_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2024 SeatGeek, Inc.
2+
//
3+
// Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms.
4+
5+
package server
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestError_Error(t *testing.T) {
16+
t.Parallel()
17+
18+
tests := []struct {
19+
name string
20+
err *Error
21+
want string
22+
}{
23+
{
24+
name: "with reason",
25+
err: &Error{Code: 403, Reason: errors.New("forbidden")},
26+
want: "internal 403: forbidden",
27+
},
28+
{
29+
name: "without reason",
30+
err: &Error{Code: 403},
31+
want: "internal 403: something happened, perhaps",
32+
},
33+
}
34+
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
t.Parallel()
38+
39+
assert.Equal(t, tt.want, tt.err.Error())
40+
})
41+
}
42+
}
43+
44+
func TestError_Is(t *testing.T) {
45+
t.Parallel()
46+
47+
target := &Error{Code: 403, Reason: errors.New("forbidden")}
48+
49+
tests := []struct {
50+
name string
51+
other error
52+
want bool
53+
}{
54+
{
55+
name: "exact same object",
56+
other: target,
57+
want: true,
58+
},
59+
{
60+
name: "different object, same values",
61+
other: &Error{Code: 403, Reason: errors.New("forbidden")},
62+
want: true,
63+
},
64+
{
65+
name: "different code",
66+
other: &Error{Code: 400, Reason: errors.New("forbidden")},
67+
want: false,
68+
},
69+
{
70+
name: "different reason",
71+
other: &Error{Code: 403, Reason: errors.New("not allowed")},
72+
want: false,
73+
},
74+
{
75+
name: "different type",
76+
other: errors.New("forbidden"),
77+
want: false,
78+
},
79+
{
80+
name: "wrapped",
81+
other: fmt.Errorf("wrapped: %w", target),
82+
want: true,
83+
},
84+
{
85+
name: "inner reason",
86+
other: target.Reason,
87+
want: false,
88+
},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
t.Parallel()
94+
95+
assert.Equal(t, tt.want, errors.Is(target, tt.other))
96+
})
97+
}
98+
}

pkg/server/handler.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package server
77

88
import (
9+
"errors"
910
"fmt"
1011
"log/slog"
1112
"net/http"
@@ -23,8 +24,7 @@ func CreateEventHandler(s handler.Handler, n notifier.Notifier) http.HandlerFunc
2324

2425
notifications, err := s.Process(request)
2526
if err != nil {
26-
slog.Error("failed to generate notifications", "handler", s.Key(), "error", err)
27-
http.Error(writer, fmt.Sprintf("failed to generate notifications: %v", err), 500)
27+
logAndSendErrorResponse(writer, s.Key(), "failed to generate notifications", err)
2828
return
2929
}
3030

@@ -51,3 +51,21 @@ func CreateEventHandler(s handler.Handler, n notifier.Notifier) http.HandlerFunc
5151
}
5252
}
5353
}
54+
55+
func logAndSendErrorResponse(writer http.ResponseWriter, handlerKey string, errorPrefix string, err error) {
56+
statusCode := 500
57+
58+
var httpError *Error
59+
if errors.As(err, &httpError) {
60+
statusCode = httpError.Code
61+
err = httpError.Reason
62+
}
63+
64+
if statusCode < 500 {
65+
slog.Warn(errorPrefix, "handler", handlerKey, "error", err)
66+
} else {
67+
slog.Error(errorPrefix, "handler", handlerKey, "error", err)
68+
}
69+
70+
http.Error(writer, fmt.Sprintf("%s: %v", errorPrefix, err), statusCode)
71+
}

pkg/server/handler_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ func TestHandler(t *testing.T) {
4949
handler: handlerThatReturns(t, nil, someError),
5050
wantStatusCode: 500,
5151
},
52+
{
53+
name: "parse error with custom HTTP status code",
54+
handler: handlerThatReturns(t, nil, &Error{
55+
Code: 400,
56+
Reason: someError,
57+
}),
58+
wantStatusCode: 400,
59+
},
5260
{
5361
name: "notifier error",
5462
handler: handlerThatReturns(t, someNotifications, nil),

0 commit comments

Comments
 (0)