feat: Append 方法支持 R 接口包装的指针类型

增强 setValue 函数,支持将 R 接口包装的指针值追加到非指针类型的切片中。当源值是指针但目标不是指针时,自动解引用后再赋值,使得 New(&item) 可以追加到 []Item 类型的切片。同时统一使用 Raw() 方法替代 Value() 方法名。
This commit is contained in:
what 2025-12-08 17:24:45 +08:00
parent f5f261e541
commit bacec92841
4 changed files with 264 additions and 6 deletions

11
rfx.go
View File

@ -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
View 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"])
}
})
}

View File

@ -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")
}

View File

@ -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
}