Skip to content

Commit

Permalink
feat(validator): make validators return error
Browse files Browse the repository at this point in the history
ForStruct, ForStructWithContext, ForSlice and ForSliceWithContext validators may return an error when invalid data was provided.
  • Loading branch information
donatorsky committed May 11, 2023
1 parent 82e6627 commit 8f98c1e
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 22 deletions.
34 changes: 20 additions & 14 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ package validator

import (
"context"
"errors"
"reflect"

ve "github.com/donatorsky/go-validator/error"
vr "github.com/donatorsky/go-validator/rule"
)

var (
ErrNotStructType = errors.New("not a struct type")
ErrNotListType = errors.New("not an array or a slice type")
)

type RulesMap map[string][]vr.Rule

func ForMap(data map[string]any, rules RulesMap) ve.ErrorsBag {
func ForMap(data map[string]any, rules RulesMap) (ve.ErrorsBag, error) {
return ForMapWithContext(context.Background(), data, rules)
}

func ForMapWithContext(ctx context.Context, data map[string]any, rules RulesMap) ve.ErrorsBag {
func ForMapWithContext(ctx context.Context, data map[string]any, rules RulesMap) (ve.ErrorsBag, error) {
errorsBag := ve.NewErrorsBag()

for field, rules := range rules {
Expand All @@ -23,18 +29,18 @@ func ForMapWithContext(ctx context.Context, data map[string]any, rules RulesMap)
}
}

return errorsBag
return errorsBag, nil
}

func ForStruct(data any, rules RulesMap) ve.ErrorsBag {
func ForStruct(data any, rules RulesMap) (ve.ErrorsBag, error) {
return ForStructWithContext(context.Background(), data, rules)
}

func ForStructWithContext(ctx context.Context, data any, rules RulesMap) ve.ErrorsBag {
func ForStructWithContext(ctx context.Context, data any, rules RulesMap) (ve.ErrorsBag, error) {
data, _ = vr.Dereference(data)

if reflect.TypeOf(data).Kind() != reflect.Struct {
panic("not a struct")
return nil, ErrNotStructType
}

errorsBag := ve.NewErrorsBag()
Expand All @@ -45,18 +51,18 @@ func ForStructWithContext(ctx context.Context, data any, rules RulesMap) ve.Erro
}
}

return errorsBag
return errorsBag, nil
}

func ForSlice(data any, rules ...vr.Rule) ve.ErrorsBag {
func ForSlice(data any, rules ...vr.Rule) (ve.ErrorsBag, error) {
return ForSliceWithContext(context.Background(), data, rules...)
}

func ForSliceWithContext(ctx context.Context, data any, rules ...vr.Rule) ve.ErrorsBag {
func ForSliceWithContext(ctx context.Context, data any, rules ...vr.Rule) (ve.ErrorsBag, error) {
data, _ = vr.Dereference(data)

if kind := reflect.TypeOf(data).Kind(); kind != reflect.Slice && kind != reflect.Array {
panic("not a slice or an array")
return nil, ErrNotListType
}

errorsBag := ve.NewErrorsBag()
Expand All @@ -65,14 +71,14 @@ func ForSliceWithContext(ctx context.Context, data any, rules ...vr.Rule) ve.Err
applyRules(ctx, data, rules, fieldValue, errorsBag)
}

return errorsBag
return errorsBag, nil
}

func ForValue[In any](value In, rules ...vr.Rule) (errors []ve.ValidationError) {
func ForValue[In any](value In, rules ...vr.Rule) (errors []ve.ValidationError, _ error) {
return ForValueWithContext[In](context.Background(), value, rules...)
}

func ForValueWithContext[In any](ctx context.Context, value In, rules ...vr.Rule) (errors []ve.ValidationError) {
func ForValueWithContext[In any](ctx context.Context, value In, rules ...vr.Rule) (errors []ve.ValidationError, _ error) {
errorsBag := ve.NewErrorsBag()

applyRules(
Expand All @@ -86,7 +92,7 @@ func ForValueWithContext[In any](ctx context.Context, value In, rules ...vr.Rule
errorsBag,
)

return errorsBag.Get("_")
return errorsBag.Get("_"), nil
}

func applyRules(ctx context.Context, data any, rules []vr.Rule, fieldValue fieldValue, errorsBag ve.ErrorsBag) {
Expand Down
56 changes: 48 additions & 8 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestForMap(t *testing.T) {
)

// when
errorsBag := ForMap(data, mergeMaps(
errorsBag, err := ForMap(data, mergeMaps(
RulesMap{
"value": valueRuleMocks,
"slice": sliceRuleMocks,
Expand All @@ -67,6 +67,7 @@ func TestForMap(t *testing.T) {
))

// then
require.NoError(t, err)
require.Len(t, errorsBag, 10)

// "value" assertions
Expand Down Expand Up @@ -116,7 +117,7 @@ func TestForMapWithContext(t *testing.T) {
)

// when
errorsBag := ForMapWithContext(ctx, data, mergeMaps(
errorsBag, err := ForMapWithContext(ctx, data, mergeMaps(
RulesMap{
"value": valueRuleMocks,
"slice": sliceRuleMocks,
Expand All @@ -141,6 +142,7 @@ func TestForMapWithContext(t *testing.T) {
))

// then
require.NoError(t, err)
require.Len(t, errorsBag, 10)

// "value" assertions
Expand Down Expand Up @@ -195,7 +197,7 @@ func TestForStruct(t *testing.T) {
)

// when
errorsBag := ForStruct(data, mergeMaps(
errorsBag, err := ForStruct(data, mergeMaps(
RulesMap{
"value": valueRuleMocks,
"slice": sliceRuleMocks,
Expand All @@ -220,6 +222,7 @@ func TestForStruct(t *testing.T) {
))

// then
require.NoError(t, err)
require.Len(t, errorsBag, 10)

// "value" assertions
Expand Down Expand Up @@ -247,6 +250,14 @@ func TestForStruct(t *testing.T) {
require.True(t, assertErrorsBagContainsErrorsForField(t, errorsBag, []ve.ValidationError{vr.NewRequiredValidationError()}, "slice.foo"))
}

func TestForStruct_FailsWhenInvalidDataProvided(t *testing.T) {
// when
_, err := ForStruct(1, nil)

// then
require.ErrorIs(t, err, ErrNotStructType)
}

func TestForStructWithContext(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -269,7 +280,7 @@ func TestForStructWithContext(t *testing.T) {
)

// when
errorsBag := ForStructWithContext(ctx, data, mergeMaps(
errorsBag, err := ForStructWithContext(ctx, data, mergeMaps(
RulesMap{
"value": valueRuleMocks,
"slice": sliceRuleMocks,
Expand All @@ -294,6 +305,7 @@ func TestForStructWithContext(t *testing.T) {
))

// then
require.NoError(t, err)
require.Len(t, errorsBag, 10)

// "value" assertions
Expand Down Expand Up @@ -321,6 +333,14 @@ func TestForStructWithContext(t *testing.T) {
require.True(t, assertErrorsBagContainsErrorsForField(t, errorsBag, []ve.ValidationError{vr.NewRequiredValidationError()}, "slice.foo"))
}

func TestForStructWithContext_FailsWhenInvalidDataProvided(t *testing.T) {
// when
_, err := ForStructWithContext(context.TODO(), 1, nil)

// then
require.ErrorIs(t, err, ErrNotStructType)
}

func TestForSlice(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -338,15 +358,24 @@ func TestForSlice(t *testing.T) {
)

// when
validationErrors := ForSlice(data, valueRuleMocks["_.*"]...)
validationErrors, err := ForSlice(data, valueRuleMocks["_.*"]...)

// then
require.NoError(t, err)
require.Len(t, validationErrors, 3)
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["0"], "_.0"))
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["1"], "_.1"))
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["2"], "_.2"))
}

func TestForSlice_FailsWhenInvalidDataProvided(t *testing.T) {
// when
_, err := ForSlice(1)

// then
require.ErrorIs(t, err, ErrNotListType)
}

func TestForSliceWithContext(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -364,15 +393,24 @@ func TestForSliceWithContext(t *testing.T) {
)

// when
validationErrors := ForSliceWithContext(ctx, data, valueRuleMocks["_.*"]...)
validationErrors, err := ForSliceWithContext(ctx, data, valueRuleMocks["_.*"]...)

// then
require.NoError(t, err)
require.Len(t, validationErrors, 3)
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["0"], "_.0"))
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["1"], "_.1"))
require.True(t, assertErrorsBagContainsErrorsForField(t, sliceElementsValidationErrorsBag, validationErrors["2"], "_.2"))
}

func TestForSliceWithContext_FailsWhenInvalidDataProvided(t *testing.T) {
// when
_, err := ForSliceWithContext(context.TODO(), 1)

// then
require.ErrorIs(t, err, ErrNotListType)
}

func TestForValue(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -386,9 +424,10 @@ func TestForValue(t *testing.T) {
)

// when
validationErrors := ForValue(data, valueRuleMocks...)
validationErrors, err := ForValue(data, valueRuleMocks...)

// then
require.NoError(t, err)
require.Len(t, validationErrors, 2)
require.Equal(t, valueValidationErrorsBag.Get("*"), validationErrors)
}
Expand All @@ -406,9 +445,10 @@ func TestForValueWithContext(t *testing.T) {
)

// when
validationErrors := ForValueWithContext(ctx, data, valueRuleMocks...)
validationErrors, err := ForValueWithContext(ctx, data, valueRuleMocks...)

// then
require.NoError(t, err)
require.Len(t, validationErrors, 2)
require.Equal(t, valueValidationErrorsBag.Get("*"), validationErrors)
}
Expand Down

0 comments on commit 8f98c1e

Please sign in to comment.