mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-12 21:05:53 +08:00
2:This reduces a redundant check. When tagValue is empty, we can first check whether field.Name is empty before assigning it. 3: With this approach, the comment in the second block can also be removed. The second invocation of k, v := head(opt, "=") in the original code appears to be unnecessary and can be removed. Some of these changes may be too minor to provide any meaningful benefit, and the actual optimization is very limited. Please feel free to ignore any suggestions that do not seem worthwhile.
549 lines
13 KiB
Go
549 lines
13 KiB
Go
// Copyright 2014 Manu Martinez-Almeida. 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 (
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"mime/multipart"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin/codec/json"
|
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
|
)
|
|
|
|
var (
|
|
errUnknownType = errors.New("unknown type")
|
|
|
|
// ErrConvertMapStringSlice can not convert to map[string][]string
|
|
ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
|
|
|
|
// ErrConvertToMapString can not convert to map[string]string
|
|
ErrConvertToMapString = errors.New("can not convert to map of strings")
|
|
)
|
|
|
|
func mapURI(ptr any, m map[string][]string) error {
|
|
return mapFormByTag(ptr, m, "uri")
|
|
}
|
|
|
|
func mapForm(ptr any, form map[string][]string) error {
|
|
return mapFormByTag(ptr, form, "form")
|
|
}
|
|
|
|
func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
|
|
return mapFormByTag(ptr, form, tag)
|
|
}
|
|
|
|
var emptyField = reflect.StructField{}
|
|
|
|
func mapFormByTag(ptr any, form map[string][]string, tag string) error {
|
|
// Check if ptr is a map
|
|
ptrVal := reflect.ValueOf(ptr)
|
|
var pointed any
|
|
if ptrVal.Kind() == reflect.Ptr {
|
|
ptrVal = ptrVal.Elem()
|
|
pointed = ptrVal.Interface()
|
|
}
|
|
if ptrVal.Kind() == reflect.Map &&
|
|
ptrVal.Type().Key().Kind() == reflect.String {
|
|
if pointed != nil {
|
|
ptr = pointed
|
|
}
|
|
return setFormMap(ptr, form)
|
|
}
|
|
|
|
return mappingByPtr(ptr, formSource(form), tag)
|
|
}
|
|
|
|
// setter tries to set value on a walking by fields of a struct
|
|
type setter interface {
|
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
|
|
}
|
|
|
|
type formSource map[string][]string
|
|
|
|
var _ setter = formSource(nil)
|
|
|
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
|
|
return setByForm(value, field, form, tagValue, opt)
|
|
}
|
|
|
|
func mappingByPtr(ptr any, setter setter, tag string) error {
|
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
|
return err
|
|
}
|
|
|
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
|
if field.Tag.Get(tag) == "-" { // just ignoring this field
|
|
return false, nil
|
|
}
|
|
|
|
vKind := value.Kind()
|
|
|
|
if vKind == reflect.Ptr {
|
|
var isNew bool
|
|
vPtr := value
|
|
if value.IsNil() {
|
|
isNew = true
|
|
vPtr = reflect.New(value.Type().Elem())
|
|
}
|
|
isSet, err := mapping(vPtr.Elem(), field, setter, tag)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if isNew && isSet {
|
|
value.Set(vPtr)
|
|
}
|
|
return isSet, nil
|
|
}
|
|
|
|
if vKind != reflect.Struct || !field.Anonymous {
|
|
ok, err := tryToSetValue(value, field, setter, tag)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if ok {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
if vKind == reflect.Struct {
|
|
tValue := value.Type()
|
|
|
|
var isSet bool
|
|
for i := range value.NumField() {
|
|
sf := tValue.Field(i)
|
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
|
continue
|
|
}
|
|
ok, err := mapping(value.Field(i), sf, setter, tag)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
isSet = isSet || ok
|
|
}
|
|
return isSet, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
type setOptions struct {
|
|
isDefaultExists bool
|
|
defaultValue string
|
|
// parser specifies what interface to use for reading the request & default values (e.g. `encoding.TextUnmarshaler`)
|
|
parser string
|
|
}
|
|
|
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
|
var setOpt setOptions
|
|
|
|
tagValue, opts := head(field.Tag.Get(tag), ",")
|
|
|
|
if tagValue == "" { // default value is FieldName
|
|
if field.Name == "" {
|
|
return false, nil
|
|
}
|
|
tagValue = field.Name
|
|
}
|
|
|
|
var opt string
|
|
for len(opts) > 0 {
|
|
opt, opts = head(opts, ",")
|
|
k, v := head(opt, "=")
|
|
if k == "default" {
|
|
setOpt.isDefaultExists = true
|
|
setOpt.defaultValue = v
|
|
|
|
// convert semicolon-separated default values to csv-separated values for processing in setByForm
|
|
if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
|
|
cfTag := field.Tag.Get("collection_format")
|
|
if cfTag == "" || cfTag == "multi" || cfTag == "csv" {
|
|
setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
|
|
}
|
|
}
|
|
} else if k == "parser" {
|
|
setOpt.parser = v
|
|
}
|
|
}
|
|
|
|
return setter.TrySet(value, field, tagValue, setOpt)
|
|
}
|
|
|
|
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
|
type BindUnmarshaler interface {
|
|
// UnmarshalParam decodes and assigns a value from a form or query param.
|
|
UnmarshalParam(param string) error
|
|
}
|
|
|
|
// trySetCustom tries to set a custom type value
|
|
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
|
|
// to skip the default value setting.
|
|
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
|
switch v := value.Addr().Interface().(type) {
|
|
case BindUnmarshaler:
|
|
return true, v.UnmarshalParam(val)
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// trySetUsingParser tries to set a custom type value based on the presence of the "parser" tag on the field.
|
|
// If the parser tag does not exist or does not match any of the supported parsers, gin will skip over this.
|
|
func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) {
|
|
switch parser {
|
|
case "encoding.TextUnmarshaler":
|
|
v, ok := value.Addr().Interface().(encoding.TextUnmarshaler)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
return true, v.UnmarshalText([]byte(val))
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
|
|
cfTag := field.Tag.Get("collection_format")
|
|
if cfTag == "" || cfTag == "multi" {
|
|
return vs, nil
|
|
}
|
|
|
|
var sep string
|
|
switch cfTag {
|
|
case "csv":
|
|
sep = ","
|
|
case "ssv":
|
|
sep = " "
|
|
case "tsv":
|
|
sep = "\t"
|
|
case "pipes":
|
|
sep = "|"
|
|
default:
|
|
return vs, fmt.Errorf("%s is not supported in the collection_format. (multi, csv, ssv, tsv, pipes)", cfTag)
|
|
}
|
|
|
|
totalLength := 0
|
|
for _, v := range vs {
|
|
totalLength += strings.Count(v, sep) + 1
|
|
}
|
|
newVs = make([]string, 0, totalLength)
|
|
for _, v := range vs {
|
|
newVs = append(newVs, strings.Split(v, sep)...)
|
|
}
|
|
|
|
return newVs, nil
|
|
}
|
|
|
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
|
vs, ok := form[tagValue]
|
|
if !ok && !opt.isDefaultExists {
|
|
return false, nil
|
|
}
|
|
|
|
switch value.Kind() {
|
|
case reflect.Slice:
|
|
if len(vs) == 0 {
|
|
if !opt.isDefaultExists {
|
|
return false, nil
|
|
}
|
|
|
|
vs = []string{opt.defaultValue}
|
|
// pre-process the default value for multi if present
|
|
cfTag := field.Tag.Get("collection_format")
|
|
if cfTag == "" || cfTag == "multi" {
|
|
vs = strings.Split(opt.defaultValue, ",")
|
|
}
|
|
}
|
|
|
|
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
|
|
return ok, err
|
|
} else if ok, err = trySetCustom(vs[0], value); ok {
|
|
return ok, err
|
|
}
|
|
|
|
if vs, err = trySplit(vs, field); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, setSlice(vs, value, field, opt)
|
|
case reflect.Array:
|
|
if len(vs) == 0 {
|
|
if !opt.isDefaultExists {
|
|
return false, nil
|
|
}
|
|
|
|
vs = []string{opt.defaultValue}
|
|
// pre-process the default value for multi if present
|
|
cfTag := field.Tag.Get("collection_format")
|
|
if cfTag == "" || cfTag == "multi" {
|
|
vs = strings.Split(opt.defaultValue, ",")
|
|
}
|
|
}
|
|
|
|
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
|
|
return ok, err
|
|
} else if ok, err = trySetCustom(vs[0], value); ok {
|
|
return ok, err
|
|
}
|
|
|
|
if vs, err = trySplit(vs, field); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if len(vs) != value.Len() {
|
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
|
}
|
|
|
|
return true, setArray(vs, value, field, opt)
|
|
default:
|
|
var val string
|
|
if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") {
|
|
val = opt.defaultValue
|
|
} else if len(vs) > 0 {
|
|
val = vs[0]
|
|
}
|
|
|
|
if ok, err = trySetUsingParser(val, value, opt.parser); ok {
|
|
return ok, err
|
|
} else if ok, err = trySetCustom(val, value); ok {
|
|
return ok, err
|
|
}
|
|
return true, setWithProperType(val, value, field, opt)
|
|
}
|
|
}
|
|
|
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
|
// this if-check is required for parsing nested types like []MyId, where MyId is [12]byte
|
|
if ok, err := trySetUsingParser(val, value, opt.parser); ok {
|
|
return err
|
|
} else if ok, err = trySetCustom(val, value); ok {
|
|
return err
|
|
}
|
|
|
|
// If it is a string type, no spaces are removed, and the user data is not modified here
|
|
if value.Kind() != reflect.String {
|
|
val = strings.TrimSpace(val)
|
|
}
|
|
|
|
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:
|
|
switch value.Interface().(type) {
|
|
case time.Duration:
|
|
return setTimeDuration(val, value)
|
|
}
|
|
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)
|
|
case multipart.FileHeader:
|
|
return nil
|
|
}
|
|
return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
|
case reflect.Map:
|
|
return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
|
case reflect.Ptr:
|
|
if !value.Elem().IsValid() {
|
|
value.Set(reflect.New(value.Type().Elem()))
|
|
}
|
|
return setWithProperType(val, value.Elem(), field, opt)
|
|
default:
|
|
return errUnknownType
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setIntField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0"
|
|
}
|
|
intVal, err := strconv.ParseInt(val, 10, bitSize)
|
|
if err == nil {
|
|
field.SetInt(intVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setUintField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0"
|
|
}
|
|
uintVal, err := strconv.ParseUint(val, 10, bitSize)
|
|
if err == nil {
|
|
field.SetUint(uintVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setBoolField(val string, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "false"
|
|
}
|
|
boolVal, err := strconv.ParseBool(val)
|
|
if err == nil {
|
|
field.SetBool(boolVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0.0"
|
|
}
|
|
floatVal, err := strconv.ParseFloat(val, bitSize)
|
|
if err == nil {
|
|
field.SetFloat(floatVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
|
timeFormat := structField.Tag.Get("time_format")
|
|
if timeFormat == "" {
|
|
timeFormat = time.RFC3339
|
|
}
|
|
|
|
if val == "" {
|
|
value.Set(reflect.ValueOf(time.Time{}))
|
|
return nil
|
|
}
|
|
|
|
switch tf := strings.ToLower(timeFormat); tf {
|
|
case "unix", "unixmilli", "unixmicro", "unixnano":
|
|
tv, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var t time.Time
|
|
switch tf {
|
|
case "unix":
|
|
t = time.Unix(tv, 0)
|
|
case "unixmilli":
|
|
t = time.UnixMilli(tv)
|
|
case "unixmicro":
|
|
t = time.UnixMicro(tv)
|
|
default:
|
|
t = time.Unix(0, tv)
|
|
}
|
|
|
|
value.Set(reflect.ValueOf(t))
|
|
return nil
|
|
}
|
|
|
|
l := time.Local
|
|
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
|
|
l = time.UTC
|
|
}
|
|
|
|
if locTag := structField.Tag.Get("time_location"); locTag != "" {
|
|
loc, err := time.LoadLocation(locTag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l = loc
|
|
}
|
|
|
|
t, err := time.ParseInLocation(timeFormat, val, l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value.Set(reflect.ValueOf(t))
|
|
return nil
|
|
}
|
|
|
|
func setArray(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
|
for i, s := range vals {
|
|
err := setWithProperType(s, value.Index(i), field, opt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
|
err := setArray(vals, slice, field, opt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value.Set(slice)
|
|
return nil
|
|
}
|
|
|
|
func setTimeDuration(val string, value reflect.Value) error {
|
|
if val == "" {
|
|
val = "0"
|
|
}
|
|
|
|
d, err := time.ParseDuration(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value.Set(reflect.ValueOf(d))
|
|
return nil
|
|
}
|
|
|
|
func head(str, sep string) (head string, tail string) {
|
|
head, tail, _ = strings.Cut(str, sep)
|
|
return head, tail
|
|
}
|
|
|
|
func setFormMap(ptr any, form map[string][]string) error {
|
|
el := reflect.TypeOf(ptr).Elem()
|
|
|
|
if el.Kind() == reflect.Slice {
|
|
ptrMap, ok := ptr.(map[string][]string)
|
|
if !ok {
|
|
return ErrConvertMapStringSlice
|
|
}
|
|
maps.Copy(ptrMap, form)
|
|
|
|
return nil
|
|
}
|
|
|
|
ptrMap, ok := ptr.(map[string]string)
|
|
if !ok {
|
|
return ErrConvertToMapString
|
|
}
|
|
for k, v := range form {
|
|
ptrMap[k] = v[len(v)-1] // pick last
|
|
}
|
|
|
|
return nil
|
|
}
|