Merge pull request #4718 from flycash/refactor-ctx

Refator: Remove methods to web.context.Context
This commit is contained in:
Ming Deng 2021-08-12 21:45:57 +08:00 committed by GitHub
commit 419e395d78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 365 additions and 233 deletions

2
.gitignore vendored
View File

@ -12,4 +12,6 @@ pkg/_beeTmp2/
test/tmp/
core/config/env/pkg/
my save path/
profile.out

View File

@ -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

2
go.mod
View File

@ -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
)

View File

@ -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...)

189
server/web/context/form.go Normal file
View File

@ -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
}

View File

@ -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")

View File

@ -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

View File

@ -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: "<S>\n <foo>bar</foo>\n</S>"},
{Accept: context.ApplicationJSON, ExpectedContentLength: 13, ExpectedResponse: `{"foo":"bar"}`},
{Accept: context.ApplicationXML, ExpectedContentLength: 21, ExpectedResponse: `<S><foo>bar</foo></S>`},
{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 {

View File

@ -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")