refactor(form_mapping.go): mapping ptr, struct and map (#1749)
* refactor(form_mapping.go): mapping ptr, struct and map * fix #1672 correct work with ptr - not create value if field is not set * avoid allocations on strings.Split() - change to strings.Index() * fix #610 tag value "-" is mean ignoring field * struct fields mapped like json.Unmarshal * map fields mapped like json.Unmarshal * fix after @thinkerou review
This commit is contained in:
		
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @ -1836,24 +1836,6 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world" | |||||||
| {"d":"world","x":{"FieldX":"hello"}} | {"d":"world","x":{"FieldX":"hello"}} | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **NOTE**: NOT support the follow style struct: |  | ||||||
|  |  | ||||||
| ```go |  | ||||||
| type StructX struct { |  | ||||||
|     X struct {} `form:"name_x"` // HERE have form |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type StructY struct { |  | ||||||
|     Y StructX `form:"name_y"` // HERE have form |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type StructZ struct { |  | ||||||
|     Z *StructZ `form:"name_z"` // HERE have form |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| In a word, only support nested custom struct which have no `form` now. |  | ||||||
|  |  | ||||||
| ### Try to bind body into different structs | ### Try to bind body into different structs | ||||||
|  |  | ||||||
| The normal methods for binding request body consumes `c.Request.Body` and they | The normal methods for binding request body consumes `c.Request.Body` and they | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import ( | |||||||
| 	"mime/multipart" | 	"mime/multipart" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @ -57,7 +58,6 @@ type FooStructForTimeTypeFailLocation struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type FooStructForMapType struct { | type FooStructForMapType struct { | ||||||
| 	// Unknown type: not support map |  | ||||||
| 	MapFoo map[string]interface{} `form:"map_foo"` | 	MapFoo map[string]interface{} `form:"map_foo"` | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -303,7 +303,7 @@ func TestBindingFormInvalidName2(t *testing.T) { | |||||||
| func TestBindingFormForType(t *testing.T) { | func TestBindingFormForType(t *testing.T) { | ||||||
| 	testFormBindingForType(t, "POST", | 	testFormBindingForType(t, "POST", | ||||||
| 		"/", "/", | 		"/", "/", | ||||||
| 		"map_foo=", "bar2=1", "Map") | 		"map_foo={\"bar\":123}", "map_foo=1", "Map") | ||||||
|  |  | ||||||
| 	testFormBindingForType(t, "POST", | 	testFormBindingForType(t, "POST", | ||||||
| 		"/", "/", | 		"/", "/", | ||||||
| @ -508,20 +508,30 @@ func TestBindingYAMLFail(t *testing.T) { | |||||||
| 		`foo:\nbar`, `bar: foo`) | 		`foo:\nbar`, `bar: foo`) | ||||||
| } | } | ||||||
|  |  | ||||||
| func createFormPostRequest() *http.Request { | func createFormPostRequest(t *testing.T) *http.Request { | ||||||
| 	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) | 	req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	req.Header.Set("Content-Type", MIMEPOSTForm) | 	req.Header.Set("Content-Type", MIMEPOSTForm) | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
|  |  | ||||||
| func createDefaultFormPostRequest() *http.Request { | func createDefaultFormPostRequest(t *testing.T) *http.Request { | ||||||
| 	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) | 	req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	req.Header.Set("Content-Type", MIMEPOSTForm) | 	req.Header.Set("Content-Type", MIMEPOSTForm) | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
|  |  | ||||||
| func createFormPostRequestFail() *http.Request { | func createFormPostRequestForMap(t *testing.T) *http.Request { | ||||||
| 	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) | 	req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	req.Header.Set("Content-Type", MIMEPOSTForm) | ||||||
|  | 	return req | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createFormPostRequestForMapFail(t *testing.T) *http.Request { | ||||||
|  | 	req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	req.Header.Set("Content-Type", MIMEPOSTForm) | 	req.Header.Set("Content-Type", MIMEPOSTForm) | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
| @ -535,26 +545,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request { | |||||||
| 	assert.NoError(t, mw.SetBoundary(boundary)) | 	assert.NoError(t, mw.SetBoundary(boundary)) | ||||||
| 	assert.NoError(t, mw.WriteField("foo", "bar")) | 	assert.NoError(t, mw.WriteField("foo", "bar")) | ||||||
| 	assert.NoError(t, mw.WriteField("bar", "foo")) | 	assert.NoError(t, mw.WriteField("bar", "foo")) | ||||||
| 	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) | 	req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) | 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
|  |  | ||||||
| func createFormMultipartRequestFail(t *testing.T) *http.Request { | func createFormMultipartRequestForMap(t *testing.T) *http.Request { | ||||||
| 	boundary := "--testboundary" | 	boundary := "--testboundary" | ||||||
| 	body := new(bytes.Buffer) | 	body := new(bytes.Buffer) | ||||||
| 	mw := multipart.NewWriter(body) | 	mw := multipart.NewWriter(body) | ||||||
| 	defer mw.Close() | 	defer mw.Close() | ||||||
|  |  | ||||||
| 	assert.NoError(t, mw.SetBoundary(boundary)) | 	assert.NoError(t, mw.SetBoundary(boundary)) | ||||||
| 	assert.NoError(t, mw.WriteField("map_foo", "bar")) | 	assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) | ||||||
| 	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) | 	req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) | ||||||
|  | 	return req | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { | ||||||
|  | 	boundary := "--testboundary" | ||||||
|  | 	body := new(bytes.Buffer) | ||||||
|  | 	mw := multipart.NewWriter(body) | ||||||
|  | 	defer mw.Close() | ||||||
|  |  | ||||||
|  | 	assert.NoError(t, mw.SetBoundary(boundary)) | ||||||
|  | 	assert.NoError(t, mw.WriteField("map_foo", "3.14")) | ||||||
|  | 	req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) | 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) | ||||||
| 	return req | 	return req | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestBindingFormPost(t *testing.T) { | func TestBindingFormPost(t *testing.T) { | ||||||
| 	req := createFormPostRequest() | 	req := createFormPostRequest(t) | ||||||
| 	var obj FooBarStruct | 	var obj FooBarStruct | ||||||
| 	assert.NoError(t, FormPost.Bind(req, &obj)) | 	assert.NoError(t, FormPost.Bind(req, &obj)) | ||||||
|  |  | ||||||
| @ -564,7 +590,7 @@ func TestBindingFormPost(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestBindingDefaultValueFormPost(t *testing.T) { | func TestBindingDefaultValueFormPost(t *testing.T) { | ||||||
| 	req := createDefaultFormPostRequest() | 	req := createDefaultFormPostRequest(t) | ||||||
| 	var obj FooDefaultBarStruct | 	var obj FooDefaultBarStruct | ||||||
| 	assert.NoError(t, FormPost.Bind(req, &obj)) | 	assert.NoError(t, FormPost.Bind(req, &obj)) | ||||||
|  |  | ||||||
| @ -572,8 +598,16 @@ func TestBindingDefaultValueFormPost(t *testing.T) { | |||||||
| 	assert.Equal(t, "hello", obj.Bar) | 	assert.Equal(t, "hello", obj.Bar) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestBindingFormPostFail(t *testing.T) { | func TestBindingFormPostForMap(t *testing.T) { | ||||||
| 	req := createFormPostRequestFail() | 	req := createFormPostRequestForMap(t) | ||||||
|  | 	var obj FooStructForMapType | ||||||
|  | 	err := FormPost.Bind(req, &obj) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBindingFormPostForMapFail(t *testing.T) { | ||||||
|  | 	req := createFormPostRequestForMapFail(t) | ||||||
| 	var obj FooStructForMapType | 	var obj FooStructForMapType | ||||||
| 	err := FormPost.Bind(req, &obj) | 	err := FormPost.Bind(req, &obj) | ||||||
| 	assert.Error(t, err) | 	assert.Error(t, err) | ||||||
| @ -589,8 +623,18 @@ func TestBindingFormMultipart(t *testing.T) { | |||||||
| 	assert.Equal(t, "foo", obj.Bar) | 	assert.Equal(t, "foo", obj.Bar) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestBindingFormMultipartFail(t *testing.T) { | func TestBindingFormMultipartForMap(t *testing.T) { | ||||||
| 	req := createFormMultipartRequestFail(t) | 	req := createFormMultipartRequestForMap(t) | ||||||
|  | 	var obj FooStructForMapType | ||||||
|  | 	err := FormMultipart.Bind(req, &obj) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) | ||||||
|  | 	assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string)) | ||||||
|  | 	assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBindingFormMultipartForMapFail(t *testing.T) { | ||||||
|  | 	req := createFormMultipartRequestForMapFail(t) | ||||||
| 	var obj FooStructForMapType | 	var obj FooStructForMapType | ||||||
| 	err := FormMultipart.Bind(req, &obj) | 	err := FormMultipart.Bind(req, &obj) | ||||||
| 	assert.Error(t, err) | 	assert.Error(t, err) | ||||||
| @ -773,6 +817,17 @@ func TestFormBindingFail(t *testing.T) { | |||||||
| 	assert.Error(t, err) | 	assert.Error(t, err) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestFormBindingMultipartFail(t *testing.T) { | ||||||
|  | 	obj := FooBarStruct{} | ||||||
|  | 	req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") | ||||||
|  | 	_, err = req.MultipartReader() | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	err = Form.Bind(req, &obj) | ||||||
|  | 	assert.Error(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestFormPostBindingFail(t *testing.T) { | func TestFormPostBindingFail(t *testing.T) { | ||||||
| 	b := FormPost | 	b := FormPost | ||||||
| 	assert.Equal(t, "form-urlencoded", b.Name()) | 	assert.Equal(t, "form-urlencoded", b.Name()) | ||||||
| @ -1109,7 +1164,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s | |||||||
| 	case "Map": | 	case "Map": | ||||||
| 		obj := FooStructForMapType{} | 		obj := FooStructForMapType{} | ||||||
| 		err := b.Bind(req, &obj) | 		err := b.Bind(req, &obj) | ||||||
| 		assert.Error(t, err) | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) | ||||||
| 	case "SliceMap": | 	case "SliceMap": | ||||||
| 		obj := FooStructForSliceMapType{} | 		obj := FooStructForSliceMapType{} | ||||||
| 		err := b.Bind(req, &obj) | 		err := b.Bind(req, &obj) | ||||||
| @ -1317,3 +1373,43 @@ func TestCanSet(t *testing.T) { | |||||||
| 	var c CanSetStruct | 	var c CanSetStruct | ||||||
| 	assert.Nil(t, mapForm(&c, nil)) | 	assert.Nil(t, mapForm(&c, nil)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func formPostRequest(path, body string) *http.Request { | ||||||
|  | 	req := requestWithBody("POST", path, body) | ||||||
|  | 	req.Header.Add("Content-Type", MIMEPOSTForm) | ||||||
|  | 	return req | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBindingSliceDefault(t *testing.T) { | ||||||
|  | 	var s struct { | ||||||
|  | 		Friends []string `form:"friends,default=mike"` | ||||||
|  | 	} | ||||||
|  | 	req := formPostRequest("", "") | ||||||
|  | 	err := Form.Bind(req, &s) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	assert.Len(t, s.Friends, 1) | ||||||
|  | 	assert.Equal(t, "mike", s.Friends[0]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBindingStructField(t *testing.T) { | ||||||
|  | 	var s struct { | ||||||
|  | 		Opts struct { | ||||||
|  | 			Port int | ||||||
|  | 		} `form:"opts"` | ||||||
|  | 	} | ||||||
|  | 	req := formPostRequest("", `opts={"Port": 8000}`) | ||||||
|  | 	err := Form.Bind(req, &s) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, 8000, s.Opts.Port) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBindingUnknownTypeChan(t *testing.T) { | ||||||
|  | 	var s struct { | ||||||
|  | 		Stop chan bool `form:"stop"` | ||||||
|  | 	} | ||||||
|  | 	req := formPostRequest("", "stop=true") | ||||||
|  | 	err := Form.Bind(req, &s) | ||||||
|  | 	assert.Error(t, err) | ||||||
|  | 	assert.Equal(t, errUnknownType, err) | ||||||
|  | } | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| package binding | package binding | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @ -12,6 +13,8 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | var errUnknownType = errors.New("Unknown type") | ||||||
|  |  | ||||||
| func mapUri(ptr interface{}, m map[string][]string) error { | func mapUri(ptr interface{}, m map[string][]string) error { | ||||||
| 	return mapFormByTag(ptr, m, "uri") | 	return mapFormByTag(ptr, m, "uri") | ||||||
| } | } | ||||||
| @ -20,124 +23,153 @@ func mapForm(ptr interface{}, form map[string][]string) error { | |||||||
| 	return mapFormByTag(ptr, form, "form") | 	return mapFormByTag(ptr, form, "form") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var emptyField = reflect.StructField{} | ||||||
|  |  | ||||||
| func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { | func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { | ||||||
| 	typ := reflect.TypeOf(ptr).Elem() | 	_, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag) | ||||||
| 	val := reflect.ValueOf(ptr).Elem() | 	return err | ||||||
| 	for i := 0; i < typ.NumField(); i++ { |  | ||||||
| 		typeField := typ.Field(i) |  | ||||||
| 		structField := val.Field(i) |  | ||||||
| 		if !structField.CanSet() { |  | ||||||
| 			continue |  | ||||||
| } | } | ||||||
|  |  | ||||||
| 		structFieldKind := structField.Kind() | func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { | ||||||
| 		inputFieldName := typeField.Tag.Get(tag) | 	var vKind = value.Kind() | ||||||
| 		inputFieldNameList := strings.Split(inputFieldName, ",") |  | ||||||
| 		inputFieldName = inputFieldNameList[0] |  | ||||||
| 		var defaultValue string |  | ||||||
| 		if len(inputFieldNameList) > 1 { |  | ||||||
| 			defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) |  | ||||||
| 			if defaultList[0] == "default" { |  | ||||||
| 				defaultValue = defaultList[1] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if inputFieldName == "-" { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if inputFieldName == "" { |  | ||||||
| 			inputFieldName = typeField.Name |  | ||||||
|  |  | ||||||
| 			// if "form" tag is nil, we inspect if the field is a struct or struct pointer. | 	if vKind == reflect.Ptr { | ||||||
| 			// this would not make sense for JSON parsing but it does for a form | 		var isNew bool | ||||||
| 			// since data is flatten | 		vPtr := value | ||||||
| 			if structFieldKind == reflect.Ptr { | 		if value.IsNil() { | ||||||
| 				if !structField.Elem().IsValid() { | 			isNew = true | ||||||
| 					structField.Set(reflect.New(structField.Type().Elem())) | 			vPtr = reflect.New(value.Type().Elem()) | ||||||
| 		} | 		} | ||||||
| 				structField = structField.Elem() | 		isSetted, err := mapping(vPtr.Elem(), field, form, tag) | ||||||
| 				structFieldKind = structField.Kind() |  | ||||||
| 			} |  | ||||||
| 			if structFieldKind == reflect.Struct { |  | ||||||
| 				err := mapFormByTag(structField.Addr().Interface(), form, tag) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 					return err | 			return false, err | ||||||
| 		} | 		} | ||||||
| 				continue | 		if isNew && isSetted { | ||||||
|  | 			value.Set(vPtr) | ||||||
| 		} | 		} | ||||||
| 		} | 		return isSetted, nil | ||||||
| 		inputValue, exists := form[inputFieldName] |  | ||||||
|  |  | ||||||
| 		if !exists { |  | ||||||
| 			if defaultValue == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			inputValue = make([]string, 1) |  | ||||||
| 			inputValue[0] = defaultValue |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 		numElems := len(inputValue) | 	ok, err := tryToSetValue(value, field, form, tag) | ||||||
| 		if structFieldKind == reflect.Slice && numElems > 0 { | 	if err != nil { | ||||||
| 			sliceOf := structField.Type().Elem().Kind() | 		return false, err | ||||||
| 			slice := reflect.MakeSlice(structField.Type(), numElems, numElems) |  | ||||||
| 			for i := 0; i < numElems; i++ { |  | ||||||
| 				if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil { |  | ||||||
| 					return err |  | ||||||
| 	} | 	} | ||||||
| 			} | 	if ok { | ||||||
| 			val.Field(i).Set(slice) | 		return true, nil | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if _, isTime := structField.Interface().(time.Time); isTime { |  | ||||||
| 			if err := setTimeField(inputValue[0], typeField, structField); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { | 	if vKind == reflect.Struct { | ||||||
| 	switch valueKind { | 		tValue := value.Type() | ||||||
| 	case reflect.Int: |  | ||||||
| 		return setIntField(val, 0, structField) | 		var isSetted bool | ||||||
| 	case reflect.Int8: | 		for i := 0; i < value.NumField(); i++ { | ||||||
| 		return setIntField(val, 8, structField) | 			if !value.Field(i).CanSet() { | ||||||
| 	case reflect.Int16: | 				continue | ||||||
| 		return setIntField(val, 16, structField) |  | ||||||
| 	case reflect.Int32: |  | ||||||
| 		return setIntField(val, 32, structField) |  | ||||||
| 	case reflect.Int64: |  | ||||||
| 		return setIntField(val, 64, structField) |  | ||||||
| 	case reflect.Uint: |  | ||||||
| 		return setUintField(val, 0, structField) |  | ||||||
| 	case reflect.Uint8: |  | ||||||
| 		return setUintField(val, 8, structField) |  | ||||||
| 	case reflect.Uint16: |  | ||||||
| 		return setUintField(val, 16, structField) |  | ||||||
| 	case reflect.Uint32: |  | ||||||
| 		return setUintField(val, 32, structField) |  | ||||||
| 	case reflect.Uint64: |  | ||||||
| 		return setUintField(val, 64, structField) |  | ||||||
| 	case reflect.Bool: |  | ||||||
| 		return setBoolField(val, structField) |  | ||||||
| 	case reflect.Float32: |  | ||||||
| 		return setFloatField(val, 32, structField) |  | ||||||
| 	case reflect.Float64: |  | ||||||
| 		return setFloatField(val, 64, structField) |  | ||||||
| 	case reflect.String: |  | ||||||
| 		structField.SetString(val) |  | ||||||
| 	case reflect.Ptr: |  | ||||||
| 		if !structField.Elem().IsValid() { |  | ||||||
| 			structField.Set(reflect.New(structField.Type().Elem())) |  | ||||||
| 			} | 			} | ||||||
| 		structFieldElem := structField.Elem() | 			ok, err := mapping(value.Field(i), tValue.Field(i), form, tag) | ||||||
| 		return setWithProperType(structFieldElem.Kind(), val, structFieldElem) | 			if err != nil { | ||||||
|  | 				return false, err | ||||||
|  | 			} | ||||||
|  | 			isSetted = isSetted || ok | ||||||
|  | 		} | ||||||
|  | 		return isSetted, nil | ||||||
|  | 	} | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { | ||||||
|  | 	var tagValue, defaultValue string | ||||||
|  | 	var isDefaultExists bool | ||||||
|  |  | ||||||
|  | 	tagValue = field.Tag.Get(tag) | ||||||
|  | 	tagValue, opts := head(tagValue, ",") | ||||||
|  |  | ||||||
|  | 	if tagValue == "-" { // just ignoring this field | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	if tagValue == "" { // default value is FieldName | ||||||
|  | 		tagValue = field.Name | ||||||
|  | 	} | ||||||
|  | 	if tagValue == "" { // when field is "emptyField" variable | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var opt string | ||||||
|  | 	for len(opts) > 0 { | ||||||
|  | 		opt, opts = head(opts, ",") | ||||||
|  |  | ||||||
|  | 		k, v := head(opt, "=") | ||||||
|  | 		switch k { | ||||||
|  | 		case "default": | ||||||
|  | 			isDefaultExists = true | ||||||
|  | 			defaultValue = v | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	vs, ok := form[tagValue] | ||||||
|  | 	if !ok && !isDefaultExists { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch value.Kind() { | ||||||
|  | 	case reflect.Slice: | ||||||
|  | 		if !ok { | ||||||
|  | 			vs = []string{defaultValue} | ||||||
|  | 		} | ||||||
|  | 		return true, setSlice(vs, value, field) | ||||||
| 	default: | 	default: | ||||||
| 		return errors.New("Unknown type") | 		var val string | ||||||
|  | 		if !ok { | ||||||
|  | 			val = defaultValue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(vs) > 0 { | ||||||
|  | 			val = vs[0] | ||||||
|  | 		} | ||||||
|  | 		return true, setWithProperType(val, value, field) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { | ||||||
|  | 	switch value.Kind() { | ||||||
|  | 	case reflect.Int: | ||||||
|  | 		return setIntField(val, 0, value) | ||||||
|  | 	case reflect.Int8: | ||||||
|  | 		return setIntField(val, 8, value) | ||||||
|  | 	case reflect.Int16: | ||||||
|  | 		return setIntField(val, 16, value) | ||||||
|  | 	case reflect.Int32: | ||||||
|  | 		return setIntField(val, 32, value) | ||||||
|  | 	case reflect.Int64: | ||||||
|  | 		return setIntField(val, 64, value) | ||||||
|  | 	case reflect.Uint: | ||||||
|  | 		return setUintField(val, 0, value) | ||||||
|  | 	case reflect.Uint8: | ||||||
|  | 		return setUintField(val, 8, value) | ||||||
|  | 	case reflect.Uint16: | ||||||
|  | 		return setUintField(val, 16, value) | ||||||
|  | 	case reflect.Uint32: | ||||||
|  | 		return setUintField(val, 32, value) | ||||||
|  | 	case reflect.Uint64: | ||||||
|  | 		return setUintField(val, 64, value) | ||||||
|  | 	case reflect.Bool: | ||||||
|  | 		return setBoolField(val, value) | ||||||
|  | 	case reflect.Float32: | ||||||
|  | 		return setFloatField(val, 32, value) | ||||||
|  | 	case reflect.Float64: | ||||||
|  | 		return setFloatField(val, 64, value) | ||||||
|  | 	case reflect.String: | ||||||
|  | 		value.SetString(val) | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		switch value.Interface().(type) { | ||||||
|  | 		case time.Time: | ||||||
|  | 			return setTimeField(val, field, value) | ||||||
|  | 		} | ||||||
|  | 		return json.Unmarshal([]byte(val), value.Addr().Interface()) | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		return json.Unmarshal([]byte(val), value.Addr().Interface()) | ||||||
|  | 	default: | ||||||
|  | 		return errUnknownType | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @ -218,3 +250,23 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val | |||||||
| 	value.Set(reflect.ValueOf(t)) | 	value.Set(reflect.ValueOf(t)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { | ||||||
|  | 	slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) | ||||||
|  | 	for i, s := range vals { | ||||||
|  | 		err := setWithProperType(s, slice.Index(i), field) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	value.Set(slice) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func head(str, sep string) (head string, tail string) { | ||||||
|  | 	idx := strings.Index(str, sep) | ||||||
|  | 	if idx < 0 { | ||||||
|  | 		return str, "" | ||||||
|  | 	} | ||||||
|  | 	return str[:idx], str[idx+len(sep):] | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								binding/form_mapping_benchmark_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								binding/form_mapping_benchmark_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | // Copyright 2019 Gin Core Team.  All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
|  | package binding | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var form = map[string][]string{ | ||||||
|  | 	"name":      {"mike"}, | ||||||
|  | 	"friends":   {"anna", "nicole"}, | ||||||
|  | 	"id_number": {"12345678"}, | ||||||
|  | 	"id_date":   {"2018-01-20"}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type structFull struct { | ||||||
|  | 	Name    string   `form:"name"` | ||||||
|  | 	Age     int      `form:"age,default=25"` | ||||||
|  | 	Friends []string `form:"friends"` | ||||||
|  | 	ID      *struct { | ||||||
|  | 		Number      string    `form:"id_number"` | ||||||
|  | 		DateOfIssue time.Time `form:"id_date" time_format:"2006-01-02" time_utc:"true"` | ||||||
|  | 	} | ||||||
|  | 	Nationality *string `form:"nationality"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func BenchmarkMapFormFull(b *testing.B) { | ||||||
|  | 	var s structFull | ||||||
|  | 	for i := 0; i < b.N; i++ { | ||||||
|  | 		mapForm(&s, form) | ||||||
|  | 	} | ||||||
|  | 	b.StopTimer() | ||||||
|  |  | ||||||
|  | 	t := b | ||||||
|  | 	assert.Equal(t, "mike", s.Name) | ||||||
|  | 	assert.Equal(t, 25, s.Age) | ||||||
|  | 	assert.Equal(t, []string{"anna", "nicole"}, s.Friends) | ||||||
|  | 	assert.Equal(t, "12345678", s.ID.Number) | ||||||
|  | 	assert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue) | ||||||
|  | 	assert.Nil(t, s.Nationality) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type structName struct { | ||||||
|  | 	Name string `form:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func BenchmarkMapFormName(b *testing.B) { | ||||||
|  | 	var s structName | ||||||
|  | 	for i := 0; i < b.N; i++ { | ||||||
|  | 		mapForm(&s, form) | ||||||
|  | 	} | ||||||
|  | 	b.StopTimer() | ||||||
|  |  | ||||||
|  | 	t := b | ||||||
|  | 	assert.Equal(t, "mike", s.Name) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user