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}