package web import ( "net/http" "path" "regexp" "strings" ) type routeKey struct { method, path string prefix bool } type patternTable struct { routes map[string]map[string]http.Handler shapes map[string][]string } var placeholder = regexp.MustCompile(`^\{[A-Za-z_][A-Za-z0-9_]*\}$`) func newPatternTable() patternTable { return patternTable{routes: map[string]map[string]http.Handler{}, shapes: map[string][]string{}} } func isPattern(p string) bool { return strings.ContainsAny(p, "{}") } func isPlaceholder(s string) bool { return strings.HasPrefix(s, "{") } func (k *Kit) statusPage(status int) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { k.ErrorPage(w, r, status) }) } func (t patternTable) add(rt Route) map[string]map[string]http.Handler { segs := strings.Split(rt.Path[1:], "/") ok := !rt.Prefix && path.Clean(rt.Path) == rt.Path seen := map[string]bool{} for _, s := range segs { switch { case placeholder.MatchString(s): ok = ok && !seen[s] seen[s] = true case strings.ContainsAny(s, "{}"): ok = false } } if !ok { panic("web: a pattern route is a clean path whose placeholders are whole, distinct {name} segments: " + rt.Path) } t.shapes[rt.Path] = segs return t.routes } func overlaps(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if !isPlaceholder(a[i]) && !isPlaceholder(b[i]) && a[i] != b[i] { return false } } return true } func (t patternTable) refuseOverlaps(prefixes map[string]map[string]http.Handler) { others := map[string][]string{} for p, s := range t.shapes { others[p] = s } for p := range prefixes { s := strings.Split(p[1:], "/") s[len(s)-1] = "{segment}" others[p+"*"] = s } for p, s := range t.shapes { for q, o := range others { if p != q && overlaps(s, o) { panic("web: ambiguous routes " + p + " and " + q) } } } } func fits(shape, segs []string) bool { if len(shape) != len(segs) { return false } for i, s := range shape { if segs[i] == "" || !isPlaceholder(s) && s != segs[i] { return false } } return true } func (t patternTable) match(r *http.Request) (http.Handler, bool) { p := r.URL.Path if path.Clean(p) != p { return nil, false } segs := strings.Split(p[1:], "/") for pat, shape := range t.shapes { if !fits(shape, segs) { continue } handler, ok := t.routes[pat][r.Method] if ok { for i, s := range shape { if isPlaceholder(s) { r.SetPathValue(s[1:len(s)-1], segs[i]) } } } return handler, ok } return nil, false }