From d8b76970b484365a618ed9095d3d13e64e0d3b64 Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Tue, 14 May 2024 10:22:43 +0200 Subject: [PATCH] Validation: fix reflect error on nil arrays --- util/walk/walk_test.go | 50 +++++++++++++++++++++++++++++++++--- validation/validator.go | 3 +++ validation/validator_test.go | 39 ++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/util/walk/walk_test.go b/util/walk/walk_test.go index ed16b6fe..59095a03 100644 --- a/util/walk/walk_test.go +++ b/util/walk/walk_test.go @@ -256,7 +256,7 @@ func TestMustParse(t *testing.T) { }) } -func testWalk(t *testing.T, data map[string]any, p string) []*Context { +func testWalk(t *testing.T, data any, p string) []*Context { matches := make([]*Context, 0, 5) path, err := Parse(p) @@ -877,7 +877,7 @@ func TestPathWalkWildcardEmptyObject(t *testing.T) { } func TestPathWalkEmptyArray(t *testing.T) { - data := map[string]any{ + var data any = map[string]any{ "array": []string{}, "narray": [][][]string{}, } @@ -885,7 +885,7 @@ func TestPathWalkEmptyArray(t *testing.T) { expected := []*Context{ { Value: nil, - Parent: data["array"], + Parent: data.(map[string]any)["array"], Name: "", Path: &Path{ Name: strPtr("array"), @@ -904,7 +904,7 @@ func TestPathWalkEmptyArray(t *testing.T) { expected = []*Context{ { Value: nil, - Parent: data["narray"], + Parent: data.(map[string]any)["narray"], Name: "", Path: &Path{ Name: strPtr("narray"), @@ -916,6 +916,48 @@ func TestPathWalkEmptyArray(t *testing.T) { }, } assert.Equal(t, expected, matches) + + // nil array + data = map[string]any{ + "array": nil, + } + expected = []*Context{ + { + Value: nil, + Parent: data, + Name: "array", + Path: &Path{ + Name: strPtr("array"), + Type: PathTypeElement, + Next: nil, + }, + Index: -1, + Found: ParentNotFound, + }, + } + + matches = testWalk(t, data, "array[]") + assert.Equal(t, expected, matches) + + // array is the root element + data = []any{} + expected = []*Context{ + { + Value: nil, + Parent: data, + Name: "", + Path: &Path{ + Name: nil, + Type: PathTypeArray, + Next: &Path{}, + }, + Index: -1, + Found: ElementNotFound, + }, + } + + matches = testWalk(t, data, "[]") + assert.Equal(t, expected, matches) } func TestPathWalkNotFoundInObject(t *testing.T) { diff --git a/validation/validator.go b/validation/validator.go index 94c5f213..a2dcecd2 100644 --- a/validation/validator.go +++ b/validation/validator.go @@ -520,6 +520,9 @@ func makeGenericSlice(original any) ([]any, bool) { return o, false } list := reflect.ValueOf(original) + if !list.IsValid() { + return []any{}, false + } length := list.Len() newSlice := make([]any, 0, length) for i := 0; i < length; i++ { diff --git a/validation/validator_test.go b/validation/validator_test.go index 043d7d9e..80d507bb 100644 --- a/validation/validator_test.go +++ b/validation/validator_test.go @@ -716,6 +716,45 @@ func TestValidate(t *testing.T) { }, wantData: map[string]any{"property": 123, "object": map[string]any{"property": 456}, "array": []int{7}, "narray": [][]int{{1, 8, 3}}}, }, + { + desc: "empty_array", + options: &Options{ + Data: map[string]any{"narray": []any{}}, + Language: lang.New().GetDefault(), + Rules: RuleSet{ + {Path: "narray", Rules: List{Required(), Array()}}, + {Path: "narray[]", Rules: List{Array()}}, + {Path: "narray[][]", Rules: List{Int()}}, + }, + }, + wantData: map[string]any{"narray": []any{}}, + }, + { + desc: "empty_narray", + options: &Options{ + Data: map[string]any{"narray": []any{[]any{}}}, + Language: lang.New().GetDefault(), + Rules: RuleSet{ + {Path: "narray", Rules: List{Required(), Array()}}, + {Path: "narray[]", Rules: List{Array()}}, + {Path: "narray[][]", Rules: List{Int()}}, + }, + }, + wantData: map[string]any{"narray": [][]any{{}}}, + }, + { + desc: "nil_array", + options: &Options{ + Data: map[string]any{"narray": nil}, + Language: lang.New().GetDefault(), + Rules: RuleSet{ + {Path: "narray", Rules: List{Required(), Nullable(), Array()}}, + {Path: "narray[]", Rules: List{Required(), Array()}}, + {Path: "narray[][]", Rules: List{Int()}}, + }, + }, + wantData: map[string]any{"narray": nil}, + }, { desc: "type-dependent", options: &Options{