summaryrefslogtreecommitdiff
path: root/vendor/github.com/go-zoo/bone/route.go
blob: 7f6c1b1d0f561617e3bd6a1f887810f006f31442 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/********************************
*** Multiplexer for Go        ***
*** Bone is under MIT license ***
*** Code by CodingFerret      ***
*** github.com/go-zoo         ***
*********************************/

package bone

import (
	"net/http"
	"regexp"
	"strings"
)

const (
	//PARAM value store in Atts if the route have parameters
	PARAM = 2
	//SUB value store in Atts if the route is a sub router
	SUB = 4
	//WC value store in Atts if the route have wildcard
	WC = 8
	//REGEX value store in Atts if the route contains regex
	REGEX = 16
)

// Route content the required information for a valid route
// Path: is the Route URL
// Size: is the length of the path
// Token: is the value of each part of the path, split by /
// Pattern: is content information about the route, if it's have a route variable
// handler: is the handler who handle this route
// Method: define HTTP method on the route
type Route struct {
	Path    string
	Method  string
	Size    int
	Atts    int
	wildPos int
	Token   Token
	Pattern map[int]string
	Compile map[int]*regexp.Regexp
	Tag     map[int]string
	Handler http.Handler
}

// Token content all value of a spliting route path
// Tokens: string value of each token
// size: number of token
type Token struct {
	raw    []int
	Tokens []string
	Size   int
}

// NewRoute return a pointer to a Route instance and call save() on it
func NewRoute(url string, h http.Handler) *Route {
	r := &Route{Path: url, Handler: h}
	r.save()
	return r
}

// Save, set automatically the the Route.Size and Route.Pattern value
func (r *Route) save() {
	r.Size = len(r.Path)
	r.Token.Tokens = strings.Split(r.Path, "/")
	for i, s := range r.Token.Tokens {
		if len(s) >= 1 {
			switch s[:1] {
			case ":":
				if r.Pattern == nil {
					r.Pattern = make(map[int]string)
				}
				r.Pattern[i] = s[1:]
				r.Atts |= PARAM
			case "#":
				if r.Compile == nil {
					r.Compile = make(map[int]*regexp.Regexp)
					r.Tag = make(map[int]string)
				}
				tmp := strings.Split(s, "^")
				r.Tag[i] = tmp[0][1:]
				r.Compile[i] = regexp.MustCompile("^" + tmp[1][:len(tmp[1])-1])
				r.Atts |= REGEX
			case "*":
				r.wildPos = i
				r.Atts |= WC
			default:
				r.Token.raw = append(r.Token.raw, i)
			}
		}
		r.Token.Size++
	}
}

// Match check if the request match the route Pattern
func (r *Route) Match(req *http.Request) bool {
	ok, _ := r.matchAndParse(req)
	return ok
}

// matchAndParse check if the request matches the route Pattern and returns a map of the parsed
// variables if it matches
func (r *Route) matchAndParse(req *http.Request) (bool, map[string]string) {
	ss := strings.Split(req.URL.EscapedPath(), "/")
	if r.matchRawTokens(&ss) {
		if len(ss) == r.Token.Size || r.Atts&WC != 0 {
			totalSize := len(r.Pattern)
			if r.Atts&REGEX != 0 {
				totalSize += len(r.Compile)
			}

			vars := make(map[string]string, totalSize)
			for k, v := range r.Pattern {
				vars[v] = ss[k]
			}

			if r.Atts&REGEX != 0 {
				for k, v := range r.Compile {
					if !v.MatchString(ss[k]) {
						return false, nil
					}
					vars[r.Tag[k]] = ss[k]
				}
			}

			return true, vars
		}
	}

	return false, nil
}

func (r *Route) parse(rw http.ResponseWriter, req *http.Request) bool {
	if r.Atts != 0 {
		if r.Atts&SUB != 0 {
			if len(req.URL.Path) >= r.Size {
				if req.URL.Path[:r.Size] == r.Path {
					req.URL.Path = req.URL.Path[r.Size:]
					r.Handler.ServeHTTP(rw, req)
					return true
				}
			}
		}

		if ok, vars := r.matchAndParse(req); ok {
			r.serveMatchedRequest(rw, req, vars)
			return true
		}
	}
	if req.URL.Path == r.Path {
		r.Handler.ServeHTTP(rw, req)
		return true
	}
	return false
}

func (r *Route) matchRawTokens(ss *[]string) bool {
	if len(*ss) >= r.Token.Size {
		for i, v := range r.Token.raw {
			if (*ss)[v] != r.Token.Tokens[v] {
				if r.Atts&WC != 0 && r.wildPos == i {
					return true
				}
				return false
			}
		}
		return true
	}
	return false
}

func (r *Route) exists(rw http.ResponseWriter, req *http.Request) bool {
	if r.Atts != 0 {
		if r.Atts&SUB != 0 {
			if len(req.URL.Path) >= r.Size {
				if req.URL.Path[:r.Size] == r.Path {
					return true
				}
			}
		}

		if ok, _ := r.matchAndParse(req); ok {
			return true
		}
	}
	if req.URL.Path == r.Path {
		return true
	}
	return false
}

// Get set the route method to Get
func (r *Route) Get() *Route {
	r.Method = "GET"
	return r
}

// Post set the route method to Post
func (r *Route) Post() *Route {
	r.Method = "POST"
	return r
}

// Put set the route method to Put
func (r *Route) Put() *Route {
	r.Method = "PUT"
	return r
}

// Delete set the route method to Delete
func (r *Route) Delete() *Route {
	r.Method = "DELETE"
	return r
}

// Head set the route method to Head
func (r *Route) Head() *Route {
	r.Method = "HEAD"
	return r
}

// Patch set the route method to Patch
func (r *Route) Patch() *Route {
	r.Method = "PATCH"
	return r
}

// Options set the route method to Options
func (r *Route) Options() *Route {
	r.Method = "OPTIONS"
	return r
}

func (r *Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if r.Method != "" {
		if req.Method == r.Method {
			r.Handler.ServeHTTP(rw, req)
			return
		}
		http.NotFound(rw, req)
		return
	}
	r.Handler.ServeHTTP(rw, req)
}