/
app_routing.go
296 lines (244 loc) · 7.7 KB
/
app_routing.go
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package gimlet
import (
"fmt"
"net/http"
"strings"
"github.com/mongodb/grip"
)
// APIRoute is a object that represents each route in the application
// and includes the route and associate internal metadata for the
// route.
type APIRoute struct {
route string
prefix string
methods []httpMethod
handler http.HandlerFunc
wrappers []Middleware
version int
overrideAppPrefix bool
isPrefix bool
}
func (r *APIRoute) String() string {
var methods []string
for _, m := range r.methods {
methods = append(methods, m.String())
}
return fmt.Sprintf(
"r='%s%s', v='%d', methods=[%s], defined=%t, prefixOverride=%t, isPrefex=%t",
r.prefix,
r.route,
r.version,
strings.Join(methods, ", "),
r.handler != nil,
r.overrideAppPrefix,
r.isPrefix,
)
}
// AddRoute is the primary method for creating and registering a new route with an
// application. Use as the root of a method chain, passing this method
// the path of the route.
func (a *APIApp) AddRoute(r string) *APIRoute {
route := &APIRoute{route: r, version: -1}
// data validation and cleanup
if !strings.HasPrefix(route.route, "/") {
route.route = "/" + route.route
}
a.routes = append(a.routes, route)
return route
}
// AddPrefixRoute creates and registers a new route with an application
// matching everything under the given prefix.
func (a *APIApp) AddPrefixRoute(prefix string) *APIRoute {
route := a.AddRoute(prefix)
route.isPrefix = true
return route
}
// Route returns a route object without a route name configured.
func (a *APIApp) Route() *APIRoute {
route := &APIRoute{version: -1}
a.routes = append(a.routes, route)
return route
}
// PrefixRoute allows you to create a new route with a
// prefix. Prefixes override the applications global prefix.
func (a *APIApp) PrefixRoute(p string) *APIRoute {
route := &APIRoute{prefix: p, version: -1}
if !strings.HasPrefix(route.prefix, "/") {
route.prefix = "/" + route.prefix
}
a.routes = append(a.routes, route)
return route
}
func (a *APIApp) Routes() []*APIRoute {
return a.routes
}
// Route allows you to set or reset the route path on an existing route.
func (r *APIRoute) Route(route string) *APIRoute {
r.route = route
if !strings.HasPrefix(r.route, "/") {
r.route = "/" + r.route
}
return r
}
// IsValid checks if a route has is valid and populated.
func (r *APIRoute) IsValid() bool {
switch {
case len(r.methods) == 0:
return false
case r.handler == nil:
return false
case r.route == "":
return false
default:
return true
}
}
// ClearWrappers resets the routes middlware wrappers.
func (r *APIRoute) ClearWrappers() { r.wrappers = []Middleware{} }
// Wrap adds a middleware that is applied specifically to this
// route. Route-specific middlware is applied after application specific
// middleware (when there's a route or application prefix) and before
// global application middleware (when merging applications without prefixes.)
func (r *APIRoute) Wrap(m ...Middleware) *APIRoute { r.wrappers = append(r.wrappers, m...); return r }
// Prefix allows per-route prefixes, which will override the application's global prefix if set.
func (r *APIRoute) Prefix(p string) *APIRoute {
if !strings.HasPrefix(p, "/") {
p = "/" + p
}
r.prefix = p
return r
}
// OverridePrefix forces the route's prefix to override the global app
// prefix. By default, the route's prefix is combined (after a
// version) with the application's prefix.
//
// When setting override prefix on an application that has a prefix
// specified, this option will not take effect in cases where you're
// merging multiple applications together.
func (r *APIRoute) OverridePrefix() *APIRoute { r.overrideAppPrefix = true; return r }
// Version allows you to specify an integer for the version of this
// route. Version is chainable.
func (r *APIRoute) Version(version int) *APIRoute {
if version < 0 {
grip.Warningf("%d is not a valid version", version)
}
r.version = version
return r
}
// Handler makes it possible to register an http.HandlerFunc with a
// route. Chainable. The common pattern for implementing these
// functions is to write functions and methods in your application
// that *return* handler functions, so you can pass application state
// or other data into to the handlers when the applications start,
// without relying on either global state *or* running into complex
// typing issues.
func (r *APIRoute) Handler(h http.HandlerFunc) *APIRoute {
if r.handler != nil {
grip.Warningf("called Handler more than once for route %s", r.route)
}
if h == nil {
grip.Alertf("adding nil route handler will probably result in runtime panics for '%s'", r.route)
}
r.handler = h
return r
}
// HandlerType is equivalent to Handler, but allows you to use a type
// that implements http.Handler rather than a function object.
func (r *APIRoute) HandlerType(h http.Handler) *APIRoute {
if r.handler != nil {
grip.Warningf("called Handler more than once for route %s", r.route)
}
if h == nil {
grip.Alertf("adding nil route handler will probably result in runtime panics for '%s'", r.route)
}
r.handler = h.ServeHTTP
return r
}
// RouteHandler defines a handler defined using the RouteHandler
// interface, which provides additional infrastructure for defining
// handlers, to separate input parsing, business logic, and response
// generation.
func (r *APIRoute) RouteHandler(h RouteHandler) *APIRoute {
if r.handler != nil {
grip.Warningf("called Handler more than once for route %s", r.route)
}
if h == nil {
grip.Alertf("adding nil route handler will probably result in runtime panics for '%s'", r.route)
}
r.handler = handleHandler(h)
return r
}
// Get is a chainable method to add a handler for the GET method to
// the current route. Routes may specify multiple methods.
func (r *APIRoute) Get() *APIRoute {
r.methods = append(r.methods, get)
return r
}
// Put is a chainable method to add a handler for the PUT method to
// the current route. Routes may specify multiple methods.
func (r *APIRoute) Put() *APIRoute {
r.methods = append(r.methods, put)
return r
}
// Post is a chainable method to add a handler for the POST method to
// the current route. Routes may specify multiple methods.
func (r *APIRoute) Post() *APIRoute {
r.methods = append(r.methods, post)
return r
}
// Delete is a chainable method to add a handler for the DELETE method
// to the current route. Routes may specify multiple methods.
func (r *APIRoute) Delete() *APIRoute {
r.methods = append(r.methods, del)
return r
}
// Patch is a chainable method to add a handler for the PATCH method
// to the current route. Routes may specify multiple methods.
func (r *APIRoute) Patch() *APIRoute {
r.methods = append(r.methods, patch)
return r
}
// Head is a chainable method to add a handler for the HEAD method
// to the current route. Routes may specify multiple methods.
func (r *APIRoute) Head() *APIRoute {
r.methods = append(r.methods, head)
return r
}
func (r *APIRoute) Options() *APIRoute {
r.methods = append(r.methods, options)
return r
}
func (r *APIRoute) AllMethods() *APIRoute {
r.methods = append(r.methods, get, put, post, del, patch, head, options)
return r
}
// Method makes it possible to specify an HTTP method pragmatically.
func (r *APIRoute) Method(m string) *APIRoute {
switch m {
case get.String():
return r.Get()
case put.String():
return r.Put()
case post.String():
return r.Post()
case del.String():
return r.Delete()
case patch.String():
return r.Patch()
case head.String():
return r.Head()
default:
return r
}
}
func (r *APIRoute) HasMethod(method string) bool {
for _, m := range r.methods {
if m.String() == method {
return true
}
}
return false
}
func (r *APIRoute) GetRoute() string {
return r.route
}