package crud import ( "errors" "github.com/labstack/echo" "reflect" "strconv" "strings" ) const paramTagName = "param" // ParamBinder binds parameters to a struct. // Currently a working implementation, waiting to implement this officially into echo. func ParamBinder(i interface{}, c echo.Context) (err error) { // Default binder db := new(echo.DefaultBinder) if err = db.Bind(i, c); err != nil { return } paramNames := c.ParamNames() paramValues := c.ParamValues() paramVars := make(map[string][]string) for in, name := range paramNames { // Hotfix for an echo bug where a param name would show up which dont exist names := strings.Split(name, ",") for _, n := range names { paramVars[n] = append(paramVars[name], paramValues[in]) } } b := Binder{} err = b.bindData(i, paramVars, paramTagName) /* // Our custom magic starts here paramNames := c.ParamNames() paramValues := c.ParamValues() v := reflect.ValueOf(i) t := reflect.TypeOf(i) s := reflect.ValueOf(i).Elem() for i := 0; i < v.NumField(); i++ { field := t.Field(i) f := s.Field(i) // Check if it has a param tag tag := field.Tag.Get(paramTagName) if tag != "" { // If it has one, range over all url parameters to see if we have a match for in, name := range paramNames { // Found match if tag == name { // Put the value of that match in our sruct switch field.Type.Name() { case "int64": // SetInt only accepts int64, so the struct field can only have int64 of int (no int32/16/int...) intParam, err := strconv.ParseInt(paramValues[in], 10, 64) f.SetInt(intParam) if err != nil { return err } case "string": f.SetString(paramValues[in]) } } } } //f.SetString("blub") }*/ return } // Binder represents a binder type Binder struct{} func (b *Binder) bindData(ptr interface{}, data map[string][]string, tag string) error { typ := reflect.TypeOf(ptr).Elem() val := reflect.ValueOf(ptr).Elem() if typ.Kind() != reflect.Struct { return errors.New("Binding element must be a struct") } for i := 0; i < typ.NumField(); i++ { typeField := typ.Field(i) structField := val.Field(i) if !structField.CanSet() { continue } structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get(tag) if inputFieldName == "" { inputFieldName = typeField.Name // If tag is nil, we inspect if the field is a struct. if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct { err := b.bindData(structField.Addr().Interface(), data, tag) if err != nil { return err } continue } } inputValue, exists := data[inputFieldName] if !exists { continue } // Call this first, in case we're dealing with an alias to an array type if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok { if err != nil { return err } continue } numElems := len(inputValue) if structFieldKind == reflect.Slice && numElems > 0 { sliceOf := structField.Type().Elem().Kind() slice := reflect.MakeSlice(structField.Type(), numElems, numElems) for j := 0; j < numElems; j++ { if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil { return err } } val.Field(i).Set(slice) } else { if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { return err } } } return nil } func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { // But also call it here, in case we're dealing with an array of BindUnmarshalers if ok, err := unmarshalField(valueKind, val, structField); ok { return err } switch valueKind { case reflect.Int: return setIntField(val, 0, structField) case reflect.Int8: return setIntField(val, 8, structField) case reflect.Int16: return setIntField(val, 16, structField) case reflect.Int32: return setIntField(val, 32, structField) case reflect.Int64: return setIntField(val, 64, structField) case reflect.Uint: return setUintField(val, 0, structField) case reflect.Uint8: return setUintField(val, 8, structField) case reflect.Uint16: return setUintField(val, 16, structField) case reflect.Uint32: return setUintField(val, 32, structField) case reflect.Uint64: return setUintField(val, 64, structField) case reflect.Bool: return setBoolField(val, structField) case reflect.Float32: return setFloatField(val, 32, structField) case reflect.Float64: return setFloatField(val, 64, structField) case reflect.String: structField.SetString(val) default: return errors.New("unknown type") } return nil } func setIntField(value string, bitSize int, field reflect.Value) error { if value == "" { value = "0" } intVal, err := strconv.ParseInt(value, 10, bitSize) if err == nil { field.SetInt(intVal) } return err } func setUintField(value string, bitSize int, field reflect.Value) error { if value == "" { value = "0" } uintVal, err := strconv.ParseUint(value, 10, bitSize) if err == nil { field.SetUint(uintVal) } return err } func setBoolField(value string, field reflect.Value) error { if value == "" { value = "false" } boolVal, err := strconv.ParseBool(value) if err == nil { field.SetBool(boolVal) } return err } func setFloatField(value string, bitSize int, field reflect.Value) error { if value == "" { value = "0.0" } floatVal, err := strconv.ParseFloat(value, bitSize) if err == nil { field.SetFloat(floatVal) } return err } // BindUnmarshaler type type BindUnmarshaler interface { // UnmarshalParam decodes and assigns a value from an form or query param. UnmarshalParam(param string) error } // bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) { ptr := reflect.New(field.Type()) if ptr.CanInterface() { iface := ptr.Interface() if unmarshaler, ok := iface.(BindUnmarshaler); ok { return unmarshaler, ok } } return nil, false } func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) { switch valueKind { case reflect.Ptr: return unmarshalFieldPtr(val, field) default: return unmarshalFieldNonPtr(val, field) } } func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { if unmarshaler, ok := bindUnmarshaler(field); ok { err := unmarshaler.UnmarshalParam(value) field.Set(reflect.ValueOf(unmarshaler).Elem()) return true, err } return false, nil } func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) { if field.IsNil() { // Initialize the pointer to a nil value field.Set(reflect.New(field.Type().Elem())) } return unmarshalFieldNonPtr(value, field.Elem()) }