~samwhited/mux

4a11630d78fc4185bd0f0f865d4ecfa0362fac5d — Sam Whited 1 year, 2 months ago 97b9995 relax_path
mux: merge path and wildcard types
6 files changed, 20 insertions(+), 52 deletions(-)

M example_test.go
M mux.go
M node.go
M options.go
M params_test.go
M register_test.go
M example_test.go => example_test.go +2 -22
@@ 12,9 12,9 @@ import (
	"code.soquee.net/mux"
)

func Example_wild() {
func Example_path() {
	m := mux.New(
		mux.HandleFunc("GET", "/sha256/{wildcard wild}", func(w http.ResponseWriter, r *http.Request) {
		mux.HandleFunc("GET", "/sha256/{wildcard path}", func(w http.ResponseWriter, r *http.Request) {
			val, _ := mux.Param(r, "wildcard")
			sum := sha256.Sum256([]byte(val.Raw))
			fmt.Fprintf(w, "the hash of %q is %x", val.Raw, sum)


@@ 33,26 33,6 @@ func Example_wild() {
	// the hash of "a/b" is c14cddc033f64b9dea80ea675cf280a015e672516090a5626781153dc68fea11
}

func Example_path() {
	m := mux.New(
		mux.HandleFunc("GET", "/files/{p path}/cover.png", func(w http.ResponseWriter, r *http.Request) {
			val, _ := mux.Param(r, "p")
			fmt.Fprintf(w, "downloading file %q…", val.Raw)
		}),
	)

	server := httptest.NewServer(m)
	resp, err := http.Get(server.URL + "/files/myalbum/cover.png")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	io.Copy(os.Stdout, resp.Body)
	// Output:
	// downloading file "myalbum/cover.png"…
}

func Example_normalization() {
	m := mux.New(
		mux.HandleFunc("GET", "/profile/{username string}/personal", func(w http.ResponseWriter, r *http.Request) {

M mux.go => mux.go +6 -12
@@ 24,24 24,19 @@
//     float  eg. 1, 1.123, -1.123 (float64 in Go)
//     string eg. anything ({string} is the same as {})
//     path   eg. files/123.png
//     wild   eg. files/123.png (must be the last path component)
//
// All numeric types are 64 bits wide.
// Parameters of type "path" always match the current position of the input path
// but have their value set to the remainder of the path.
// The rest of the path continues to be matched as normal.
// Parameters of type "path" always match the remainder of the input path but
// also do not prevent future route parameters from being matched.
// For example, the route:
//
//     /file/{p path}/foo
//
// matches both /file/a/foo and /file/b/foo and p will have the value "a/foo"
// and "b/foo" respectively.
// Parameters of type "wild" are like "path" except that they match the
// remainder of the path and therefore may only appear as the final component of
// a route.
// For example, the route:
// Similarly, the route:
//
//     /file/{p wild}
//     /file/{p path}
//
// matches /file/foo.png and /file/myalbum/foo.png and p will have the value
// "foo.png" and "myalbum/foo.png" respectively.


@@ 69,8 64,7 @@ import (

const (
	typStatic = "static"
	typWild   = "wild"
	typPath   = "path"
	typWild   = "path"
	typString = "string"
	typUint   = "uint"
	typInt    = "int"


@@ 270,7 264,7 @@ func parseParam(pattern string) (name string, typ string) {
	}

	switch typ {
	case typInt, typUint, typFloat, typString, typWild, typPath:
	case typInt, typUint, typFloat, typString, typWild:
		return pattern[1:idx], typ
	}
	panic(fmt.Sprintf("invalid type: %q", typ))

M node.go => node.go +6 -8
@@ 20,13 20,6 @@ func (n *node) match(path string, offset uint, r *http.Request) (part string, re
		return "", "", r
	}

	// wildcards are a special case that always match the entire remainder of the
	// path.
	if n.typ == typWild {
		r = addValue(r, n.name, n.typ, path, offset, path)
		return path, "", r
	}

	part, remain = nextPart(path)
	switch n.typ {
	case typStatic:


@@ 37,8 30,13 @@ func (n *node) match(path string, offset uint, r *http.Request) (part string, re
	case typString:
		r = addValue(r, n.name, n.typ, part, offset, part)
		return part, remain, r
	case typPath:
	case typWild:
		r = addValue(r, n.name, n.typ, path, offset, path)

		// If we're the last node in the route, consume the remainder of the path.
		if len(n.child) == 0 {
			return path, "", r
		}
		return part, remain, r
	case typUint:
		v, err := strconv.ParseUint(part, 10, 64)

M options.go => options.go +0 -4
@@ 90,10 90,6 @@ func Handle(method, r string, h http.Handler) Option {
		for part, remain := nextPart(r); remain != "" || part != ""; part, remain = nextPart(remain) {
			name, typ := parseParam(part)

			if typ == typWild && remain != "" {
				panic(fmt.Sprintf("wildcards must be the last component in a route: /%s", r))
			}

			// If there are already children, check that this one is compatible with
			// them.
			if len(pointer.child) > 0 {

M params_test.go => params_test.go +2 -2
@@ 57,14 57,14 @@ var paramsTests = [...]struct {
		noMatch: true,
	},
	2: {
		routes: []string{"/one/{other wild}"},
		routes: []string{"/one/{other path}"},
		path:   "/one/two/three",
		params: []mux.ParamInfo{
			{
				Value:  "two/three",
				Raw:    "two/three",
				Name:   "other",
				Type:   "wild",
				Type:   "path",
				Offset: 5,
			},
		},

M register_test.go => register_test.go +4 -4
@@ 75,11 75,11 @@ var registerTests = [...]struct {
			{path: "/nope", code: 404},
		},
	},
	7: {panics: true, routes: func(t *testing.T) []mux.Option {
		return []mux.Option{mux.Handle("GET", "/{wild}/user", failHandler(t))}
	7: {routes: func(t *testing.T) []mux.Option {
		return []mux.Option{mux.Handle("GET", "/{path}/user", failHandler(t))}
	}},
	8: {panics: true, routes: func(t *testing.T) []mux.Option {
		return []mux.Option{mux.Handle("GET", "/{named wild}/user", failHandler(t))}
	8: {routes: func(t *testing.T) []mux.Option {
		return []mux.Option{mux.Handle("GET", "/{named path}/user", failHandler(t))}
	}},
	9: {panics: true, routes: func(t *testing.T) []mux.Option {
		return []mux.Option{