From cb4fcf2c6c96f609b351ce483a6ac6869c659777 Mon Sep 17 00:00:00 2001 From: Kevin Wan Date: Fri, 15 Apr 2022 19:07:34 +0800 Subject: [PATCH] fix marshal ptr in httpc (#1789) * fix marshal ptr in httpc * add more tests * add more tests * add more tests * fix issue on options and optional both provided --- core/mapping/marshaler.go | 10 ++++++++ core/mapping/marshaler_test.go | 41 ++++++++++++++++++++++++++++++++ core/mapping/unmarshaler_test.go | 37 ++++++++++++++++++++++++++++ rest/httpc/requests_test.go | 34 ++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) 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 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)