From 97328ddf3e403e312e5075982635631268e3e833 Mon Sep 17 00:00:00 2001 From: Vee Zhang Date: Fri, 15 Apr 2022 18:17:53 +0800 Subject: [PATCH] add response pkg (#16) --- Makefile | 1 - README.md | 2 +- middleware/standard_response.go | 168 -------------- middleware/standard_response_test.go | 330 --------------------------- response/handler.go | 10 + response/standard_handler.go | 128 +++++++++++ response/standard_handler_test.go | 305 +++++++++++++++++++++++++ 7 files changed, 444 insertions(+), 500 deletions(-) delete mode 100644 middleware/standard_response.go delete mode 100644 middleware/standard_response_test.go create mode 100644 response/handler.go create mode 100644 response/standard_handler.go create mode 100644 response/standard_handler_test.go diff --git a/Makefile b/Makefile index 0c24dfe..f63ece7 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ vet: imports: $(GOBIN)/goimports $(GOBIN)/impi $(GOBIN)/impi --local github.com/vesoft-inc --scheme stdLocalThirdParty \ - --skip handler/*.go --skip model/*.go \ -ignore-generated ./... \ || exit 1 diff --git a/README.md b/README.md index f7bbb9c..e0be5bc 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,4 @@ - [mail](mail) - Simple mail client. - [notify](notify) - Notification interface, supports template, filter, tingtalk and mail. - [validator](validator) - Used for parameter validation. -- [middleware]middleware - Some generic middleware. +- [response](response) - Standard response. diff --git a/middleware/standard_response.go b/middleware/standard_response.go deleted file mode 100644 index fc251e6..0000000 --- a/middleware/standard_response.go +++ /dev/null @@ -1,168 +0,0 @@ -package middleware - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/vesoft-inc/go-pkg/errorx" -) - -const ( - StandardResponseBodyJson StandardResponseBodyType = 0 // nolint:revive - StandardResponseBodyNone StandardResponseBodyType = 1 -) - -const ( - standardResponseFieldCode = "code" - standardResponseFieldMessage = "message" - standardResponseFieldData = "data" - standardResponseFieldDebugInfo = "debugInfo" -) - -type ( - StandardResponseHandler struct { - params StandardResponseHandlerParams - } - - StandardResponseBodyType int - - StandardResponseHandlerParams struct { - // GetErrCode used to parse the error. - GetErrCode func(error) *errorx.ErrCode - // CheckBodyType checks the type of body, default is StandardResponseBodyJson. - CheckBodyType func(r *http.Request) StandardResponseBodyType - // Errorf write the error logs. - Errorf func(format string, a ...interface{}) - // DebugInfo add debugInfo details in body when error. - DebugInfo bool - } - - StandardResponse struct { - params StandardResponseParams - } - - StandardResponseParams struct { - Handle func(w http.ResponseWriter, r *http.Request, data interface{}, err error) - } - - standardResponseCtxKey struct{} -) - -func NewStandardResponseHandler(params StandardResponseHandlerParams) *StandardResponseHandler { - return &StandardResponseHandler{ - params: params, - } -} - -func NewStandardResponse(params StandardResponseParams) *StandardResponse { - return &StandardResponse{ - params: params, - } -} - -func GetStandardResponseHandler(ctx context.Context) func(interface{}, error) { - v := ctx.Value(standardResponseCtxKey{}) - if v == nil { - return nil - } - return v.(func(data interface{}, err error)) -} - -func (h *StandardResponseHandler) GetHandleBody( - r *http.Request, - data interface{}, - err error, -) (httpStatus int, body interface{}) { - httpStatus = http.StatusOK - bodyType := StandardResponseBodyJson - if h.params.CheckBodyType != nil { - bodyType = h.params.CheckBodyType(r) - } - - if err != nil { - e, ok := errorx.AsCodeError(err) - if !ok { - err = errorx.WithCode(errorx.TakeCodePriority(func() *errorx.ErrCode { - if h.params.GetErrCode == nil { - return nil - } - return h.params.GetErrCode(err) - }, func() *errorx.ErrCode { - return errorx.NewErrCode(errorx.CCInternalServer, 0, 0, "ErrInternalServer") - }), err) - e, _ = errorx.AsCodeError(err) - } - httpStatus = e.GetHTTPStatus() - - if bodyType != StandardResponseBodyNone { - resp := map[string]interface{}{ - standardResponseFieldCode: e.GetCode(), - standardResponseFieldMessage: e.GetMessage(), - } - if h.params.DebugInfo { - resp[standardResponseFieldDebugInfo] = fmt.Sprintf("%+v", err) - } - body = resp - } - } else if bodyType != StandardResponseBodyNone { - resp := map[string]interface{}{ - standardResponseFieldCode: 0, - standardResponseFieldMessage: "Success", - } - if data != nil { - resp[standardResponseFieldData] = data - } - body = resp - } - - return httpStatus, body -} - -func (h *StandardResponseHandler) Handle( - w http.ResponseWriter, - r *http.Request, - data interface{}, - err error, -) { - httpStatus, body := h.GetHandleBody(r, data, err) - if body == nil { - w.WriteHeader(httpStatus) - return - } - - bs, err := json.Marshal(body) - if err != nil { - h.errorf("write response json.Marshal failed, error: %s", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(httpStatus) - if n, err := w.Write(bs); err != nil { - if err != http.ErrHandlerTimeout { - h.errorf("write response failed, error: %s", err) - } - } else if n < len(bs) { - h.errorf("write response failed, actual bytes: %d, written bytes: %d", len(bs), n) - } -} - -func (m *StandardResponse) Handler(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - next(w, r.WithContext(context.WithValue(r.Context(), - standardResponseCtxKey{}, - func(data interface{}, err error) { - m.params.Handle(w, r, data, err) - }), - )) - } -} - -func (h *StandardResponseHandler) errorf(format string, a ...interface{}) { - if h.params.Errorf != nil { - h.params.Errorf(format, a...) - } -} diff --git a/middleware/standard_response_test.go b/middleware/standard_response_test.go deleted file mode 100644 index 7da1a36..0000000 --- a/middleware/standard_response_test.go +++ /dev/null @@ -1,330 +0,0 @@ -package middleware - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "net/http" - "net/http/httptest" - "testing" - - "github.com/vesoft-inc/go-pkg/errorx" - - "github.com/stretchr/testify/assert" -) - -type testRecorder struct { - HeaderMap http.Header - Code int - Body *bytes.Buffer - write func([]byte) (int, error) - wroteHeader bool -} - -func newTestRecorder(write func([]byte) (int, error)) *testRecorder { - return &testRecorder{ - write: write, - HeaderMap: make(http.Header), - Code: 200, - Body: new(bytes.Buffer), - } -} - -func (rec *testRecorder) Header() http.Header { - return rec.HeaderMap -} - -func (rec *testRecorder) Write(buf []byte) (int, error) { - n, err := rec.write(buf) - rec.Body.Write(buf[:n]) - return n, err -} - -func (rec *testRecorder) WriteHeader(statusCode int) { - if rec.wroteHeader { - return - } - rec.wroteHeader = true - rec.Code = statusCode -} - -func TestGetStandardResponseHandler(t *testing.T) { - handler := GetStandardResponseHandler(context.Background()) - assert.Nil(t, handler) -} - -func TestStandardResponseHandler(t *testing.T) { - tests := []struct { - name string - params StandardResponseHandlerParams - r *http.Request - data interface{} - err error - expectedStatus int - expectedBody interface{} - }{{ - name: "data:no", - params: StandardResponseHandlerParams{}, - expectedStatus: 200, - expectedBody: map[string]interface{}{ - "code": 0, - "message": "Success", - }, - }, { - name: "data:no:error:normal", - params: StandardResponseHandlerParams{}, - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: map[string]interface{}{ - "code": 50000000, - "message": "ErrInternalServer", - }, - }, { - name: "data:no:error:errorx", - params: StandardResponseHandlerParams{}, - err: errorx.WithCode(errorx.NewErrCode(403, 1, 2, "testError"), nil), - expectedStatus: 403, - expectedBody: map[string]interface{}{ - "code": 40301002, - "message": "testError", - }, - }, { - name: "data:no:error:GetErrCode", - params: StandardResponseHandlerParams{ - GetErrCode: func(err error) *errorx.ErrCode { - return errorx.NewErrCode(403, 1, 2, "testError") - }, - }, - err: errors.New("testError0"), - expectedStatus: 403, - expectedBody: map[string]interface{}{ - "code": 40301002, - "message": "testError", - }, - }, { - name: "data:no:DebugInfo", - params: StandardResponseHandlerParams{ - DebugInfo: true, - }, - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: map[string]interface{}{ - "code": 50000000, - "message": "ErrInternalServer", - "debugInfo": "testError\n50000000: ErrInternalServer", - }, - }, { - name: "data:no:CheckBodyType:none", - params: StandardResponseHandlerParams{ - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyNone }, - }, - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: nil, - }, { - name: "data:no:CheckBodyType:json", - params: StandardResponseHandlerParams{ - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyJson }, - }, - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: map[string]interface{}{ - "code": 50000000, - "message": "ErrInternalServer", - }, - }, { - name: "data:yes", - params: StandardResponseHandlerParams{}, - data: "data", - expectedStatus: 200, - expectedBody: map[string]interface{}{ - "code": 0, - "message": "Success", - "data": "data", - }, - }, { - name: "data:yes:error", - params: StandardResponseHandlerParams{}, - data: "data", - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: map[string]interface{}{ - "code": 50000000, - "message": "ErrInternalServer", - }, - }, { - name: "data:yes:CheckBodyType:none", - params: StandardResponseHandlerParams{ - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyNone }, - }, - data: "data", - expectedStatus: 200, - expectedBody: nil, - }, { - name: "data:yes:CheckBodyType:json", - params: StandardResponseHandlerParams{ - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyJson }, - }, - data: "data", - expectedStatus: 200, - expectedBody: map[string]interface{}{ - "code": 0, - "message": "Success", - "data": "data", - }, - }} - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - fn := func(httpStatus int, body interface{}) { - assert.Equal(t, test.expectedStatus, httpStatus) - if test.err != nil && test.params.DebugInfo { - assert.Contains(t, body.(map[string]interface{})["debugInfo"], test.expectedBody.(map[string]interface{})["debugInfo"]) - assert.Equal(t, map[string]interface{}{ - "code": test.expectedBody.(map[string]interface{})["code"], - "message": test.expectedBody.(map[string]interface{})["message"], - }, map[string]interface{}{ - "code": body.(map[string]interface{})["code"], - "message": body.(map[string]interface{})["message"], - }) - } else { - assert.Equal(t, test.expectedBody, body) - } - } - - h := NewStandardResponseHandler(test.params) - { - httpStatus, body := h.GetHandleBody(test.r, test.data, test.err) - fn(httpStatus, body) - } - { - rec := httptest.NewRecorder() - h.Handle(rec, test.r, test.data, test.err) - if rec.Body.String() == "" { - fn(rec.Code, nil) - } else { - body := struct { - Code int - Message string - Data interface{} - DebugInfo string - }{} - err := json.Unmarshal(rec.Body.Bytes(), &body) - assert.NoError(t, err) - cmpBody := map[string]interface{}{ - "code": body.Code, - "message": body.Message, - } - if body.Data != nil { - cmpBody["data"] = body.Data - } - if body.DebugInfo != "" { - cmpBody["debugInfo"] = body.DebugInfo - } - fn(rec.Code, cmpBody) - } - } - }) - } -} - -func TestStandardResponseMiddleware(t *testing.T) { - tests := []struct { - name string - CheckBodyType func(r *http.Request) StandardResponseBodyType - data interface{} - err error - expectedStatus int - expectedBody string - }{{ - name: "CheckBodyType:json", - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyJson }, - data: "data", - expectedStatus: 200, - expectedBody: "{\"code\":0,\"data\":\"data\",\"message\":\"Success\"}", - }, { - name: "CheckBodyType:none", - CheckBodyType: func(r *http.Request) StandardResponseBodyType { return StandardResponseBodyNone }, - data: "data", - err: nil, - expectedStatus: 200, - expectedBody: "", - }, { - name: "error:normal", - data: "data", - err: errors.New("testError"), - expectedStatus: 500, - expectedBody: "{\"code\":50000000,\"message\":\"ErrInternalServer\"}", - }, { - name: "error:errorx", - data: "data", - err: errorx.WithCode(errorx.NewErrCode(403, 1, 2, "testError"), nil), - expectedStatus: 403, - expectedBody: "{\"code\":40301002,\"message\":\"testError\"}", - }, { - name: "data:unsupported:type", - data: make(chan struct{}), - err: nil, - expectedStatus: 500, - expectedBody: "", - }} - - for _, test := range tests { - if test.name != "data:unsupported:type" { - continue - } - t.Run(test.name, func(t *testing.T) { - m := NewStandardResponse(StandardResponseParams{ - Handle: NewStandardResponseHandler(StandardResponseHandlerParams{ - CheckBodyType: test.CheckBodyType, - Errorf: func(format string, a ...interface{}) {}, - }).Handle, - }) - - nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - f := r.Context().Value(standardResponseCtxKey{}) - assert.NotNil(t, f) - handle := GetStandardResponseHandler(r.Context()) - assert.NotNil(t, handle) - handle(test.data, test.err) - }) - - h := m.Handler(nextHandler) - req := httptest.NewRequest("GET", "http://localhost", nil) - - { - rec := httptest.NewRecorder() - h.ServeHTTP(rec, req) - assert.Equal(t, test.expectedStatus, rec.Code) - assert.Equal(t, test.expectedBody, rec.Body.String()) - } - - { - rec := newTestRecorder(func(buf []byte) (int, error) { - return 0, errors.New("testError") - }) - h.ServeHTTP(rec, req) - assert.Equal(t, test.expectedStatus, rec.Code) - assert.Equal(t, "", rec.Body.String()) - } - - { - rec := newTestRecorder(func(buf []byte) (int, error) { - l := len(buf) - if l == 0 { - return 0, nil - } - return l - 1, nil - }) - h.ServeHTTP(rec, req) - assert.Equal(t, test.expectedStatus, rec.Code) - if test.expectedBody == "" { - assert.Equal(t, test.expectedBody, rec.Body.String()) - } else { - assert.Equal(t, test.expectedBody[:len(test.expectedBody)-1], rec.Body.String()) - } - } - }) - } -} diff --git a/response/handler.go b/response/handler.go new file mode 100644 index 0000000..5b50194 --- /dev/null +++ b/response/handler.go @@ -0,0 +1,10 @@ +package response + +import "net/http" + +type ( + Handler interface { + Handle(w http.ResponseWriter, r *http.Request, data interface{}, err error) + GetStatusBody(r *http.Request, data interface{}, err error) (httpStatus int, body interface{}) + } +) diff --git a/response/standard_handler.go b/response/standard_handler.go new file mode 100644 index 0000000..c40e578 --- /dev/null +++ b/response/standard_handler.go @@ -0,0 +1,128 @@ +package response + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/vesoft-inc/go-pkg/errorx" +) + +const ( + StandardHandlerBodyJson StandardHandlerBodyType = 0 // nolint:revive + StandardHandlerBodyNone StandardHandlerBodyType = 1 +) + +const ( + standardHandlerFieldCode = "code" + standardHandlerFieldMessage = "message" + standardHandlerFieldData = "data" + standardHandlerFieldDebugInfo = "debugInfo" +) + +var _ Handler = (*standardHandler)(nil) + +type ( + standardHandler struct { + params StandardHandlerParams + } + + StandardHandlerBodyType int + + StandardHandlerParams struct { + // GetErrCode used to parse the error. + GetErrCode func(error) *errorx.ErrCode + // CheckBodyType checks the type of body, default is StandardHandlerBodyJson. + CheckBodyType func(r *http.Request) StandardHandlerBodyType + // Errorf write the error logs. + Errorf func(format string, a ...interface{}) + // DebugInfo add debugInfo details in body when error. + DebugInfo bool + } +) + +func NewStandardHandler(params StandardHandlerParams) Handler { + return &standardHandler{ + params: params, + } +} + +func (h *standardHandler) GetStatusBody(r *http.Request, data interface{}, err error) (httpStatus int, body interface{}) { + httpStatus = http.StatusOK + bodyType := StandardHandlerBodyJson + + if r == nil { + bodyType = StandardHandlerBodyNone + } else if h.params.CheckBodyType != nil { + bodyType = h.params.CheckBodyType(r) + } + + if err != nil { + e, ok := errorx.AsCodeError(err) + if !ok { + err = errorx.WithCode(errorx.TakeCodePriority(func() *errorx.ErrCode { + if h.params.GetErrCode == nil { + return nil + } + return h.params.GetErrCode(err) + }, func() *errorx.ErrCode { + return errorx.NewErrCode(errorx.CCInternalServer, 0, 0, "ErrInternalServer") + }), err) + e, _ = errorx.AsCodeError(err) + } + httpStatus = e.GetHTTPStatus() + + if bodyType != StandardHandlerBodyNone { + resp := map[string]interface{}{ + standardHandlerFieldCode: e.GetCode(), + standardHandlerFieldMessage: e.GetMessage(), + } + if h.params.DebugInfo { + resp[standardHandlerFieldDebugInfo] = fmt.Sprintf("%+v", err) + } + body = resp + } + } else if bodyType != StandardHandlerBodyNone { + resp := map[string]interface{}{ + standardHandlerFieldCode: 0, + standardHandlerFieldMessage: "Success", + } + if data != nil { + resp[standardHandlerFieldData] = data + } + body = resp + } + + return httpStatus, body +} + +func (h *standardHandler) Handle(w http.ResponseWriter, r *http.Request, data interface{}, err error) { + httpStatus, body := h.GetStatusBody(r, data, err) + if body == nil { + w.WriteHeader(httpStatus) + return + } + + bs, err := json.Marshal(body) + if err != nil { + h.errorf("write response json.Marshal failed, error: %s", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(httpStatus) + if n, err := w.Write(bs); err != nil { + if err != http.ErrHandlerTimeout { + h.errorf("write response failed, error: %s", err) + } + } else if n < len(bs) { + h.errorf("write response failed, actual bytes: %d, written bytes: %d", len(bs), n) + } +} + +func (h *standardHandler) errorf(format string, a ...interface{}) { + if h.params.Errorf != nil { + h.params.Errorf(format, a...) + } +} diff --git a/response/standard_handler_test.go b/response/standard_handler_test.go new file mode 100644 index 0000000..30a0205 --- /dev/null +++ b/response/standard_handler_test.go @@ -0,0 +1,305 @@ +package response + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/vesoft-inc/go-pkg/errorx" + + "github.com/stretchr/testify/assert" +) + +type testRecorder struct { + HeaderMap http.Header + Code int + Body *bytes.Buffer + write func([]byte) (int, error) + wroteHeader bool +} + +func newTestRecorder(write func([]byte) (int, error)) *testRecorder { + return &testRecorder{ + write: write, + HeaderMap: make(http.Header), + Code: 200, + Body: new(bytes.Buffer), + } +} + +func (rec *testRecorder) Header() http.Header { + return rec.HeaderMap +} + +func (rec *testRecorder) Write(buf []byte) (int, error) { + n, err := rec.write(buf) + rec.Body.Write(buf[:n]) + return n, err +} + +func (rec *testRecorder) WriteHeader(statusCode int) { + if rec.wroteHeader { + return + } + rec.wroteHeader = true + rec.Code = statusCode +} + +func TestStandardHandler(t *testing.T) { + tests := []struct { + name string + params StandardHandlerParams + r *http.Request + data interface{} + unsupportedData bool + err error + expectedStatus int + expectedBody interface{} + }{{ + name: "data:no", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + expectedStatus: 200, + expectedBody: map[string]interface{}{ + "code": 0, + "message": "Success", + }, + }, { + name: "data:no:error:normal", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errors.New("testError"), + expectedStatus: 500, + expectedBody: map[string]interface{}{ + "code": 50000000, + "message": "ErrInternalServer", + }, + }, { + name: "data:no:error:errorx", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errorx.WithCode(errorx.NewErrCode(403, 1, 2, "testError"), nil), + expectedStatus: 403, + expectedBody: map[string]interface{}{ + "code": 40301002, + "message": "testError", + }, + }, { + name: "data:no:error:GetErrCode", + params: StandardHandlerParams{ + GetErrCode: func(err error) *errorx.ErrCode { + return errorx.NewErrCode(403, 1, 2, "testError") + }, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errors.New("testError0"), + expectedStatus: 403, + expectedBody: map[string]interface{}{ + "code": 40301002, + "message": "testError", + }, + }, { + name: "data:no:DebugInfo", + params: StandardHandlerParams{ + DebugInfo: true, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errors.New("testError"), + expectedStatus: 500, + expectedBody: map[string]interface{}{ + "code": 50000000, + "message": "ErrInternalServer", + "debugInfo": "testError\n50000000: ErrInternalServer", + }, + }, { + name: "data:no:CheckBodyType:none", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyNone }, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + expectedStatus: 200, + expectedBody: nil, + }, { + name: "data:no:CheckBodyType:none:error", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyNone }, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errors.New("testError"), + expectedStatus: 500, + expectedBody: nil, + }, { + name: "data:no:CheckBodyType:json", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyJson }, + Errorf: func(format string, a ...interface{}) {}, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + err: errors.New("testError"), + expectedStatus: 500, + expectedBody: map[string]interface{}{ + "code": 50000000, + "message": "ErrInternalServer", + }, + }, { + name: "data:yes", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + data: "data", + expectedStatus: 200, + expectedBody: map[string]interface{}{ + "code": 0, + "message": "Success", + "data": "data", + }, + }, { + name: "data:yes:error", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + data: "data", + err: errors.New("testError"), + expectedStatus: 500, + expectedBody: map[string]interface{}{ + "code": 50000000, + "message": "ErrInternalServer", + }, + }, { + name: "data:yes:CheckBodyType:none", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyNone }, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + data: "data", + expectedStatus: 200, + expectedBody: nil, + }, { + name: "data:yes:CheckBodyType:json", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyJson }, + }, + r: httptest.NewRequest("GET", "http://localhost", nil), + data: "data", + expectedStatus: 200, + expectedBody: map[string]interface{}{ + "code": 0, + "message": "Success", + "data": "data", + }, + }, { + name: "data:unsupported:type", + params: StandardHandlerParams{}, + r: httptest.NewRequest("GET", "http://localhost", nil), + data: complex(0, 0), + unsupportedData: true, + expectedStatus: 200, + expectedBody: map[string]interface{}{ + "code": 0, + "message": "Success", + "data": complex(0, 0), + }, + }, { + name: "data:yes:r:nil", + params: StandardHandlerParams{ + CheckBodyType: func(r *http.Request) StandardHandlerBodyType { return StandardHandlerBodyJson }, + }, + data: "data", + expectedStatus: 200, + expectedBody: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + checkFunc := func(expectedStatus, httpStatus int, expectedBody, body interface{}) { + assert.Equal(t, expectedStatus, httpStatus) + if test.err != nil && test.params.DebugInfo { + assert.Contains(t, body.(map[string]interface{})["debugInfo"], expectedBody.(map[string]interface{})["debugInfo"]) + assert.Equal(t, map[string]interface{}{ + "code": expectedBody.(map[string]interface{})["code"], + "message": expectedBody.(map[string]interface{})["message"], + }, map[string]interface{}{ + "code": body.(map[string]interface{})["code"], + "message": body.(map[string]interface{})["message"], + }) + } else { + assert.Equal(t, expectedBody, body) + } + } + checkRespFunc := func(expectedStatus, httpStatus int, expectedBody interface{}, bodyBytes []byte) { + var body interface{} + if len(bodyBytes) > 0 { + bodyStruct := struct { + Code int + Message string + Data interface{} + DebugInfo string + }{} + err := json.Unmarshal(bodyBytes, &bodyStruct) + assert.NoError(t, err) + cmpBody := map[string]interface{}{ + "code": bodyStruct.Code, + "message": bodyStruct.Message, + } + if bodyStruct.Data != nil { + cmpBody["data"] = bodyStruct.Data + } + if bodyStruct.DebugInfo != "" { + cmpBody["debugInfo"] = bodyStruct.DebugInfo + } + body = cmpBody + } + checkFunc(expectedStatus, httpStatus, expectedBody, body) + } + + h := NewStandardHandler(test.params) + { + httpStatus, body := h.GetStatusBody(test.r, test.data, test.err) + checkFunc(test.expectedStatus, httpStatus, test.expectedBody, body) + } + { + rec := httptest.NewRecorder() + h.Handle(rec, test.r, test.data, test.err) + if test.unsupportedData { + assert.Equal(t, 500, rec.Code) + assert.Equal(t, "", rec.Body.String()) + } else { + checkRespFunc(test.expectedStatus, rec.Code, test.expectedBody, rec.Body.Bytes()) + } + } + { + rec := newTestRecorder(func(buf []byte) (int, error) { + return 0, errors.New("testError") + }) + h.Handle(rec, test.r, test.data, test.err) + if test.unsupportedData { + assert.Equal(t, 500, rec.Code) + assert.Equal(t, "", rec.Body.String()) + } else { + assert.Equal(t, test.expectedStatus, rec.Code) + assert.Equal(t, "", rec.Body.String()) + } + } + + { + rec := newTestRecorder(func(buf []byte) (int, error) { + l := len(buf) + if l == 0 { + return 0, nil + } + return l - 1, nil + }) + h.Handle(rec, test.r, test.data, test.err) + if test.unsupportedData { + assert.Equal(t, 500, rec.Code) + assert.Equal(t, "", rec.Body.String()) + } else if test.expectedBody == nil { + checkRespFunc(test.expectedStatus, rec.Code, test.expectedBody, rec.Body.Bytes()) + } else { + checkRespFunc(test.expectedStatus, rec.Code, test.expectedBody, append(rec.Body.Bytes(), byte('}'))) + } + } + }) + } +}