Repository: zeromicro/go-zero
Pull request #1789 (2022-04-15 10:03:45)

fix marshal ptr in httpc


Patch:
diff --git a/core/mapping/marshaler.go b/core/mapping/marshaler.go
index 6d1bee8e..a10a79c6 100644
--- a/core/mapping/marshaler.go
+++ b/core/mapping/marshaler.go
@@ -16,7 +16,13 @@ const (
 func Marshal(val interface{}) (map[string]map[string]interface{}, error) {
 	ret := make(map[string]map[string]interface{})
 	tp := reflect.TypeOf(val)
+	if tp.Kind() == reflect.Ptr {
+		tp = tp.Elem()
+	}
 	rv := reflect.ValueOf(val)
+	if rv.Kind() == reflect.Ptr {
+		rv = rv.Elem()
+	}
 
 	for i := 0; i < tp.NumField(); i++ {
 		field := tp.Field(i)
@@ -87,6 +93,10 @@ func validate(field reflect.StructField, value reflect.Value, opt *fieldOptions)
 		return nil
 	}
 
+	if opt.Optional && value.IsZero() {
+		return nil
+	}
+
 	if len(opt.Options) > 0 {
 		if err := validateOptions(value, opt); err != nil {
 			return err


Test patch:
diff --git a/core/mapping/marshaler_test.go b/core/mapping/marshaler_test.go
index b7b45bdc..42491d4a 100644
--- a/core/mapping/marshaler_test.go
+++ b/core/mapping/marshaler_test.go
@@ -27,6 +27,27 @@ func TestMarshal(t *testing.T) {
 	assert.True(t, m[emptyTag]["Anonymous"].(bool))
 }
 
+func TestMarshal_Ptr(t *testing.T) {
+	v := &struct {
+		Name      string `path:"name"`
+		Address   string `json:"address,options=[beijing,shanghai]"`
+		Age       int    `json:"age"`
+		Anonymous bool
+	}{
+		Name:      "kevin",
+		Address:   "shanghai",
+		Age:       20,
+		Anonymous: true,
+	}
+
+	m, err := Marshal(v)
+	assert.Nil(t, err)
+	assert.Equal(t, "kevin", m["path"]["name"])
+	assert.Equal(t, "shanghai", m["json"]["address"])
+	assert.Equal(t, 20, m["json"]["age"].(int))
+	assert.True(t, m[emptyTag]["Anonymous"].(bool))
+}
+
 func TestMarshal_OptionalPtr(t *testing.T) {
 	var val = 1
 	v := struct {
@@ -71,6 +92,26 @@ func TestMarshal_NotInOptions(t *testing.T) {
 	assert.NotNil(t, err)
 }
 
+func TestMarshal_NotInOptionsOptional(t *testing.T) {
+	v := struct {
+		Name string `json:"name,options=[a,b],optional"`
+	}{}
+
+	_, err := Marshal(v)
+	assert.Nil(t, err)
+}
+
+func TestMarshal_NotInOptionsOptionalWrongValue(t *testing.T) {
+	v := struct {
+		Name string `json:"name,options=[a,b],optional"`
+	}{
+		Name: "kevin",
+	}
+
+	_, err := Marshal(v)
+	assert.NotNil(t, err)
+}
+
 func TestMarshal_Nested(t *testing.T) {
 	type address struct {
 		Country string `json:"country"`
diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go
index 2cf21520..0bf1739c 100644
--- a/core/mapping/unmarshaler_test.go
+++ b/core/mapping/unmarshaler_test.go
@@ -987,6 +987,43 @@ func TestUnmarshalWithStringOptionsCorrect(t *testing.T) {
 	ast.Equal("2", in.Correct)
 }
 
+func TestUnmarshalOptionsOptional(t *testing.T) {
+	type inner struct {
+		Value         string `key:"value,options=first|second,optional"`
+		OptionalValue string `key:"optional_value,options=first|second,optional"`
+		Foo           string `key:"foo,options=[bar,baz]"`
+		Correct       string `key:"correct,options=1|2"`
+	}
+	m := map[string]interface{}{
+		"value":   "first",
+		"foo":     "bar",
+		"correct": "2",
+	}
+
+	var in inner
+	ast := assert.New(t)
+	ast.Nil(UnmarshalKey(m, &in))
+	ast.Equal("first", in.Value)
+	ast.Equal("", in.OptionalValue)
+	ast.Equal("bar", in.Foo)
+	ast.Equal("2", in.Correct)
+}
+
+func TestUnmarshalOptionsOptionalWrongValue(t *testing.T) {
+	type inner struct {
+		Value         string `key:"value,options=first|second,optional"`
+		OptionalValue string `key:"optional_value,options=first|second,optional"`
+		WrongValue    string `key:"wrong_value,options=first|second,optional"`
+	}
+	m := map[string]interface{}{
+		"value":       "first",
+		"wrong_value": "third",
+	}
+
+	var in inner
+	assert.NotNil(t, UnmarshalKey(m, &in))
+}
+
 func TestUnmarshalStringOptionsWithStringOptionsNotString(t *testing.T) {
 	type inner struct {
 		Value   string `key:"value,options=first|second"`
diff --git a/rest/httpc/requests_test.go b/rest/httpc/requests_test.go
index 34bd82af..a2b4bfd1 100644
--- a/rest/httpc/requests_test.go
+++ b/rest/httpc/requests_test.go
@@ -73,6 +73,40 @@ func TestDo(t *testing.T) {
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 }
 
+func TestDo_Ptr(t *testing.T) {
+	type Data struct {
+		Key    string `path:"key"`
+		Value  int    `form:"value"`
+		Header string `header:"X-Header"`
+		Body   string `json:"body"`
+	}
+
+	rt := router.NewRouter()
+	err := rt.Handle(http.MethodPost, "/nodes/:key",
+		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			var req Data
+			assert.Nil(t, httpx.Parse(r, &req))
+			assert.Equal(t, "foo", req.Key)
+			assert.Equal(t, 10, req.Value)
+			assert.Equal(t, "my-header", req.Header)
+			assert.Equal(t, "my body", req.Body)
+		}))
+	assert.Nil(t, err)
+
+	svr := httptest.NewServer(http.HandlerFunc(rt.ServeHTTP))
+	defer svr.Close()
+
+	data := &Data{
+		Key:    "foo",
+		Value:  10,
+		Header: "my-header",
+		Body:   "my body",
+	}
+	resp, err := Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
+	assert.Nil(t, err)
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+}
+
 func TestDo_BadRequest(t *testing.T) {
 	_, err := Do(context.Background(), http.MethodPost, ":/nodes/:key", nil)
 	assert.NotNil(t, err)