Skip to content

Commit 0de3fc3

Browse files
authored
feat: better error handling, minor refactor (#58)
1 parent dfcecbf commit 0de3fc3

File tree

9 files changed

+93
-109
lines changed

9 files changed

+93
-109
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ test:
1010
@go test ./...
1111

1212
bench:
13-
@go test -bench=. -benchmem ./benchmarks
13+
@cd ./benchmarks && go test -bench=. -benchmem .

benchmarks/binding_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"testing"
77

88
"github.com/abemedia/go-don"
9+
_ "github.com/abemedia/go-don/encoding/text"
910
"github.com/abemedia/go-don/pkg/httptest"
1011
"github.com/gin-gonic/gin"
11-
"github.com/valyala/fasthttp"
1212
)
1313

1414
func BenchmarkDon_BindRequest(b *testing.B) {
@@ -19,14 +19,12 @@ func BenchmarkDon_BindRequest(b *testing.B) {
1919
}
2020

2121
api := don.New(nil)
22-
2322
api.Post("/:path", don.H(func(ctx context.Context, req request) (any, error) {
2423
return nil, nil
2524
}))
2625

2726
h := api.RequestHandler()
28-
29-
ctx := httptest.NewRequest(fasthttp.MethodPost, "/path?query=query", "", map[string]string{"header": "header"})
27+
ctx := httptest.NewRequest("POST", "/path?query=query", "", map[string]string{"header": "header"})
3028

3129
for i := 0; i < b.N; i++ {
3230
h(ctx)

benchmarks/http_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import (
88
"testing"
99

1010
"github.com/abemedia/go-don"
11+
_ "github.com/abemedia/go-don/encoding/text"
1112
"github.com/gin-gonic/gin"
1213
"github.com/valyala/fasthttp"
1314
)
1415

1516
func BenchmarkDon_HTTP(b *testing.B) {
1617
api := don.New(nil)
17-
18-
api.Post("/:path", don.H(func(ctx context.Context, req don.Empty) (string, error) {
18+
api.Get("/:path", don.H(func(ctx context.Context, req don.Empty) (string, error) {
1919
return "foo", nil
2020
}))
2121

@@ -37,13 +37,12 @@ func BenchmarkDon_HTTP(b *testing.B) {
3737
}
3838

3939
func BenchmarkGin_HTTP(b *testing.B) {
40-
h := func(c *gin.Context) {
41-
c.String(200, "foo")
42-
}
43-
4440
gin.SetMode("release")
41+
4542
router := gin.New()
46-
router.POST("/:path", h)
43+
router.GET("/:path", func(c *gin.Context) {
44+
c.String(200, "foo")
45+
})
4746

4847
ln, err := net.Listen("tcp", "localhost:0")
4948
if err != nil {

encoding/text/text.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ func encodeText(ctx *fasthttp.RequestCtx, v interface{}) error {
159159
error:
160160

161161
default:
162-
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotAcceptable), fasthttp.StatusNotAcceptable)
163-
return nil
162+
return don.ErrNotAcceptable
164163
}
165164
}
166165

errors.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"strconv"
1111

12+
"github.com/abemedia/go-don/internal/byteconv"
1213
"github.com/goccy/go-json"
1314
"github.com/valyala/fasthttp"
1415
"gopkg.in/yaml.v3"
@@ -51,7 +52,7 @@ func (e *HTTPError) MarshalText() ([]byte, error) {
5152
return m.MarshalText()
5253
}
5354

54-
return []byte(e.Error()), nil
55+
return byteconv.Atob(e.Error()), nil
5556
}
5657

5758
func (e *HTTPError) MarshalJSON() ([]byte, error) {
@@ -94,3 +95,56 @@ var (
9495
_ xml.Marshaler = (*HTTPError)(nil)
9596
_ yaml.Marshaler = (*HTTPError)(nil)
9697
)
98+
99+
// StatusError creates an error from an HTTP status code.
100+
type StatusError int
101+
102+
const (
103+
ErrBadRequest = StatusError(fasthttp.StatusBadRequest)
104+
ErrUnauthorized = StatusError(fasthttp.StatusUnauthorized)
105+
ErrForbidden = StatusError(fasthttp.StatusForbidden)
106+
ErrNotFound = StatusError(fasthttp.StatusNotFound)
107+
ErrMethodNotAllowed = StatusError(fasthttp.StatusMethodNotAllowed)
108+
ErrNotAcceptable = StatusError(fasthttp.StatusNotAcceptable)
109+
ErrUnsupportedMediaType = StatusError(fasthttp.StatusUnsupportedMediaType)
110+
ErrInternalServerError = StatusError(fasthttp.StatusInternalServerError)
111+
)
112+
113+
func (e StatusError) Error() string {
114+
return fasthttp.StatusMessage(int(e))
115+
}
116+
117+
func (e StatusError) StatusCode() int {
118+
return int(e)
119+
}
120+
121+
func (e StatusError) MarshalText() ([]byte, error) {
122+
return byteconv.Atob(e.Error()), nil
123+
}
124+
125+
func (e StatusError) MarshalJSON() ([]byte, error) {
126+
var buf bytes.Buffer
127+
128+
buf.WriteString(`{"message":`)
129+
buf.WriteString(strconv.Quote(e.Error()))
130+
buf.WriteRune('}')
131+
132+
return buf.Bytes(), nil
133+
}
134+
135+
func (e StatusError) MarshalXML(enc *xml.Encoder, _ xml.StartElement) error {
136+
start := xml.StartElement{Name: xml.Name{Local: "message"}}
137+
return enc.EncodeElement(e.Error(), start)
138+
}
139+
140+
func (e StatusError) MarshalYAML() (interface{}, error) {
141+
return map[string]string{"message": e.Error()}, nil
142+
}
143+
144+
var (
145+
_ error = (*StatusError)(nil)
146+
_ encoding.TextMarshaler = (*StatusError)(nil)
147+
_ json.Marshaler = (*StatusError)(nil)
148+
_ xml.Marshaler = (*StatusError)(nil)
149+
_ yaml.Marshaler = (*StatusError)(nil)
150+
)

handler.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,13 @@ func H[T, O any](handle Handle[T, O]) httprouter.Handle { //nolint:gocognit,cycl
3636
{
3737
var t T
3838
if hasTag(t, headerTag) {
39-
dec, err := decoder.NewHeaderDecoder(t, headerTag)
40-
if err == nil {
41-
decodeHeader = dec
42-
}
39+
decodeHeader, _ = decoder.NewHeaderDecoder(t, headerTag)
4340
}
4441
if hasTag(t, queryTag) {
45-
dec, err := decoder.NewArgsDecoder(t, queryTag)
46-
if err == nil {
47-
decodeQuery = dec
48-
}
42+
decodeQuery, _ = decoder.NewArgsDecoder(t, queryTag)
4943
}
5044
if hasTag(t, pathTag) {
51-
dec, err := decoder.NewParamsDecoder(t, pathTag)
52-
if err == nil {
53-
decodePath = dec
54-
}
45+
decodePath, _ = decoder.NewParamsDecoder(t, pathTag)
5546
}
5647
}
5748

@@ -60,7 +51,7 @@ func H[T, O any](handle Handle[T, O]) httprouter.Handle { //nolint:gocognit,cycl
6051

6152
enc, err := getEncoder(contentType)
6253
if err != nil {
63-
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotAcceptable), fasthttp.StatusNotAcceptable)
54+
handleError(ctx, ErrNotAcceptable)
6455
return
6556
}
6657

@@ -142,10 +133,20 @@ func H[T, O any](handle Handle[T, O]) httprouter.Handle { //nolint:gocognit,cycl
142133
}
143134

144135
if err = enc(ctx, res); err != nil {
145-
ctx.Logger().Printf("%v", err)
146-
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusInternalServerError), fasthttp.StatusInternalServerError)
136+
handleError(ctx, err)
137+
}
138+
}
139+
}
140+
141+
func handleError(ctx *fasthttp.RequestCtx, err error) {
142+
if statusCoder, ok := err.(StatusCoder); ok { //nolint:errorlint
143+
if sc := statusCoder.StatusCode(); sc < http.StatusInternalServerError {
144+
ctx.Error(err.Error()+"\n", sc)
145+
return
147146
}
148147
}
148+
ctx.Error(fasthttp.StatusMessage(http.StatusInternalServerError)+"\n", http.StatusInternalServerError)
149+
ctx.Logger().Printf("%v", err)
149150
}
150151

151152
func getEncoding(b []byte) string {

handler_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package don_test
22

33
import (
44
"context"
5-
"net/http"
65
"testing"
76

87
"github.com/abemedia/go-don"
@@ -32,7 +31,7 @@ func TestHandler(t *testing.T) {
3231
{
3332
message: "should return no content",
3433
expected: response{
35-
Code: http.StatusNoContent,
34+
Code: fasthttp.StatusNoContent,
3635
Body: "",
3736
Header: map[string]string{
3837
"Content-Length": "0",
@@ -47,7 +46,7 @@ func TestHandler(t *testing.T) {
4746
{
4847
message: "should return null",
4948
expected: response{
50-
Code: http.StatusOK,
49+
Code: fasthttp.StatusOK,
5150
Body: "null\n",
5251
Header: map[string]string{
5352
"Content-Length": "0",
@@ -62,7 +61,7 @@ func TestHandler(t *testing.T) {
6261
{
6362
message: "should JSON encode map",
6463
expected: response{
65-
Code: http.StatusOK,
64+
Code: fasthttp.StatusOK,
6665
Body: `{"foo":"bar"}` + "\n",
6766
Header: map[string]string{"Content-Type": "application/json; charset=utf-8"},
6867
},
@@ -74,7 +73,7 @@ func TestHandler(t *testing.T) {
7473
{
7574
message: "should return error on unprocessable request",
7675
expected: response{
77-
Code: http.StatusUnsupportedMediaType,
76+
Code: fasthttp.StatusUnsupportedMediaType,
7877
Body: "Unsupported Media Type\n",
7978
Header: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
8079
},
@@ -87,8 +86,8 @@ func TestHandler(t *testing.T) {
8786
{
8887
message: "should return error on unacceptable",
8988
expected: response{
90-
Code: http.StatusNotAcceptable,
91-
Body: "Not Acceptable",
89+
Code: fasthttp.StatusNotAcceptable,
90+
Body: "Not Acceptable\n",
9291
Header: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
9392
},
9493
config: &don.Config{DefaultEncoding: "text/plain"},
@@ -100,8 +99,8 @@ func TestHandler(t *testing.T) {
10099
{
101100
message: "should return error on unsupported accept",
102101
expected: response{
103-
Code: http.StatusNotAcceptable,
104-
Body: "Not Acceptable",
102+
Code: fasthttp.StatusNotAcceptable,
103+
Body: "Not Acceptable\n",
105104
Header: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
106105
},
107106
config: &don.Config{DefaultEncoding: "text/plain"},
@@ -113,7 +112,7 @@ func TestHandler(t *testing.T) {
113112
{
114113
message: "should return error on unsupported content type",
115114
expected: response{
116-
Code: http.StatusUnsupportedMediaType,
115+
Code: fasthttp.StatusUnsupportedMediaType,
117116
Body: "Unsupported Media Type\n",
118117
Header: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
119118
},
@@ -127,7 +126,7 @@ func TestHandler(t *testing.T) {
127126
{
128127
message: "should read text request",
129128
expected: response{
130-
Code: http.StatusOK,
129+
Code: fasthttp.StatusOK,
131130
Body: "foo\n",
132131
Header: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
133132
},

pkg/httptest/request.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package httptest
22

33
import (
4-
"bytes"
4+
"strings"
55

66
"github.com/valyala/fasthttp"
77
)
@@ -15,8 +15,7 @@ func NewRequest(method, url, body string, header map[string]string) *fasthttp.Re
1515
ctx.Request.Header.Set(k, v)
1616
}
1717

18-
b := []byte(body)
19-
ctx.Request.SetBodyStream(bytes.NewReader(b), len(b))
18+
ctx.Request.SetBodyStream(strings.NewReader(body), len(body))
2019

2120
return ctx
2221
}

status_error.go

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)