package mux import ( "encoding/json" "fmt" "net/http" "net/url" "reflect" "strconv" "strings" ) func Bind[T any](r *http.Request, obj *T) error { contentType := r.Header.Get("Content-Type") switch { case strings.HasPrefix(contentType, "multipart/form-data"), strings.HasPrefix(contentType, "application/x-www-form-urlencoded"): if err := r.ParseForm(); err != nil { return err } return bindForm(r.Form, obj) case strings.HasPrefix(contentType, "application/json"): defer r.Body.Close() return json.NewDecoder(r.Body).Decode(obj) case r.Method == http.MethodGet: return bindForm(r.URL.Query(), obj) case r.Method == http.MethodPost: return fmt.Errorf("invalid content-type: %s", contentType) } return nil } func bindForm(values url.Values, obj any) error { val := reflect.ValueOf(obj) if val.Kind() == reflect.Ptr { val = val.Elem() } fields := val.NumField() for i := 0; i < fields; i++ { f := val.Field(i) if !f.IsValid() { continue } if !f.CanSet() { continue } t := val.Type().Field(i) k := t.Tag.Get("form") if k == "" { continue } if !values.Has(k) { continue } v := values.Get(k) switch f.Type().Kind() { case reflect.Bool: switch v { case "on", "true", "1": f.SetBool(true) default: f.SetBool(false) } case reflect.Int, reflect.Int64: if i, e := strconv.ParseInt(v, 0, 0); e == nil { f.SetInt(i) } else { return fmt.Errorf("could not set int value of %s: %s", k, e) } case reflect.Float64: if fl, e := strconv.ParseFloat(v, 64); e == nil { f.SetFloat(fl) } else { return fmt.Errorf("could not set float64 value of %s: %s", k, e) } case reflect.String: f.SetString(v) default: return fmt.Errorf("unsupported format %v for field %s", f.Type().Kind(), k) } } return nil }