diff --git a/pkg/publicapi/apimodels/job.go b/pkg/publicapi/apimodels/job.go index 5060d33cde..01791dbc25 100644 --- a/pkg/publicapi/apimodels/job.go +++ b/pkg/publicapi/apimodels/job.go @@ -59,7 +59,7 @@ type GetJobResponse struct { Executions *ListJobExecutionsResponse `json:"Executions,omitempty"` } -// Normalize is used to33 canonicalize fields in the GetJobResponse. +// Normalize is used to canonicalize fields in the GetJobResponse. func (r *GetJobResponse) Normalize() { r.BaseGetResponse.Normalize() if r.Job != nil { diff --git a/pkg/publicapi/binder.go b/pkg/publicapi/binder.go new file mode 100644 index 0000000000..44212ffe5c --- /dev/null +++ b/pkg/publicapi/binder.go @@ -0,0 +1,33 @@ +package publicapi + +import ( + "github.com/labstack/echo/v4" +) + +// normalizable is an interface that defines a Normalize method. +type normalizable interface { + Normalize() +} + +// NormalizeBinder is a custom binder that extends the default binder with normalization. +type NormalizeBinder struct { + defaultBinder echo.Binder +} + +// NewNormalizeBinder creates a new NormalizeBinder with the default binder. +func NewNormalizeBinder() *NormalizeBinder { + return &NormalizeBinder{ + defaultBinder: &echo.DefaultBinder{}, + } +} + +// Bind binds and validates the request body, then normalizes if it implements the normalizable interface. +func (cb *NormalizeBinder) Bind(i interface{}, c echo.Context) error { + if err := cb.defaultBinder.Bind(i, c); err != nil { + return err + } + if normalizer, ok := i.(normalizable); ok { + normalizer.Normalize() + } + return nil +} diff --git a/pkg/publicapi/binder_test.go b/pkg/publicapi/binder_test.go new file mode 100644 index 0000000000..b441b53f97 --- /dev/null +++ b/pkg/publicapi/binder_test.go @@ -0,0 +1,86 @@ +//go:build unit || !integration + +package publicapi + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/suite" +) + +// Mock struct that implements normalizable interface +type mockNormalizableRequest struct { + Data string `json:"data" validate:"required"` +} + +func (mr *mockNormalizableRequest) Normalize() { + mr.Data = strings.TrimSpace(mr.Data) +} + +// Mock struct that does not implement normalizable interface +type mockNonNormalizableRequest struct { + Data string `json:"data" validate:"required"` +} + +// TestSuite struct for NormalizeBinder +type NormalizeBinderTestSuite struct { + suite.Suite + e *echo.Echo + binder *NormalizeBinder + rec *httptest.ResponseRecorder +} + +// SetupTest sets up the test environment +func (s *NormalizeBinderTestSuite) SetupTest() { + s.e = echo.New() + s.binder = NewNormalizeBinder() + s.rec = httptest.NewRecorder() +} + +// TestBindWithNormalization tests binding with normalization +func (s *NormalizeBinderTestSuite) TestBindWithNormalization() { + echoContext := s.mockRequest(`{"data": " some data "}`) + mockReq := new(mockNormalizableRequest) + err := s.binder.Bind(mockReq, echoContext) + + s.NoError(err) + s.Equal("some data", mockReq.Data) +} + +// TestBindWithoutNormalization tests binding without normalization +func (s *NormalizeBinderTestSuite) TestBindWithoutNormalization() { + echoContext := s.mockRequest(`{"data": " some data "}`) + + mockReq := new(mockNonNormalizableRequest) + err := s.binder.Bind(mockReq, echoContext) + + s.NoError(err) + s.Equal(" some data ", mockReq.Data) +} + +// TestBindWithBadJSON tests binding with bad JSON +func (s *NormalizeBinderTestSuite) TestBindWithBadJSON() { + echoContext := s.mockRequest(`{"data": " some data "`) + + mockReq := new(mockNormalizableRequest) + err := s.binder.Bind(mockReq, echoContext) + + s.Error(err) + s.Equal(400, err.(*echo.HTTPError).Code) + s.Equal("unexpected EOF", err.(*echo.HTTPError).Message) +} + +func (s *NormalizeBinderTestSuite) mockRequest(body string) echo.Context { + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + return s.e.NewContext(req, s.rec) +} + +// TestNormalizeBinderSuite runs the test suite +func TestNormalizeBinderSuite(t *testing.T) { + suite.Run(t, new(NormalizeBinderTestSuite)) +} diff --git a/pkg/publicapi/server.go b/pkg/publicapi/server.go index 37e16e3c25..e4ebcc4fdd 100644 --- a/pkg/publicapi/server.go +++ b/pkg/publicapi/server.go @@ -86,7 +86,8 @@ func NewAPIServer(params ServerParams) (*Server, error) { "/requester/websocket/events": "/api/v1/requester/websocket/events", } - // set validator + // set custom binders and validators + server.Router.Binder = NewNormalizeBinder() server.Router.Validator = NewCustomValidator() // enable debug mode to get clearer error messages