~samwhited/mux

2a6d65fe73ec0a502173dba9b4c2a78eef0d9355 — Sam Whited 1 year, 7 months ago 4facad3
mux: return param metadata
4 files changed, 72 insertions(+), 24 deletions(-)

M mux.go
M node.go
M params.go
M params_test.go
M mux.go => mux.go +6 -2
@@ 145,12 145,15 @@ func (mux *ServeMux) handler(r *http.Request) (http.Handler, *http.Request) {
		return node.handler, r
	}

	offset := uint(1)

nodeloop:
	for node != nil {
		// If this is a variable route,
		if len(node.child) == 1 && node.child[0].typ != typStatic {
			var part, remain string
			part, remain, r = node.child[0].match(path, r)
			part, remain, r = node.child[0].match(path, offset, r)
			offset += uint(len(part)) + 1

			// If the type doesn't match, we're done.
			if part == "" {


@@ 173,7 176,8 @@ nodeloop:
		// If this is a static route
		for _, child := range node.child {
			var part, remain string
			part, remain, r = child.match(path, r)
			part, remain, r = child.match(path, offset, r)
			offset += uint(len(part)) + 1
			// The child did not match, so check the next.
			if part == "" {
				path = remain

M node.go => node.go +13 -8
@@ 14,7 14,7 @@ type node struct {
	child []node
}

func (n *node) match(path string, r *http.Request) (part string, remain string, req *http.Request) {
func (n *node) match(path string, offset uint, r *http.Request) (part string, remain string, req *http.Request) {
	// Nil nodes never match.
	if n == nil {
		return "", "", r


@@ 23,7 23,7 @@ func (n *node) match(path string, r *http.Request) (part string, remain string, 
	// wildcards are a special case that always match the entire remainder of the
	// path.
	if n.typ == typWild {
		r = addValue(r, n.name, path)
		r = addValue(r, n.name, path, offset, path)
		return path, "", r
	}



@@ 35,36 35,41 @@ func (n *node) match(path string, r *http.Request) (part string, remain string, 
		}
		return "", path, r
	case typString:
		r = addValue(r, n.name, part)
		r = addValue(r, n.name, part, offset, part)
		return part, remain, r
	case typUint:
		v, err := strconv.ParseUint(part, 10, 64)
		if err != nil {
			return "", path, r
		}
		r = addValue(r, n.name, v)
		r = addValue(r, n.name, part, offset, v)
		return part, remain, r
	case typInt:
		v, err := strconv.ParseInt(part, 10, 64)
		if err != nil {
			return "", path, r
		}
		r = addValue(r, n.name, v)
		r = addValue(r, n.name, part, offset, v)
		return part, remain, r
	case typFloat:
		v, err := strconv.ParseFloat(part, 64)
		if err != nil {
			return "", path, r
		}
		r = addValue(r, n.name, v)
		r = addValue(r, n.name, part, offset, v)
		return part, remain, r
	}
	panic("unknown type")
}

func addValue(r *http.Request, name string, val interface{}) *http.Request {
func addValue(r *http.Request, name, raw string, offset uint, val interface{}) *http.Request {
	pinfo := ParamInfo{
		Value:  val,
		Raw:    raw,
		Offset: offset,
	}
	if name != "" {
		return r.WithContext(context.WithValue(r.Context(), ctxParam(name), val))
		return r.WithContext(context.WithValue(r.Context(), ctxParam(name), pinfo))
	}
	return r
}

M params.go => params.go +17 -2
@@ 4,7 4,22 @@ import (
	"net/http"
)

// ParamInfo represents a route parameter and related metadata.
type ParamInfo struct {
	// The parsed value of the parameter (for example int64(10))
	Value interface{}
	// The raw value of the parameter (for example "10")
	Raw string
	// The offset in the path where this parameter was found (for example if "10"
	// is parsed out of the path "/10" the offset is 1)
	Offset uint
}

// Param returns the named route parameter from the requests context.
func Param(r *http.Request, name string) interface{} {
	return r.Context().Value(ctxParam(name))
func Param(r *http.Request, name string) (pinfo ParamInfo, ok bool) {
	v := r.Context().Value(ctxParam(name))
	if v == nil {
		return ParamInfo{}, false
	}
	return v.(ParamInfo), true
}

M params_test.go => params_test.go +36 -12
@@ 22,17 22,33 @@ func TestInvalidType(t *testing.T) {
var paramsTests = [...]struct {
	routes  []string
	path    string
	params  map[string]interface{}
	params  map[string]mux.ParamInfo
	noMatch bool
}{
	0: {
		routes: []string{"/user/{account uint}/{user int}/{name string}/{f float}"},
		path:   "/user/123/-11/me/1.123",
		params: map[string]interface{}{
			"account": uint64(123),
			"user":    int64(-11),
			"name":    "me",
			"f":       float64(1.123),
		params: map[string]mux.ParamInfo{
			"account": mux.ParamInfo{
				Value:  uint64(123),
				Raw:    "123",
				Offset: 6,
			},
			"user": mux.ParamInfo{
				Value:  int64(-11),
				Raw:    "-11",
				Offset: 10,
			},
			"name": mux.ParamInfo{
				Value:  "me",
				Raw:    "me",
				Offset: 14,
			},
			"f": mux.ParamInfo{
				Value:  float64(1.123),
				Raw:    "1.123",
				Offset: 17,
			},
		},
	},
	1: {


@@ 43,8 59,12 @@ var paramsTests = [...]struct {
	2: {
		routes: []string{"/one/{other path}"},
		path:   "/one/two/three",
		params: map[string]interface{}{
			"other": "two/three",
		params: map[string]mux.ParamInfo{
			"other": mux.ParamInfo{
				Value:  "two/three",
				Raw:    "two/three",
				Offset: 5,
			},
		},
	},
	3: {


@@ 66,13 86,17 @@ const (
	notFoundStatusCode = 43
)

func paramsHandler(t *testing.T, params map[string]interface{}) http.HandlerFunc {
func paramsHandler(t *testing.T, params map[string]mux.ParamInfo) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(testStatusCode)
		for k, v := range params {
			val := mux.Param(r, k)
			if !reflect.DeepEqual(val, v) {
				t.Errorf("Params has wrong type for %[1]q, want=%[2]T(%[2]v), got=%[3]T(%[3]v)", k, v, val)
			pinfo, ok := mux.Param(r, k)
			if !ok {
				t.Errorf("No such parameter found %q", k)
				continue
			}
			if !reflect.DeepEqual(pinfo, v) {
				t.Errorf("Param do not match for %q, want=%+v, got=%+v)", k, v, pinfo)
			}
		}
	}