Raw
  1package web
  2
  3import (
  4	"net/http"
  5	"path"
  6	"regexp"
  7	"strings"
  8)
  9
 10type routeKey struct {
 11	method, path string
 12	prefix       bool
 13}
 14
 15type patternTable struct {
 16	routes map[string]map[string]http.Handler
 17	shapes map[string][]string
 18}
 19
 20var placeholder = regexp.MustCompile(`^\{[A-Za-z_][A-Za-z0-9_]*\}$`)
 21
 22func newPatternTable() patternTable {
 23	return patternTable{routes: map[string]map[string]http.Handler{}, shapes: map[string][]string{}}
 24}
 25
 26func isPattern(p string) bool {
 27	return strings.ContainsAny(p, "{}")
 28}
 29
 30func isPlaceholder(s string) bool {
 31	return strings.HasPrefix(s, "{")
 32}
 33
 34func (k *Kit) statusPage(status int) http.Handler {
 35	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { k.ErrorPage(w, r, status) })
 36}
 37
 38func (t patternTable) add(rt Route) map[string]map[string]http.Handler {
 39	segs := strings.Split(rt.Path[1:], "/")
 40	ok := !rt.Prefix && path.Clean(rt.Path) == rt.Path
 41	seen := map[string]bool{}
 42	for _, s := range segs {
 43		switch {
 44		case placeholder.MatchString(s):
 45			ok = ok && !seen[s]
 46			seen[s] = true
 47		case strings.ContainsAny(s, "{}"):
 48			ok = false
 49		}
 50	}
 51	if !ok {
 52		panic("web: a pattern route is a clean path whose placeholders are whole, distinct {name} segments: " + rt.Path)
 53	}
 54	t.shapes[rt.Path] = segs
 55	return t.routes
 56}
 57
 58func overlaps(a, b []string) bool {
 59	if len(a) != len(b) {
 60		return false
 61	}
 62	for i := range a {
 63		if !isPlaceholder(a[i]) && !isPlaceholder(b[i]) && a[i] != b[i] {
 64			return false
 65		}
 66	}
 67	return true
 68}
 69
 70func (t patternTable) refuseOverlaps(prefixes map[string]map[string]http.Handler) {
 71	others := map[string][]string{}
 72	for p, s := range t.shapes {
 73		others[p] = s
 74	}
 75	for p := range prefixes {
 76		s := strings.Split(p[1:], "/")
 77		s[len(s)-1] = "{segment}"
 78		others[p+"*"] = s
 79	}
 80	for p, s := range t.shapes {
 81		for q, o := range others {
 82			if p != q && overlaps(s, o) {
 83				panic("web: ambiguous routes " + p + " and " + q)
 84			}
 85		}
 86	}
 87}
 88
 89func fits(shape, segs []string) bool {
 90	if len(shape) != len(segs) {
 91		return false
 92	}
 93	for i, s := range shape {
 94		if segs[i] == "" || !isPlaceholder(s) && s != segs[i] {
 95			return false
 96		}
 97	}
 98	return true
 99}
100
101func (t patternTable) match(r *http.Request) (http.Handler, bool) {
102	p := r.URL.Path
103	if path.Clean(p) != p {
104		return nil, false
105	}
106	segs := strings.Split(p[1:], "/")
107	for pat, shape := range t.shapes {
108		if !fits(shape, segs) {
109			continue
110		}
111		handler, ok := t.routes[pat][r.Method]
112		if ok {
113			for i, s := range shape {
114				if isPlaceholder(s) {
115					r.SetPathValue(s[1:len(s)-1], segs[i])
116				}
117			}
118		}
119		return handler, ok
120	}
121	return nil, false
122}