diff --git a/binding.go b/binding.go index 88e6ca8..3000c2b 100644 --- a/binding.go +++ b/binding.go @@ -169,15 +169,19 @@ func bindMultipartForm(r *http.Request, injector inject.Injector, obj interface{ newObj := reflect.New(reflect.TypeOf(obj)) // This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6 - if r.Form == nil { + if r.MultipartForm == nil { if err := r.ParseMultipartForm(MaxMemory); err != nil { return apierrors.NewBadRequest(err.Error()) } } - d := form.NewDecoder() - if err := d.Decode(newObj.Interface(), r.Form); err != nil { - return NewBindingError(err, obj) + //d := form.NewDecoder() + //if err := d.Decode(newObj.Interface(), r.Form); err != nil { + // return NewBindingError(err, obj) + //} + + if err := mapForm(newObj, r.MultipartForm.Value, r.MultipartForm.File); err != nil { + return apierrors.NewBadRequest(err.Error()) } if err := check(newObj); err != nil { diff --git a/form.go b/form.go new file mode 100644 index 0000000..3bf4843 --- /dev/null +++ b/form.go @@ -0,0 +1,176 @@ +package binding + +import ( + "mime/multipart" + "reflect" + "strconv" +) + +// NameMapper represents a form tag name mapper. +type NameMapper func(string) string + +var ( + nameMapper = func(field string) string { + newstr := make([]rune, 0, len(field)) + for i, chr := range field { + if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { + if i > 0 { + newstr = append(newstr, '_') + } + chr -= ('A' - 'a') + } + newstr = append(newstr, chr) + } + return string(newstr) + } +) + +// SetNameMapper sets name mapper. +func SetNameMapper(nm NameMapper) { + nameMapper = nm +} + +// Takes values from the form data and puts them into a struct +func mapForm(formStruct reflect.Value, form map[string][]string, + formfile map[string][]*multipart.FileHeader) error { + var err error + if formStruct.Kind() == reflect.Ptr { + formStruct = formStruct.Elem() + } + typ := formStruct.Type() + + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + structField := formStruct.Field(i) + + if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous { + structField.Set(reflect.New(typeField.Type.Elem())) + if err = mapForm(structField.Elem(), form, formfile); err != nil { + return err + } + if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) { + structField.Set(reflect.Zero(structField.Type())) + } + } else if typeField.Type.Kind() == reflect.Struct { + if err = mapForm(structField, form, formfile); err != nil { + return err + } + } + + inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form")) + if len(inputFieldName) == 0 || !structField.CanSet() { + continue + } + + inputValue, exists := form[inputFieldName] + if exists { + numElems := len(inputValue) + if structField.Kind() == reflect.Slice && numElems > 0 { + sliceOf := structField.Type().Elem().Kind() + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for i := 0; i < numElems; i++ { + if err = setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName); err != nil { + return err + } + } + formStruct.Field(i).Set(slice) + } else { + if err = setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName); err != nil { + return err + } + } + continue + } + + inputFile, exists := formfile[inputFieldName] + if !exists { + continue + } + fhType := reflect.TypeOf((*multipart.FileHeader)(nil)) + numElems := len(inputFile) + if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType { + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for i := 0; i < numElems; i++ { + slice.Index(i).Set(reflect.ValueOf(inputFile[i])) + } + structField.Set(slice) + } else if structField.Type() == fhType { + structField.Set(reflect.ValueOf(inputFile[0])) + } + } + return nil +} + +func parseFormName(raw, actual string) string { + if len(actual) > 0 { + return actual + } + return nameMapper(raw) +} + +// This sets the value in a struct of an indeterminate type to the +// matching value from the request (via Form middleware) in the +// same type, so that not all deserialized values have to be strings. +// Supported types are string, int, float, and bool. +func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string) error { + switch valueKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val == "" { + val = "0" + } + intVal, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return err + } else { + structField.SetInt(intVal) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if val == "" { + val = "0" + } + uintVal, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return err + } else { + structField.SetUint(uintVal) + } + case reflect.Bool: + if val == "on" { + structField.SetBool(true) + break + } + + if val == "" { + val = "false" + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return err + } else if boolVal { + structField.SetBool(true) + } + case reflect.Float32: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 32) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.Float64: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.String: + structField.SetString(val) + } + return nil +}