diff --git a/.gitignore b/.gitignore index 7d98359d..9a2af3cf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ pkg/_beeTmp2/ test/tmp/ core/config/env/pkg/ +my save path/ + profile.out diff --git a/CHANGELOG.md b/CHANGELOG.md index 699341f9..a95edd50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Deprecated BeeMap and replace all usage with `sync.map` [4616](https://github.com/beego/beego/pull/4616) - TaskManager support graceful shutdown [4635](https://github.com/beego/beego/pull/4635) - Add comments to `web.Config`, rename `RouterXXX` to `CtrlXXX`, define `HandleFunc` [4714](https://github.com/beego/beego/pull/4714) +- Refactor: Move `BindXXX` and `XXXResp` methods to `context.Context`. [4718](https://github.com/beego/beego/pull/4718) - fix bug:reflect.ValueOf(nil) in getFlatParams [4715](https://github.com/beego/beego/pull/4715) ## Fix Sonar diff --git a/go.mod b/go.mod index 578d9f5b..a3e13fef 100644 --- a/go.mod +++ b/go.mod @@ -36,5 +36,7 @@ require ( go.etcd.io/etcd/client/v3 v3.5.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a google.golang.org/grpc v1.38.0 + google.golang.org/protobuf v1.26.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/server/web/context/context.go b/server/web/context/context.go index dde5e10d..f55112c8 100644 --- a/server/web/context/context.go +++ b/server/web/context/context.go @@ -27,24 +27,38 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" + "encoding/json" + "encoding/xml" "errors" "fmt" "net" "net/http" + "net/url" + "reflect" "strconv" "strings" "time" + "google.golang.org/protobuf/proto" + "gopkg.in/yaml.v3" + "github.com/beego/beego/v2/core/utils" "github.com/beego/beego/v2/server/web/session" ) // Commonly used mime-types const ( - ApplicationJSON = "application/json" - ApplicationXML = "application/xml" - ApplicationYAML = "application/x-yaml" - TextXML = "text/xml" + ApplicationJSON = "application/json" + ApplicationXML = "application/xml" + ApplicationForm = "application/x-www-form-urlencoded" + ApplicationProto = "application/x-protobuf" + ApplicationYAML = "application/x-yaml" + TextXML = "text/xml" + + formatTime = "15:04:05" + formatDate = "2006-01-02" + formatDateTime = "2006-01-02 15:04:05" + formatDateTimeT = "2006-01-02T15:04:05" ) // NewContext return the Context with Input and Output @@ -65,6 +79,108 @@ type Context struct { _xsrfToken string } +func (ctx *Context) Bind(obj interface{}) error { + ct, exist := ctx.Request.Header["Content-Type"] + if !exist || len(ct) == 0 { + return ctx.BindJSON(obj) + } + i, l := 0, len(ct[0]) + for i < l && ct[0][i] != ';' { + i++ + } + switch ct[0][0:i] { + case ApplicationJSON: + return ctx.BindJSON(obj) + case ApplicationXML, TextXML: + return ctx.BindXML(obj) + case ApplicationForm: + return ctx.BindForm(obj) + case ApplicationProto: + return ctx.BindProtobuf(obj.(proto.Message)) + case ApplicationYAML: + return ctx.BindYAML(obj) + default: + return errors.New("Unsupported Content-Type:" + ct[0]) + } +} + +// Resp sends response based on the Accept Header +// By default response will be in JSON +func (ctx *Context) Resp(data interface{}) error { + accept := ctx.Input.Header("Accept") + switch accept { + case ApplicationYAML: + return ctx.YamlResp(data) + case ApplicationXML, TextXML: + return ctx.XMLResp(data) + case ApplicationProto: + return ctx.ProtoResp(data.(proto.Message)) + default: + return ctx.JSONResp(data) + } +} + +func (ctx *Context) JSONResp(data interface{}) error { + return ctx.Output.JSON(data, false, false) +} + +func (ctx *Context) XMLResp(data interface{}) error { + return ctx.Output.XML(data, false) +} + +func (ctx *Context) YamlResp(data interface{}) error { + return ctx.Output.YAML(data) +} + +func (ctx *Context) ProtoResp(data proto.Message) error { + return ctx.Output.Proto(data) +} + +// BindYAML only read data from http request body +func (ctx *Context) BindYAML(obj interface{}) error { + return yaml.Unmarshal(ctx.Input.RequestBody, obj) +} + +// BindForm will parse form values to struct via tag. +func (ctx *Context) BindForm(obj interface{}) error { + err := ctx.Request.ParseForm() + if err != nil { + return err + } + return ParseForm(ctx.Request.Form, obj) +} + +// BindJSON only read data from http request body +func (ctx *Context) BindJSON(obj interface{}) error { + return json.Unmarshal(ctx.Input.RequestBody, obj) +} + +// BindProtobuf only read data from http request body +func (ctx *Context) BindProtobuf(obj proto.Message) error { + return proto.Unmarshal(ctx.Input.RequestBody, obj) +} + +// BindXML only read data from http request body +func (ctx *Context) BindXML(obj interface{}) error { + return xml.Unmarshal(ctx.Input.RequestBody, obj) +} + +// ParseForm will parse form values to struct via tag. +func ParseForm(form url.Values, obj interface{}) error { + objT := reflect.TypeOf(obj) + objV := reflect.ValueOf(obj) + if !isStructPtr(objT) { + return fmt.Errorf("%v must be a struct pointer", obj) + } + objT = objT.Elem() + objV = objV.Elem() + return parseFormToStruct(form, objT, objV) +} + +func isStructPtr(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct +} + // Reset initializes Context, BeegoInput and BeegoOutput func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx.Request = r @@ -91,7 +207,7 @@ func (ctx *Context) Abort(status int, body string) { // WriteString writes a string to response body. func (ctx *Context) WriteString(content string) { - ctx.ResponseWriter.Write([]byte(content)) + _, _ = ctx.ResponseWriter.Write([]byte(content)) } // GetCookie gets a cookie from a request for a given key. @@ -124,7 +240,10 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { sig := parts[2] h := hmac.New(sha256.New, []byte(Secret)) - fmt.Fprintf(h, "%s%s", vs, timestamp) + _, err := fmt.Fprintf(h, "%s%s", vs, timestamp) + if err != nil { + return "", false + } if fmt.Sprintf("%02x", h.Sum(nil)) != sig { return "", false @@ -138,7 +257,7 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf vs := base64.URLEncoding.EncodeToString([]byte(value)) timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) h := hmac.New(sha256.New, []byte(Secret)) - fmt.Fprintf(h, "%s%s", vs, timestamp) + _, _ = fmt.Fprintf(h, "%s%s", vs, timestamp) sig := fmt.Sprintf("%02x", h.Sum(nil)) cookie := strings.Join([]string{vs, timestamp, sig}, "|") ctx.Output.Cookie(name, cookie, others...) diff --git a/server/web/context/form.go b/server/web/context/form.go new file mode 100644 index 00000000..07a1b0b2 --- /dev/null +++ b/server/web/context/form.go @@ -0,0 +1,189 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package context + +import ( + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var ( + sliceOfInts = reflect.TypeOf([]int(nil)) + sliceOfStrings = reflect.TypeOf([]string(nil)) +) + +// ParseForm will parse form values to struct via tag. +// Support for anonymous struct. +func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error { + for i := 0; i < objT.NumField(); i++ { + fieldV := objV.Field(i) + if !fieldV.CanSet() { + continue + } + + fieldT := objT.Field(i) + if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct { + err := parseFormToStruct(form, fieldT.Type, fieldV) + if err != nil { + return err + } + continue + } + + tag, ok := formTagName(fieldT) + if !ok { + continue + } + + value, ok := formValue(tag, form, fieldT) + if !ok { + continue + } + + switch fieldT.Type.Kind() { + case reflect.Bool: + b, err := parseFormBoolValue(value) + if err != nil { + return err + } + fieldV.SetBool(b) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + fieldV.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + x, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + fieldV.SetUint(x) + case reflect.Float32, reflect.Float64: + x, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + fieldV.SetFloat(x) + case reflect.Interface: + fieldV.Set(reflect.ValueOf(value)) + case reflect.String: + fieldV.SetString(value) + case reflect.Struct: + if fieldT.Type.String() == "time.Time" { + t, err := parseFormTime(value) + if err != nil { + return err + } + fieldV.Set(reflect.ValueOf(t)) + } + case reflect.Slice: + if fieldT.Type == sliceOfInts { + formVals := form[tag] + fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals))) + for i := 0; i < len(formVals); i++ { + val, err := strconv.Atoi(formVals[i]) + if err != nil { + return err + } + fieldV.Index(i).SetInt(int64(val)) + } + } else if fieldT.Type == sliceOfStrings { + formVals := form[tag] + fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals))) + for i := 0; i < len(formVals); i++ { + fieldV.Index(i).SetString(formVals[i]) + } + } + } + } + return nil +} + +// nolint +func parseFormTime(value string) (time.Time, error) { + var pattern string + if len(value) >= 25 { + value = value[:25] + pattern = time.RFC3339 + } else if strings.HasSuffix(strings.ToUpper(value), "Z") { + pattern = time.RFC3339 + } else if len(value) >= 19 { + if strings.Contains(value, "T") { + pattern = formatDateTimeT + } else { + pattern = formatDateTime + } + value = value[:19] + } else if len(value) >= 10 { + if len(value) > 10 { + value = value[:10] + } + pattern = formatDate + } else if len(value) >= 8 { + if len(value) > 8 { + value = value[:8] + } + pattern = formatTime + } + return time.ParseInLocation(pattern, value, time.Local) +} + +func parseFormBoolValue(value string) (bool, error) { + if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" { + return true, nil + } + if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" { + return false, nil + } + return strconv.ParseBool(value) +} + +// nolint +func formTagName(fieldT reflect.StructField) (string, bool) { + tags := strings.Split(fieldT.Tag.Get("form"), ",") + var tag string + if len(tags) == 0 || tags[0] == "" { + tag = fieldT.Name + } else if tags[0] == "-" { + return "", false + } else { + tag = tags[0] + } + return tag, true +} + +func formValue(tag string, form url.Values, fieldT reflect.StructField) (string, bool) { + formValues := form[tag] + var value string + if len(formValues) == 0 { + defaultValue := fieldT.Tag.Get("default") + if defaultValue != "" { + value = defaultValue + } else { + return "", false + } + } + if len(formValues) == 1 { + value = formValues[0] + if value == "" { + return "", false + } + } + return value, true +} diff --git a/server/web/context/output.go b/server/web/context/output.go index eeac368e..0c261627 100644 --- a/server/web/context/output.go +++ b/server/web/context/output.go @@ -31,7 +31,8 @@ import ( "strings" "time" - yaml "gopkg.in/yaml.v2" + "google.golang.org/protobuf/proto" + "gopkg.in/yaml.v2" ) // BeegoOutput does work for sending response header. @@ -154,7 +155,7 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface fmt.Fprintf(&b, "; HttpOnly") } } - + // default empty if len(others) > 5 { if v, ok := others[5].(string); ok && len(v) > 0 { @@ -224,6 +225,19 @@ func (output *BeegoOutput) YAML(data interface{}) error { return output.Body(content) } +// Proto writes protobuf to the response body. +func (output *BeegoOutput) Proto(data proto.Message) error { + output.Header("Content-Type", "application/x-protobuf; charset=utf-8") + var content []byte + var err error + content, err = proto.Marshal(data) + if err != nil { + http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) + return err + } + return output.Body(content) +} + // JSONP writes jsonp to the response body. func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { output.Header("Content-Type", "application/javascript; charset=utf-8") diff --git a/server/web/controller.go b/server/web/controller.go index f9371f2d..6bf061dd 100644 --- a/server/web/controller.go +++ b/server/web/controller.go @@ -17,8 +17,6 @@ package web import ( "bytes" context2 "context" - "encoding/json" - "encoding/xml" "errors" "fmt" "html/template" @@ -32,8 +30,7 @@ import ( "strings" "sync" - "github.com/gogo/protobuf/proto" - "gopkg.in/yaml.v2" + "google.golang.org/protobuf/proto" "github.com/beego/beego/v2/server/web/context" "github.com/beego/beego/v2/server/web/context/param" @@ -244,49 +241,35 @@ func (c *Controller) HandlerFunc(fnname string) bool { // URLMapping register the internal Controller router. func (c *Controller) URLMapping() {} +// Bind if the content type is form, we read data from form +// otherwise, read data from request body func (c *Controller) Bind(obj interface{}) error { - ct, exist := c.Ctx.Request.Header["Content-Type"] - if !exist || len(ct) == 0 { - return c.BindJSON(obj) - } - i, l := 0, len(ct[0]) - for i < l && ct[0][i] != ';' { - i++ - } - switch ct[0][0:i] { - case "application/json": - return c.BindJSON(obj) - case "application/xml", "text/xml": - return c.BindXML(obj) - case "application/x-www-form-urlencoded": - return c.BindForm(obj) - case "application/x-protobuf": - return c.BindProtobuf(obj) - case "application/x-yaml": - return c.BindYAML(obj) - default: - return errors.New("Unsupported Content-Type:" + ct[0]) - } + return c.Ctx.Bind(obj) } +// BindYAML only read data from http request body func (c *Controller) BindYAML(obj interface{}) error { - return yaml.Unmarshal(c.Ctx.Input.RequestBody, obj) + return c.Ctx.BindYAML(obj) } +// BindForm read data from form func (c *Controller) BindForm(obj interface{}) error { - return c.ParseForm(obj) + return c.Ctx.BindForm(obj) } +// BindJSON only read data from http request body func (c *Controller) BindJSON(obj interface{}) error { - return json.Unmarshal(c.Ctx.Input.RequestBody, obj) + return c.Ctx.BindJSON(obj) } -func (c *Controller) BindProtobuf(obj interface{}) error { - return proto.Unmarshal(c.Ctx.Input.RequestBody, obj.(proto.Message)) +// BindProtobuf only read data from http request body +func (c *Controller) BindProtobuf(obj proto.Message) error { + return c.Ctx.BindProtobuf(obj) } +// BindXML only read data from http request body func (c *Controller) BindXML(obj interface{}) error { - return xml.Unmarshal(c.Ctx.Input.RequestBody, obj) + return c.Ctx.BindXML(obj) } // Mapping the method to function @@ -440,35 +423,23 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string { } func (c *Controller) JSONResp(data interface{}) error { - c.Data["json"] = data - return c.ServeJSON() + return c.Ctx.JSONResp(data) } func (c *Controller) XMLResp(data interface{}) error { - c.Data["xml"] = data - return c.ServeXML() + return c.Ctx.XMLResp(data) } func (c *Controller) YamlResp(data interface{}) error { - c.Data["yaml"] = data - return c.ServeYAML() + return c.Ctx.YamlResp(data) } // Resp sends response based on the Accept Header // By default response will be in JSON +// it's different from ServeXXX methods +// because we don't store the data to Data field func (c *Controller) Resp(data interface{}) error { - accept := c.Ctx.Input.Header("Accept") - switch accept { - case context.ApplicationYAML: - c.Data["yaml"] = data - return c.ServeYAML() - case context.ApplicationXML, context.TextXML: - c.Data["xml"] = data - return c.ServeXML() - default: - c.Data["json"] = data - return c.ServeJSON() - } + return c.Ctx.Resp(data) } // ServeJSON sends a json response with encoding charset. @@ -518,11 +489,7 @@ func (c *Controller) Input() (url.Values, error) { // ParseForm maps input data map to obj struct. func (c *Controller) ParseForm(obj interface{}) error { - form, err := c.Input() - if err != nil { - return err - } - return ParseForm(form, obj) + return c.Ctx.BindForm(obj) } // GetString returns the input value by key string or the default value while it's present and input is blank diff --git a/server/web/controller_test.go b/server/web/controller_test.go index dfd21713..6b0a5203 100644 --- a/server/web/controller_test.go +++ b/server/web/controller_test.go @@ -269,10 +269,10 @@ type respTestCase struct { func TestControllerResp(t *testing.T) { // test cases tcs := []respTestCase{ - {Accept: context.ApplicationJSON, ExpectedContentLength: 18, ExpectedResponse: "{\n \"foo\": \"bar\"\n}"}, - {Accept: context.ApplicationXML, ExpectedContentLength: 25, ExpectedResponse: "\n bar\n"}, + {Accept: context.ApplicationJSON, ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`}, + {Accept: context.ApplicationXML, ExpectedContentLength: 21, ExpectedResponse: `bar`}, {Accept: context.ApplicationYAML, ExpectedContentLength: 9, ExpectedResponse: "foo: bar\n"}, - {Accept: "OTHER", ExpectedContentLength: 18, ExpectedResponse: "{\n \"foo\": \"bar\"\n}"}, + {Accept: "OTHER", ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`}, } for _, tc := range tcs { diff --git a/server/web/templatefunc.go b/server/web/templatefunc.go index fce1c970..c0db9990 100644 --- a/server/web/templatefunc.go +++ b/server/web/templatefunc.go @@ -25,13 +25,8 @@ import ( "strconv" "strings" "time" -) -const ( - formatTime = "15:04:05" - formatDate = "2006-01-02" - formatDateTime = "2006-01-02 15:04:05" - formatDateTimeT = "2006-01-02T15:04:05" + "github.com/beego/beego/v2/server/web/context" ) // Substr returns the substr from start to length. @@ -266,165 +261,11 @@ func AssetsCSS(text string) template.HTML { return template.HTML(text) } -// ParseForm will parse form values to struct via tag. -// Support for anonymous struct. -func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error { - for i := 0; i < objT.NumField(); i++ { - fieldV := objV.Field(i) - if !fieldV.CanSet() { - continue - } - - fieldT := objT.Field(i) - if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct { - err := parseFormToStruct(form, fieldT.Type, fieldV) - if err != nil { - return err - } - continue - } - - tags := strings.Split(fieldT.Tag.Get("form"), ",") - var tag string - if len(tags) == 0 || len(tags[0]) == 0 { - tag = fieldT.Name - } else if tags[0] == "-" { - continue - } else { - tag = tags[0] - } - - formValues := form[tag] - var value string - if len(formValues) == 0 { - defaultValue := fieldT.Tag.Get("default") - if defaultValue != "" { - value = defaultValue - } else { - continue - } - } - if len(formValues) == 1 { - value = formValues[0] - if value == "" { - continue - } - } - - switch fieldT.Type.Kind() { - case reflect.Bool: - if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" { - fieldV.SetBool(true) - continue - } - if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" { - fieldV.SetBool(false) - continue - } - b, err := strconv.ParseBool(value) - if err != nil { - return err - } - fieldV.SetBool(b) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - fieldV.SetInt(x) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - x, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - fieldV.SetUint(x) - case reflect.Float32, reflect.Float64: - x, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - fieldV.SetFloat(x) - case reflect.Interface: - fieldV.Set(reflect.ValueOf(value)) - case reflect.String: - fieldV.SetString(value) - case reflect.Struct: - switch fieldT.Type.String() { - case "time.Time": - var ( - t time.Time - err error - ) - if len(value) >= 25 { - value = value[:25] - t, err = time.ParseInLocation(time.RFC3339, value, time.Local) - } else if strings.HasSuffix(strings.ToUpper(value), "Z") { - t, err = time.ParseInLocation(time.RFC3339, value, time.Local) - } else if len(value) >= 19 { - if strings.Contains(value, "T") { - value = value[:19] - t, err = time.ParseInLocation(formatDateTimeT, value, time.Local) - } else { - value = value[:19] - t, err = time.ParseInLocation(formatDateTime, value, time.Local) - } - } else if len(value) >= 10 { - if len(value) > 10 { - value = value[:10] - } - t, err = time.ParseInLocation(formatDate, value, time.Local) - } else if len(value) >= 8 { - if len(value) > 8 { - value = value[:8] - } - t, err = time.ParseInLocation(formatTime, value, time.Local) - } - if err != nil { - return err - } - fieldV.Set(reflect.ValueOf(t)) - } - case reflect.Slice: - if fieldT.Type == sliceOfInts { - formVals := form[tag] - fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals))) - for i := 0; i < len(formVals); i++ { - val, err := strconv.Atoi(formVals[i]) - if err != nil { - return err - } - fieldV.Index(i).SetInt(int64(val)) - } - } else if fieldT.Type == sliceOfStrings { - formVals := form[tag] - fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals))) - for i := 0; i < len(formVals); i++ { - fieldV.Index(i).SetString(formVals[i]) - } - } - } - } - return nil -} - // ParseForm will parse form values to struct via tag. func ParseForm(form url.Values, obj interface{}) error { - objT := reflect.TypeOf(obj) - objV := reflect.ValueOf(obj) - if !isStructPtr(objT) { - return fmt.Errorf("%v must be a struct pointer", obj) - } - objT = objT.Elem() - objV = objV.Elem() - - return parseFormToStruct(form, objT, objV) + return context.ParseForm(form, obj) } -var ( - sliceOfInts = reflect.TypeOf([]int(nil)) - sliceOfStrings = reflect.TypeOf([]string(nil)) -) - var unKind = map[reflect.Kind]bool{ reflect.Uintptr: true, reflect.Complex64: true, @@ -441,10 +282,11 @@ var unKind = map[reflect.Kind]bool{ // RenderForm will render object to form html. // obj must be a struct pointer. +// nolint func RenderForm(obj interface{}) template.HTML { objT := reflect.TypeOf(obj) objV := reflect.ValueOf(obj) - if !isStructPtr(objT) { + if objT.Kind() != reflect.Ptr || objT.Elem().Kind() != reflect.Struct { return template.HTML("") } objT = objT.Elem() @@ -549,10 +391,6 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str return } -func isStructPtr(t reflect.Type) bool { - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct -} - // go1.2 added template funcs. begin var ( errBadComparisonType = errors.New("invalid type for comparison")