From bacec9284138111e0ab1c5f8fa3a30dbcb980e3e Mon Sep 17 00:00:00 2001 From: what Date: Mon, 8 Dec 2025 17:24:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Append=20=E6=96=B9=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20R=20=E6=8E=A5=E5=8F=A3=E5=8C=85=E8=A3=85=E7=9A=84?= =?UTF-8?q?=E6=8C=87=E9=92=88=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增强 setValue 函数,支持将 R 接口包装的指针值追加到非指针类型的切片中。当源值是指针但目标不是指针时,自动解引用后再赋值,使得 New(&item) 可以追加到 []Item 类型的切片。同时统一使用 Raw() 方法替代 Value() 方法名。 --- rfx.go | 11 ++ rfx_append_test.go | 247 +++++++++++++++++++++++++++++++++++++++++++++ rfx_test.go | 6 +- util.go | 6 +- 4 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 rfx_append_test.go diff --git a/rfx.go b/rfx.go index 5a89a03..4ee22af 100644 --- a/rfx.go +++ b/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: // 处理指针类型 diff --git a/rfx_append_test.go b/rfx_append_test.go new file mode 100644 index 0000000..ef6dcc1 --- /dev/null +++ b/rfx_append_test.go @@ -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"]) + } + }) +} diff --git a/rfx_test.go b/rfx_test.go index 11b541d..b22ddb0 100644 --- a/rfx_test.go +++ b/rfx_test.go @@ -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") } diff --git a/util.go b/util.go index e10e2d9..9d49563 100644 --- a/util.go +++ b/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 }