From 4a41f7acea9ac0670f0107dd63209b8d23b675a5 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Tue, 12 Sep 2023 18:55:31 +0200 Subject: [PATCH] feat: suuport slices for set #4 --- .idea/markdown.xml | 9 ++++++ .idea/misc.xml | 6 ++++ .idea/modules.xml | 8 ++++++ .idea/pathfinder.iml | 10 +++++++ .idea/vcs.xml | 6 ++++ get.go | 5 +++- go.mod | 1 + go.sum | 2 ++ issue_2_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++-- set.go | 66 +++++++++++++++++++++++++++++++++++-------- 10 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 .idea/markdown.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pathfinder.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..ec0b30f --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="MarkdownSettings"> + <enabledExtensions> + <entry key="MermaidLanguageExtension" value="false" /> + <entry key="PlantUMLLanguageExtension" value="true" /> + </enabledExtensions> + </component> +</project> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..921c18f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/pathfinder.iml" filepath="$PROJECT_DIR$/.idea/pathfinder.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml new file mode 100644 index 0000000..25ed3f6 --- /dev/null +++ b/.idea/pathfinder.iml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="Go" enabled="true" /> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/get.go b/get.go index c8f6603..90c2b1c 100644 --- a/get.go +++ b/get.go @@ -9,7 +9,10 @@ import ( "strings" ) -// This function returns the value of a field in a struct, given a path to the field. +// GetValue returns the value of a field in a struct, given a path to the field. +// The path can contain dots to access nested fields. +// The object must be a pointer to a struct, a struct, a map, a slice or an array, +// otherwise an error is returned. func GetValue[D any](obj D, keyWithDots string) (any, error) { keySlice := strings.Split(keyWithDots, ".") v := reflect.ValueOf(obj) diff --git a/go.mod b/go.mod index 047406f..f1f0841 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.8.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index b410979..0a45411 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/issue_2_test.go b/issue_2_test.go index 4be4bec..1e3ef82 100644 --- a/issue_2_test.go +++ b/issue_2_test.go @@ -5,6 +5,7 @@ package pathfinder import ( "fmt" + "github.com/google/gofuzz" "github.com/stretchr/testify/assert" "testing" ) @@ -22,7 +23,7 @@ type Issue2Struct struct { } func TestGetValueWithArray(t *testing.T) { - invalidValue := Issue2Struct{ + testStructForGet := Issue2Struct{ Issue1Sub1: Issue2SubStruct{ Issue2SubField: Issue2SubSubStruct{ Issue2SubSubField: []string{"test0", "test1", "test2"}, @@ -32,16 +33,76 @@ func TestGetValueWithArray(t *testing.T) { // iterate from 0 to 2 for i := 0; i < 2; i++ { - result, err := GetValue(invalidValue, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) + result, err := GetValue(testStructForGet, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) assert.Nil(t, err) assert.Equal(t, result, "test"+fmt.Sprintf("%d", i)) } i := 3 - result, err := GetValue(invalidValue, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) + result, err := GetValue(testStructForGet, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) assert.NotNil(t, err) assert.Nil(t, result) } + +func TestGetValueWithArrayFuzz(t *testing.T) { + f := fuzz.New() + + testStructForGet := Issue2Struct{ + Issue1Sub1: Issue2SubStruct{ + Issue2SubField: Issue2SubSubStruct{ + Issue2SubSubField: []string{"test0", "test1", "test2"}, + }, + }, + } + + for i := 0; i < 100; i++ { + var randomIndex int + f.Fuzz(&randomIndex) + randomIndex = randomIndex % len(testStructForGet.Issue1Sub1.Issue2SubField.Issue2SubSubField) + if randomIndex < 0 { + randomIndex = -randomIndex + } + + result, err := GetValue(testStructForGet, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", randomIndex)) + + if randomIndex < 3 { + assert.Nil(t, err) + assert.Equal(t, result, "test"+fmt.Sprintf("%d", randomIndex)) + } else { + assert.NotNil(t, err) + assert.Nil(t, result) + } + } +} + +func TestSetValueWithArray(t *testing.T) { + testStructForSet := Issue2Struct{ + Issue1Sub1: Issue2SubStruct{ + Issue2SubField: Issue2SubSubStruct{ + Issue2SubSubField: []string{"test0", "test1", "test2"}, + }, + }, + } + + // iterate from 0 to 2 + for i := 0; i < 2; i++ { + + newValue := "test~~" + fmt.Sprintf("%d", i) + k := "Issue1Sub1.Issue2SubField.Issue2SubSubField." + fmt.Sprintf("%d", i) + err := SetValue(&testStructForSet, k, newValue) + + assert.Nil(t, err) + + result, err := GetValue(testStructForSet, k) + assert.Equal(t, result, newValue) + } + + i := 3 + k := "Issue1Sub1.Issue2SubField.Issue2SubSubField." + fmt.Sprintf("%d", i) + err := SetValue(testStructForSet, k, "test3") + assert.NotNil(t, err) + +} diff --git a/set.go b/set.go index f5ca2f4..fa7443a 100644 --- a/set.go +++ b/set.go @@ -10,7 +10,8 @@ import ( "strings" ) -// This function sets the value of a field in a struct, given a path to the field. +// SetValue sets the value of a field in a struct, given a path to the field. +// The object must be a pointer to a struct, otherwise an error is returned. func SetValue[D any](obj D, keyWithDots string, newValue any) error { keySlice := strings.Split(keyWithDots, ".") @@ -21,7 +22,13 @@ func SetValue[D any](obj D, keyWithDots string, newValue any) error { if v.Kind() == reflect.Invalid { return newInvalidPathError(keyWithDots) } - v = v.Addr() + + if v.CanAddr() { + v = v.Addr() + } else { + return newCannotSetError(keyWithDots) + } + } if v.Kind() != reflect.Ptr { @@ -46,20 +53,37 @@ func SetValue[D any](obj D, keyWithDots string, newValue any) error { } // non-supporter type at the top of the path - if v.Kind() != reflect.Struct { + switch v.Kind() { + case reflect.Struct: + + v = v.FieldByName(keySlice[len(keySlice)-1]) + if !v.IsValid() { + return newInvalidPathError(keyWithDots) + } + + if !v.CanSet() { + return newCannotSetError(keyWithDots) + } + + case reflect.Map: return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) - } + case reflect.Slice: - v = v.FieldByName(keySlice[len(keySlice)-1]) - if !v.IsValid() { - return newInvalidPathError(keyWithDots) - } + // index is a number and get v from slice with index + index, err := strconv.Atoi(keySlice[len(keySlice)-1]) + if err != nil { + return newInvalidPathError(keyWithDots) + } - if !v.CanSet() { - return newCannotSetError(keyWithDots) - } + // index out of range + if index >= v.Len() { + return newInvalidPathError(keyWithDots) + } - switch v.Kind() { + v = v.Index(index) + + case reflect.Array: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) case reflect.Ptr: if newValue == nil { v.Set(reflect.Zero(v.Type())) @@ -67,6 +91,24 @@ func SetValue[D any](obj D, keyWithDots string, newValue any) error { v.Set(reflect.ValueOf(&newValue)) } return nil + case reflect.Interface: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Chan: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Func: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.UnsafePointer: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Uintptr: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Complex64: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Complex128: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + case reflect.Invalid: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) + default: + return newUnsupportedTypeAtTopOfPathError(keyWithDots, v.Type()) } newValueType := reflect.TypeOf(newValue) -- GitLab