From 80573af0d87ec7854cd02cd718477b9f6a9d84a2 Mon Sep 17 00:00:00 2001 From: anlynn Date: Sun, 2 Mar 2025 19:10:50 +0800 Subject: [PATCH] feat: Add support for serialization of anonymous fields in HTTP client(httpc) (#4676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 李安琳 --- core/mapping/marshaler.go | 43 +++++++++++++--- core/mapping/marshaler_test.go | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/core/mapping/marshaler.go b/core/mapping/marshaler.go index fbe31a923..dbbe37085 100644 --- a/core/mapping/marshaler.go +++ b/core/mapping/marshaler.go @@ -13,6 +13,15 @@ const ( // Marshal marshals the given val and returns the map that contains the fields. // optional=another is not implemented, and it's hard to implement and not commonly used. +// support anonymous field, e.g.: +// +// type Foo struct { +// Token string `header:"token"` +// } +// type FooB struct { +// Foo +// Bar string `json:"bar"` +// } func Marshal(val any) (map[string]map[string]any, error) { ret := make(map[string]map[string]any) tp := reflect.TypeOf(val) @@ -69,15 +78,35 @@ func processMember(field reflect.StructField, value reflect.Value, val = fmt.Sprint(val) } - m, ok := collector[tag] - if ok { - m[key] = val - } else { - m = map[string]any{ - key: val, + if field.Anonymous { + anonCollector, err := Marshal(val) + if err != nil { + return err } + for anonTag, anonMap := range anonCollector { + for anonKey, anonVal := range anonMap { + m, ok := collector[anonTag] + if ok { + m[anonKey] = anonVal + } else { + m = map[string]any{ + anonKey: anonVal, + } + } + collector[anonTag] = m + } + } + } else { + m, ok := collector[tag] + if ok { + m[key] = val + } else { + m = map[string]any{ + key: val, + } + } + collector[tag] = m } - collector[tag] = m return nil } diff --git a/core/mapping/marshaler_test.go b/core/mapping/marshaler_test.go index 176d3a37f..ba26a0db1 100644 --- a/core/mapping/marshaler_test.go +++ b/core/mapping/marshaler_test.go @@ -27,6 +27,99 @@ func TestMarshal(t *testing.T) { assert.True(t, m[emptyTag]["Anonymous"].(bool)) } +func TestMarshal_Anonymous(t *testing.T) { + type BaseHeader struct { + Token string `header:"token"` + } + v := struct { + Name string `json:"name"` + Address string `json:"address,options=[beijing,shanghai]"` + Age int `json:"age"` + BaseHeader + }{ + Name: "kevin", + Address: "shanghai", + Age: 20, + BaseHeader: BaseHeader{ + Token: "token_xxx", + }, + } + m, err := Marshal(v) + assert.Nil(t, err) + assert.Equal(t, "kevin", m["json"]["name"]) + assert.Equal(t, "shanghai", m["json"]["address"]) + assert.Equal(t, 20, m["json"]["age"].(int)) + assert.Equal(t, "token_xxx", m["header"]["token"]) + + v1 := struct { + Name string `json:"name"` + Address string `json:"address,options=[beijing,shanghai]"` + Age int `json:"age"` + BaseHeader + }{ + Name: "kevin", + Address: "shanghai", + Age: 20, + } + m1, err1 := Marshal(v1) + assert.Nil(t, err1) + assert.Equal(t, "kevin", m1["json"]["name"]) + assert.Equal(t, "shanghai", m1["json"]["address"]) + assert.Equal(t, 20, m1["json"]["age"].(int)) + + type AnotherHeader struct { + Version string `header:"version"` + } + v2 := struct { + Name string `json:"name"` + Address string `json:"address,options=[beijing,shanghai]"` + Age int `json:"age"` + BaseHeader + AnotherHeader + }{ + Name: "kevin", + Address: "shanghai", + Age: 20, + BaseHeader: BaseHeader{ + Token: "token_xxx", + }, + AnotherHeader: AnotherHeader{ + Version: "v1.0", + }, + } + m2, err2 := Marshal(v2) + assert.Nil(t, err2) + assert.Equal(t, "kevin", m2["json"]["name"]) + assert.Equal(t, "shanghai", m2["json"]["address"]) + assert.Equal(t, 20, m2["json"]["age"].(int)) + assert.Equal(t, "token_xxx", m2["header"]["token"]) + assert.Equal(t, "v1.0", m2["header"]["version"]) + + type PointerHeader struct { + Ref *string `header:"ref"` + } + ref := "reference" + v3 := struct { + Name string `json:"name"` + Address string `json:"address,options=[beijing,shanghai]"` + Age int `json:"age"` + PointerHeader + }{ + Name: "kevin", + Address: "shanghai", + Age: 20, + PointerHeader: PointerHeader{ + Ref: &ref, + }, + } + m3, err3 := Marshal(v3) + assert.Nil(t, err3) + assert.Equal(t, "kevin", m3["json"]["name"]) + assert.Equal(t, "shanghai", m3["json"]["address"]) + assert.Equal(t, 20, m3["json"]["age"].(int)) + assert.Equal(t, "reference", *m3["header"]["ref"].(*string)) +} + func TestMarshal_Ptr(t *testing.T) { v := &struct { Name string `path:"name"`