I'm trying to dynamically pass parameters to a URL route handler function. I thought it would be possible to use the reflection package to convert a map of values from the URL to the function that has one parameter that happens to be an anonymous struct. I've gotten as far as creating the struct to pass to the handler function but it ends up being a pointer to the struct. If I change the handler function's signature to expect a pointer the created struct ends up being a pointer to a pointer, I think. At any rate, here's the code (the panic follows):
Link: http://play.golang.org/p/vt_wNY1f08
package main
import (
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"github.com/gorilla/mux"
)
func mapToStruct(obj interface{}, mapping map[string]string) error {
dataStruct := reflect.Indirect(reflect.ValueOf(obj))
if dataStruct.Kind() != reflect.Struct {
return errors.New("expected a pointer to a struct")
}
for key, data := range mapping {
structField := dataStruct.FieldByName(key)
if !structField.CanSet() {
fmt.Println("Can't set")
continue
}
var v interface{}
switch structField.Type().Kind() {
case reflect.Slice:
v = data
case reflect.String:
v = string(data)
case reflect.Bool:
v = string(data) == "1"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
x, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Int64:
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Float32, reflect.Float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return errors.New("arg " + key + " as float64: " + err.Error())
}
v = x
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
structField.Set(reflect.ValueOf(v))
}
return nil
}
type RouteHandler struct {
Handler interface{}
}
func (h RouteHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
t := reflect.TypeOf(h.Handler)
handlerArgs := reflect.New(t.In(0)).Interface()
if err := mapToStruct(handlerArgs, mux.Vars(req)); err != nil {
panic(fmt.Sprintf("Error converting params"))
}
f := reflect.ValueOf(h.Handler)
args := []reflect.Value{reflect.ValueOf(handlerArgs)}
f.Call(args)
fmt.Fprint(w, "Hello World")
}
type App struct {
Router mux.Router
}
func (app *App) Run(bind string, port int) {
bind_to := fmt.Sprintf("%s:%d", bind, port)
http.Handle("/", &app.Router)
http.ListenAndServe(bind_to, &app.Router)
}
func (app *App) Route(pat string, h interface{}) {
app.Router.Handle(pat, RouteHandler{Handler:h})
}
func home(args struct{Category string}) {
fmt.Println("home", args.Category)
}
func main() {
app := &App{}
app.Route("/products/{Category}", home)
app.Run("0.0.0.0", 8080)
}
Panic:
2013/03/28 18:48:43 http: panic serving 127.0.0.1:51204: reflect: Call using *struct { Category string } as type struct { Category string }
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:589 (0x3fb66)
_func_004: buf.Write(debug.Stack())
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:1443 (0x11cdb)
panic: reflect·call(d->fn, d->args, d->siz);
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:428 (0x484ba)
Value.call: panic("reflect: " + method + " using " + xt.String() + " as type " + targ.String())
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:334 (0x47c3a)
Value.Call: return v.call("Call", in)
/Users/matt/Workspaces/Go/src/pants/pants.go:86 (0x2f36)
RouteHandler.ServeHTTP: f.Call(args)
/Users/matt/Workspaces/Go/src/pants/pants.go:1 (0x347c)
(*RouteHandler).ServeHTTP: package main
/Users/matt/Workspaces/Go/src/github.com/gorilla/mux/mux.go:86 (0x5a699)
com/gorilla/mux.(*Router).ServeHTTP: handler.ServeHTTP(w, req)
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:669 (0x337b6)
(*conn).serve: handler.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:271 (0xfde1)
goexit: runtime·goexit(void)