feat: Append 方法支持 R 接口包装的指针类型
增强 setValue 函数,支持将 R 接口包装的指针值追加到非指针类型的切片中。当源值是指针但目标不是指针时,自动解引用后再赋值,使得 New(&item) 可以追加到 []Item 类型的切片。同时统一使用 Raw() 方法替代 Value() 方法名。
This commit is contained in:
parent
f5f261e541
commit
bacec92841
11
rfx.go
11
rfx.go
@ -289,6 +289,17 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果源值是指针但目标不是指针,尝试解引用后再赋值
|
||||
if val.Kind() == reflect.Ptr && !val.IsNil() && targetType.Kind() != reflect.Ptr {
|
||||
derefVal := val.Elem()
|
||||
if derefVal.Type().AssignableTo(targetType) {
|
||||
field.Set(derefVal)
|
||||
return true
|
||||
}
|
||||
// 解引用后继续使用下面的逻辑处理
|
||||
val = derefVal
|
||||
}
|
||||
|
||||
switch targetType.Kind() {
|
||||
case reflect.Ptr:
|
||||
// 处理指针类型
|
||||
|
||||
247
rfx_append_test.go
Normal file
247
rfx_append_test.go
Normal file
@ -0,0 +1,247 @@
|
||||
package reflux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestAppendWithRInterface 测试 Append 方法对 R 接口的支持
|
||||
func TestAppendWithRInterface(t *testing.T) {
|
||||
t.Run("Append R interface to slice", func(t *testing.T) {
|
||||
type Item struct {
|
||||
Name string
|
||||
Value int
|
||||
}
|
||||
|
||||
items := []Item{}
|
||||
rfx := New(&items)
|
||||
|
||||
// 创建 R 实例
|
||||
item1 := Item{Name: "apple", Value: 10}
|
||||
r1 := New(&item1)
|
||||
|
||||
item2 := Item{Name: "banana", Value: 20}
|
||||
r2 := New(item2)
|
||||
|
||||
// 使用 Append 添加 R 接口
|
||||
rfx.Append(r1, r2)
|
||||
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Expected 2 items, got %d", len(items))
|
||||
}
|
||||
|
||||
if items[0].Name != "apple" || items[0].Value != 10 {
|
||||
t.Errorf("Item[0] mismatch: got %+v", items[0])
|
||||
}
|
||||
|
||||
if items[1].Name != "banana" || items[1].Value != 20 {
|
||||
t.Errorf("Item[1] mismatch: got %+v", items[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Append mixed types including R interface", func(t *testing.T) {
|
||||
type Number struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
numbers := []int{}
|
||||
rfx := New(&numbers)
|
||||
|
||||
// 创建 R 实例包装 map(使用 map 代替基本类型)
|
||||
numMap := map[string]int{"value": 42}
|
||||
rNum := New(&numMap)
|
||||
|
||||
// 混合添加普通值和通过 Get 提取的值
|
||||
rfx.Append(10, rNum.Get("value").Int(), 30)
|
||||
|
||||
if len(numbers) != 3 {
|
||||
t.Fatalf("Expected 3 numbers, got %d", len(numbers))
|
||||
}
|
||||
|
||||
if numbers[0] != 10 || numbers[1] != 42 || numbers[2] != 30 {
|
||||
t.Errorf("Numbers mismatch: got %v", numbers)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Append with nested R interface", func(t *testing.T) {
|
||||
type Address struct {
|
||||
City string
|
||||
}
|
||||
|
||||
addresses := []Address{}
|
||||
rfx := New(&addresses)
|
||||
|
||||
// 创建嵌套的 R 实例
|
||||
addr := Address{City: "Beijing"}
|
||||
rAddr := New(&addr)
|
||||
|
||||
rfx.Append(rAddr)
|
||||
|
||||
if len(addresses) != 1 {
|
||||
t.Fatalf("Expected 1 address, got %d", len(addresses))
|
||||
}
|
||||
|
||||
if addresses[0].City != "Beijing" {
|
||||
t.Errorf("Expected City=Beijing, got %s", addresses[0].City)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Append map through R interface", func(t *testing.T) {
|
||||
type Config map[string]string
|
||||
|
||||
configs := []Config{}
|
||||
rfx := New(&configs)
|
||||
|
||||
// 创建 map 的 R 实例
|
||||
cfg := Config{"host": "localhost", "port": "8080"}
|
||||
rCfg := New(&cfg)
|
||||
|
||||
rfx.Append(rCfg)
|
||||
|
||||
if len(configs) != 1 {
|
||||
t.Fatalf("Expected 1 config, got %d", len(configs))
|
||||
}
|
||||
|
||||
if configs[0]["host"] != "localhost" || configs[0]["port"] != "8080" {
|
||||
t.Errorf("Config mismatch: got %+v", configs[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestSetWithAutoInit 测试 Set 方法的自动初始化功能
|
||||
func TestSetWithAutoInit(t *testing.T) {
|
||||
t.Run("Auto init nil map", func(t *testing.T) {
|
||||
type Container struct {
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
container := Container{}
|
||||
rfx := New(&container)
|
||||
|
||||
// 设置 nil map 的值,应该自动初始化
|
||||
rfx.Set("Data.key1", "value1")
|
||||
rfx.Set("Data.key2", "value2")
|
||||
|
||||
if container.Data == nil {
|
||||
t.Fatal("Expected Data to be initialized, got nil")
|
||||
}
|
||||
|
||||
if container.Data["key1"] != "value1" {
|
||||
t.Errorf("Expected key1=value1, got %s", container.Data["key1"])
|
||||
}
|
||||
|
||||
if container.Data["key2"] != "value2" {
|
||||
t.Errorf("Expected key2=value2, got %s", container.Data["key2"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Auto init nested nil map", func(t *testing.T) {
|
||||
type Nested struct {
|
||||
Meta map[string]any
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
Config Nested
|
||||
}
|
||||
|
||||
root := Root{}
|
||||
rfx := New(&root)
|
||||
|
||||
// 设置嵌套的 nil map
|
||||
rfx.Set("Config.Meta.host", "localhost")
|
||||
rfx.Set("Config.Meta.port", 8080)
|
||||
|
||||
if root.Config.Meta == nil {
|
||||
t.Fatal("Expected Meta to be initialized, got nil")
|
||||
}
|
||||
|
||||
if root.Config.Meta["host"] != "localhost" {
|
||||
t.Errorf("Expected host=localhost, got %v", root.Config.Meta["host"])
|
||||
}
|
||||
|
||||
if root.Config.Meta["port"] != 8080 {
|
||||
t.Errorf("Expected port=8080, got %v", root.Config.Meta["port"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Auto init map with R interface value", func(t *testing.T) {
|
||||
type Settings struct {
|
||||
Options map[string]int
|
||||
}
|
||||
|
||||
settings := Settings{}
|
||||
rfx := New(&settings)
|
||||
|
||||
// 使用 R 接口包装的值(使用 map 而非基本类型)
|
||||
timeoutData := map[string]int{"value": 30}
|
||||
rTimeout := New(&timeoutData)
|
||||
|
||||
// 通过 Get 提取值再设置
|
||||
rfx.Set("Options.timeout", rTimeout.Get("value").Int())
|
||||
|
||||
if settings.Options == nil {
|
||||
t.Fatal("Expected Options to be initialized, got nil")
|
||||
}
|
||||
|
||||
if settings.Options["timeout"] != 30 {
|
||||
t.Errorf("Expected timeout=30, got %d", settings.Options["timeout"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Set to uninitialized map in nested structure", func(t *testing.T) {
|
||||
type Database struct {
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DB Database
|
||||
}
|
||||
|
||||
config := Config{
|
||||
DB: Database{}, // Params is nil
|
||||
}
|
||||
|
||||
rfx := New(&config)
|
||||
|
||||
// 设置未初始化的 map
|
||||
rfx.Set("DB.Params.host", "localhost")
|
||||
rfx.Set("DB.Params.port", "5432")
|
||||
|
||||
if config.DB.Params == nil {
|
||||
t.Fatal("Expected Params to be initialized, got nil")
|
||||
}
|
||||
|
||||
if config.DB.Params["host"] != "localhost" {
|
||||
t.Errorf("Expected host=localhost, got %s", config.DB.Params["host"])
|
||||
}
|
||||
|
||||
if config.DB.Params["port"] != "5432" {
|
||||
t.Errorf("Expected port=5432, got %s", config.DB.Params["port"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Set complex value to auto-initialized map", func(t *testing.T) {
|
||||
type Item struct {
|
||||
Name string
|
||||
Count int
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
Items map[string]Item
|
||||
}
|
||||
|
||||
store := Store{}
|
||||
rfx := New(&store)
|
||||
|
||||
// 设置复杂类型到未初始化的 map
|
||||
item := Item{Name: "apple", Count: 10}
|
||||
rfx.Set("Items.fruit", item)
|
||||
|
||||
if store.Items == nil {
|
||||
t.Fatal("Expected Items to be initialized, got nil")
|
||||
}
|
||||
|
||||
if store.Items["fruit"].Name != "apple" || store.Items["fruit"].Count != 10 {
|
||||
t.Errorf("Expected Item{Name:apple, Count:10}, got %+v", store.Items["fruit"])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -802,12 +802,12 @@ func TestAny(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestValue 测试 Value 方法
|
||||
func TestValue(t *testing.T) {
|
||||
// TestRaw 测试 Value 方法
|
||||
func TestRaw(t *testing.T) {
|
||||
p := Person{Name: "Henry"}
|
||||
rfx := New(&p)
|
||||
|
||||
val := rfx.Value()
|
||||
val := rfx.Raw()
|
||||
if !val.IsValid() {
|
||||
t.Error("Value() returned invalid value")
|
||||
}
|
||||
|
||||
6
util.go
6
util.go
@ -332,7 +332,7 @@ func normalizeInputValue(v any) (reflect.Value, bool, error) {
|
||||
|
||||
if vv, ok := v.(R); ok {
|
||||
isPtr = true
|
||||
rv = vv.Value()
|
||||
rv = vv.Raw()
|
||||
} else if vv, ok := v.([]R); ok {
|
||||
// 支持直接传入 []R,自动转换为底层切片
|
||||
var elemType reflect.Type
|
||||
@ -340,7 +340,7 @@ func normalizeInputValue(v any) (reflect.Value, bool, error) {
|
||||
if it == nil {
|
||||
continue
|
||||
}
|
||||
val := it.Value()
|
||||
val := it.Raw()
|
||||
if val.IsValid() {
|
||||
elemType = val.Type()
|
||||
break
|
||||
@ -358,7 +358,7 @@ func normalizeInputValue(v any) (reflect.Value, bool, error) {
|
||||
if it == nil {
|
||||
continue
|
||||
}
|
||||
val := it.Value()
|
||||
val := it.Raw()
|
||||
if !val.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user