package request import ( "errors" "net/url" "reflect" "strconv" "strings" ) // Bind data from request.Form[key] to dest // like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=orange // var id int orangeInput.Bind(&id, "id") id ==123 // var isok bool orangeInput.Bind(&isok, "isok") isok ==true // var ft float64 orangeInput.Bind(&ft, "ft") ft ==1.2 // ol := make([]int, 0, 2) orangeInput.Bind(&ol, "ol") ol ==[1 2] // ul := make([]string, 0, 2) orangeInput.Bind(&ul, "ul") ul ==[str array] // user struct{Name} orangeInput.Bind(&user, "user") user == {Name:"orange"} func (input *OrangeInput) Bind(dest interface{}, key string) error { value := reflect.ValueOf(dest) if value.Kind() != reflect.Ptr { return errors.New("orange: non-pointer passed to Bind: " + key) } value = value.Elem() if !value.CanSet() { return errors.New("orange: non-settable variable passed to Bind: " + key) } typ := value.Type() // Get real type if dest define with interface{}. // e.g var dest interface{} dest=1.0 if value.Kind() == reflect.Interface { typ = value.Elem().Type() } rv := input.bind(key, typ) if !rv.IsValid() { return errors.New("orange: reflect value is empty") } value.Set(rv) return nil } func (input *OrangeInput) bind(key string, typ reflect.Type) reflect.Value { if input.request.Form == nil { input.request.ParseForm() } rv := reflect.Zero(typ) switch typ.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: val := input.Query(key) if len(val) == 0 { return rv } rv = input.bindInt(val, typ) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: val := input.Query(key) if len(val) == 0 { return rv } rv = input.bindUint(val, typ) case reflect.Float32, reflect.Float64: val := input.Query(key) if len(val) == 0 { return rv } rv = input.bindFloat(val, typ) case reflect.String: val := input.Query(key) if len(val) == 0 { return rv } rv = input.bindString(val, typ) case reflect.Bool: val := input.Query(key) if len(val) == 0 { return rv } rv = input.bindBool(val, typ) case reflect.Slice: rv = input.bindSlice(&input.request.Form, key, typ) case reflect.Struct: rv = input.bindStruct(&input.request.Form, key, typ) case reflect.Ptr: rv = input.bindPoint(key, typ) case reflect.Map: rv = input.bindMap(&input.request.Form, key, typ) } return rv } func (input *OrangeInput) bindValue(val string, typ reflect.Type) reflect.Value { rv := reflect.Zero(typ) switch typ.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: rv = input.bindInt(val, typ) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: rv = input.bindUint(val, typ) case reflect.Float32, reflect.Float64: rv = input.bindFloat(val, typ) case reflect.String: rv = input.bindString(val, typ) case reflect.Bool: rv = input.bindBool(val, typ) case reflect.Slice: rv = input.bindSlice(&url.Values{"": {val}}, "", typ) case reflect.Struct: rv = input.bindStruct(&url.Values{"": {val}}, "", typ) case reflect.Ptr: rv = input.bindPoint(val, typ) case reflect.Map: rv = input.bindMap(&url.Values{"": {val}}, "", typ) } return rv } func (input *OrangeInput) bindInt(val string, typ reflect.Type) reflect.Value { intValue, err := strconv.ParseInt(val, 10, 64) if err != nil { return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetInt(intValue) return pValue.Elem() } func (input *OrangeInput) bindUint(val string, typ reflect.Type) reflect.Value { uintValue, err := strconv.ParseUint(val, 10, 64) if err != nil { return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetUint(uintValue) return pValue.Elem() } func (input *OrangeInput) bindFloat(val string, typ reflect.Type) reflect.Value { floatValue, err := strconv.ParseFloat(val, 64) if err != nil { return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetFloat(floatValue) return pValue.Elem() } func (input *OrangeInput) bindString(val string, typ reflect.Type) reflect.Value { return reflect.ValueOf(val) } func (input *OrangeInput) bindBool(val string, typ reflect.Type) reflect.Value { val = strings.TrimSpace(strings.ToLower(val)) switch val { case "true", "on", "1": return reflect.ValueOf(true) } return reflect.ValueOf(false) } type sliceValue struct { index int // Index extracted from brackets. If -1, no index was provided. value reflect.Value // the bound value for this slice element. } func (input *OrangeInput) bindSlice(params *url.Values, key string, typ reflect.Type) reflect.Value { maxIndex := -1 numNoIndex := 0 sliceValues := []sliceValue{} for reqKey, vals := range *params { if !strings.HasPrefix(reqKey, key+"[") { continue } // Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey) index := -1 leftBracket, rightBracket := len(key), strings.Index(reqKey[len(key):], "]")+len(key) if rightBracket > leftBracket+1 { index, _ = strconv.Atoi(reqKey[leftBracket+1 : rightBracket]) } subKeyIndex := rightBracket + 1 // Handle the indexed case. if index > -1 { if index > maxIndex { maxIndex = index } sliceValues = append(sliceValues, sliceValue{ index: index, value: input.bind(reqKey[:subKeyIndex], typ.Elem()), }) continue } // It's an un-indexed element. (e.g. element[]) numNoIndex += len(vals) for _, val := range vals { // Unindexed values can only be direct-bound. sliceValues = append(sliceValues, sliceValue{ index: -1, value: input.bindValue(val, typ.Elem()), }) } } resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex) for _, sv := range sliceValues { if sv.index != -1 { resultArray.Index(sv.index).Set(sv.value) } else { resultArray = reflect.Append(resultArray, sv.value) } } return resultArray } func (input *OrangeInput) bindStruct(params *url.Values, key string, typ reflect.Type) reflect.Value { result := reflect.New(typ).Elem() fieldValues := make(map[string]reflect.Value) for reqKey, val := range *params { var fieldName string if strings.HasPrefix(reqKey, key+".") { fieldName = reqKey[len(key)+1:] } else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' { fieldName = reqKey[len(key)+1 : len(reqKey)-1] } else { continue } if _, ok := fieldValues[fieldName]; !ok { // Time to bind this field. Get it and make sure we can set it. fieldValue := result.FieldByName(fieldName) if !fieldValue.IsValid() { continue } if !fieldValue.CanSet() { continue } boundVal := input.bindValue(val[0], fieldValue.Type()) fieldValue.Set(boundVal) fieldValues[fieldName] = boundVal } } return result } func (input *OrangeInput) bindPoint(key string, typ reflect.Type) reflect.Value { return input.bind(key, typ.Elem()).Addr() } func (input *OrangeInput) bindMap(params *url.Values, key string, typ reflect.Type) reflect.Value { var ( result = reflect.MakeMap(typ) keyType = typ.Key() valueType = typ.Elem() ) for paramName, values := range *params { if !strings.HasPrefix(paramName, key+"[") || paramName[len(paramName)-1] != ']' { continue } key := paramName[len(key)+1 : len(paramName)-1] result.SetMapIndex(input.bindValue(key, keyType), input.bindValue(values[0], valueType)) } return result }