Skip to content

Commit 3b00c3a

Browse files
committed
httpserver, funk: handle validations and provide methods to box/unbox slice elements;
1 parent 59b8e35 commit 3b00c3a

File tree

6 files changed

+72
-12
lines changed

6 files changed

+72
-12
lines changed

pkg/funk/slice.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ func CastSlice[T any, I ~[]any](sl I) ([]T, error) {
2727
return result, nil
2828
}
2929

30+
func BoxSlice[T any](sl []T) []*T {
31+
return Map(sl, mdl.Box)
32+
}
33+
34+
func UnboxSlice[T any](sl []*T) []T {
35+
return Map(sl, mdl.EmptyIfNil)
36+
}
37+
3038
func Partition[S ~[]T, T any, E comparable, F func(T) E](sl S, keyer F) map[E][]T {
3139
result := make(map[E][]T)
3240

pkg/funk/slice_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/justtrackio/gosoline/pkg/funk"
8+
"github.com/justtrackio/gosoline/pkg/mdl"
89
"github.com/stretchr/testify/assert"
910
)
1011

@@ -18,6 +19,33 @@ func TestCastSlice(t *testing.T) {
1819
assert.Equal(t, expectedSlice, target)
1920
}
2021

22+
func TestBoxSlice(t *testing.T) {
23+
inputSlice := []string{"", ""}
24+
expectedSlice := []*string{mdl.Box(""), mdl.Box("")}
25+
26+
target := funk.BoxSlice(inputSlice)
27+
28+
assert.Equal(t, expectedSlice, target)
29+
}
30+
31+
func TestUnboxSlice(t *testing.T) {
32+
inputSlice := []*string{mdl.Box(""), mdl.Box("")}
33+
expectedSlice := []string{"", ""}
34+
35+
target := funk.UnboxSlice(inputSlice)
36+
37+
assert.Equal(t, expectedSlice, target)
38+
}
39+
40+
func TestUnboxSlice_EmptyValue(t *testing.T) {
41+
inputSlice := []*string{nil, nil}
42+
expectedSlice := []string{"", ""}
43+
44+
target := funk.UnboxSlice(inputSlice)
45+
46+
assert.Equal(t, expectedSlice, target)
47+
}
48+
2149
func TestContains(t *testing.T) {
2250
type test struct {
2351
Foo string

pkg/httpserver/crud/errors.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ import (
1111
"github.com/justtrackio/gosoline/pkg/exec"
1212
"github.com/justtrackio/gosoline/pkg/httpserver"
1313
"github.com/justtrackio/gosoline/pkg/log"
14-
"github.com/justtrackio/gosoline/pkg/validation"
1514
)
1615

1716
var ErrModelNotChanged = fmt.Errorf("nothing has changed on model")
1817

19-
// HandleErrorOnWrite handles errors for read operations.
18+
// HandleErrorOnRead handles errors for read operations.
2019
// Covers many default errors and responses like
2120
// - context.Canceled, context.DeadlineExceed -> HTTP 499
2221
// - dbRepo.RecordNotFoundError | dbRepo.NoQueryResultsError -> HTTP 404
23-
// - validation.Error -> HTTP 400
2422
func HandleErrorOnRead(logger log.Logger, err error) (*httpserver.Response, error) {
2523
if exec.IsRequestCanceled(err) {
2624
logger.Info("read model(s) aborted: %s", err.Error())
@@ -34,10 +32,6 @@ func HandleErrorOnRead(logger log.Logger, err error) (*httpserver.Response, erro
3432
return httpserver.NewStatusResponse(http.StatusNotFound), nil
3533
}
3634

37-
if errors.Is(err, &validation.Error{}) {
38-
return httpserver.GetErrorHandler()(http.StatusBadRequest, err), nil
39-
}
40-
4135
// rely on the outside handling of access forbidden and HTTP 500
4236
return nil, err
4337
}
@@ -48,7 +42,6 @@ func HandleErrorOnRead(logger log.Logger, err error) (*httpserver.Response, erro
4842
// - dbRepo.RecordNotFoundError | dbRepo.NoQueryResultsError -> HTTP 404
4943
// - ErrModelNotChanged -> HTTP 304
5044
// - db.IsDuplicateEntryError -> HTTP 409
51-
// - validation.Error -> HTTP 400
5245
func HandleErrorOnWrite(ctx context.Context, logger log.Logger, err error) (*httpserver.Response, error) {
5346
logger = logger.WithContext(ctx)
5447

@@ -74,10 +67,6 @@ func HandleErrorOnWrite(ctx context.Context, logger log.Logger, err error) (*htt
7467
return httpserver.NewStatusResponse(http.StatusConflict), nil
7568
}
7669

77-
if errors.Is(err, &validation.Error{}) {
78-
return httpserver.GetErrorHandler()(http.StatusBadRequest, err), nil
79-
}
80-
8170
// rely on the outside handling of access forbidden and HTTP 500
8271
return nil, err
8372
}

pkg/httpserver/handler.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/imdario/mergo"
1515
"github.com/justtrackio/gosoline/pkg/coffin"
1616
"github.com/justtrackio/gosoline/pkg/exec"
17+
"github.com/justtrackio/gosoline/pkg/validation"
1718
"github.com/pkg/errors"
1819
"github.com/spf13/cast"
1920
)
@@ -375,6 +376,17 @@ func handle(ginCtx *gin.Context, handler HandlerWithoutInput, input any, errHand
375376
return
376377
}
377378

379+
validErr := &validation.Error{}
380+
if errors.As(err, &validErr) {
381+
handleError(ginCtx, errHandler, http.StatusBadRequest, gin.Error{
382+
// only pass the validation error in case it is wrapped in something else
383+
Err: validErr,
384+
Type: gin.ErrorTypePrivate,
385+
})
386+
387+
return
388+
}
389+
378390
if err != nil {
379391
handleError(ginCtx, errHandler, http.StatusInternalServerError, gin.Error{
380392
Err: err,

pkg/httpserver/handler_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/justtrackio/gosoline/pkg/httpserver"
1313
"github.com/justtrackio/gosoline/pkg/httpserver/mocks"
1414
"github.com/justtrackio/gosoline/pkg/httpserver/testdata"
15+
"github.com/justtrackio/gosoline/pkg/validation"
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/mock"
1718
"google.golang.org/protobuf/proto"
@@ -122,6 +123,17 @@ func TestCreateHandler_RequestCanceled(t *testing.T) {
122123
assert.Equal(t, `{"err":"context canceled"}`, response.Body.String())
123124
}
124125

126+
func TestCreateHandler_ValidationError(t *testing.T) {
127+
requestHandler := mocks.NewHandlerWithoutInput(t)
128+
requestHandler.EXPECT().Handle(mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*httpserver.Request")).Return(nil, fmt.Errorf("wrap: %w", validation.NewError(fmt.Errorf("error"))))
129+
130+
handler := httpserver.CreateHandler(requestHandler)
131+
response := httpserver.HttpTest("GET", "/action", "/action", `{"text":"foobar"}`, handler)
132+
133+
assert.Equal(t, 400, response.Code)
134+
assert.Equal(t, `{"err":"validation: error"}`, response.Body.String())
135+
}
136+
125137
func TestCreateIoHandler_InputFailure(t *testing.T) {
126138
handler := httpserver.CreateJsonHandler(JsonHandler{})
127139
response := httpserver.HttpTest("PUT", "/action", "/action", `{}`, handler)

pkg/validation/error.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validation
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67
)
@@ -33,3 +34,13 @@ func (e *Error) As(target interface{}) bool {
3334

3435
return ok
3536
}
37+
38+
func NewError(errs ...error) *Error {
39+
return &Error{Errors: errs}
40+
}
41+
42+
func IsValidationError(err error) bool {
43+
validErr := &Error{}
44+
45+
return errors.As(err, &validErr)
46+
}

0 commit comments

Comments
 (0)