Skip to content
Snippets Groups Projects
Verified Commit 4a41f7ac authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat: suuport slices for set #4

parent 14521e24
No related branches found
No related tags found
No related merge requests found
<?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
<?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
<?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
<?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
<?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
......@@ -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)
......
......@@ -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
......
......@@ -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)
}
......@@ -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)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment