This repository has been archived by the owner on Jan 17, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmedeina.go
226 lines (200 loc) · 6.21 KB
/
medeina.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
// Copyright (c) 2014 Dario Castañé. Licensed under the MIT License.
package medeina
import (
"bytes"
"fmt"
"github.com/julienschmidt/httprouter"
"github.com/oleiade/lane"
"net/http"
"net/url"
"strings"
)
// Internal router struct. It can be useful to keep Medeina
// router-agnostic.
type router struct {
*httprouter.Router
}
// Medeina is a goddess willing to help you with your trees... of routes.
// Allow it to be part of your chain of HTTP Handlers and she will handle
// all those messy branches that your once-used-to-be-simple router got.
type Medeina struct {
router *router
methods *lane.Stack
path *lane.Deque
}
// Medeina closures definition.
type Handle func()
// HTTP Methods available as constants.
// We could use strings but it was cleaner to force
// specefic values in an enum-like fashion.
type Method string
type Params httprouter.Params
const (
GET = "GET"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
)
var Methods = []Method{GET, POST, PUT, PATCH, DELETE}
// Joins a deque using slashes. This is not a
// generic function.
func joinDeque(s *lane.Deque) string {
var (
buffer bytes.Buffer
bDeque *lane.Deque
)
bDeque = lane.NewDeque()
for e := s.Shift(); e != nil; e = s.Shift() {
subpath := fmt.Sprintf("%v", e)
if !(subpath == "" && s.Empty()) {
buffer.WriteString("/")
buffer.WriteString(subpath)
}
bDeque.Append(e)
}
for e := bDeque.Shift(); e != nil; e = bDeque.Shift() {
s.Append(e)
}
return buffer.String()
}
var (
// Make sure this conforms with the http.Handle interface
// as in julienschmidt/httprouter.
_ http.Handler = (*Medeina)(nil)
)
// Returns a new initialized Medeina tree routing with default httprouter's one.
func NewMedeina() *Medeina {
return &Medeina{
router: &router{
httprouter.New(),
},
methods: lane.NewStack(),
path: lane.NewDeque(),
}
}
// Core logic of handling routes in a tree.
func (m *Medeina) handle(method Method, handle Handle) {
m.methods.Push(method)
handle()
m.methods.Pop()
}
// Switches context to use GET method as default in the closure.
// You can override for a route while using Is, setting which
// methods you want.
func (m *Medeina) GET(handles Handle) {
m.handle("GET", handles)
}
// Switches context to use POST method as default in the closure.
func (m *Medeina) POST(handles Handle) {
m.handle("POST", handles)
}
// Switches context to use PUT method as default in the closure.
func (m *Medeina) PUT(handles Handle) {
m.handle("PUT", handles)
}
// Switches context to use PATCH method as default in the closure.
func (m *Medeina) PATCH(handles Handle) {
m.handle("PATCH", handles)
}
// Switches context to use DELETE method as default in the closure.
func (m *Medeina) DELETE(handles Handle) {
m.handle("DELETE", handles)
}
// Adds a new subpath to the current context. Everything under the
// closure will use all the previously set path as root for their
// URLs.
func (m *Medeina) On(path string, handle Handle) {
m.path.Append(path)
handle()
m.path.Pop()
}
// As On but using a function which accepts a routing tree as parameter.
// This will be useful to split routes definition in several functions.
func (m *Medeina) OnFunc(path string, handle func(*Medeina)) {
m.path.Append(path)
handle(m)
m.path.Pop()
}
// As On but using a function which accepts a standard http.Handler,
// delegating further route handling to the handler. It adds a HttpRouter
// catch-all matcher called 'medeina_subpath'.
// This will be useful to split routes definition in several functions.
func (m *Medeina) OnHandler(path string, handle http.Handler) {
m.path.Append(path)
m.Handler("*medeina_subpath", handle, Methods...)
m.path.Pop()
}
// As OnHandler for subrouters. It's a convenience function. These two
// calls are equivalent:
//
// m.OnHandler("api/v1/events", HandlerPathPrefix("/api/v1/events", mux))
// m.OnMux("api/v1/events", mux)
func (m *Medeina) OnMux(path string, handle http.Handler) {
var path_slash string
if path[0] != '/' {
path_slash = "/" + path
} else {
path_slash = path
}
m.OnHandler(path, HandlerPathPrefix(path_slash, handle))
}
// Sets a canonical path. A canonical path means no further entries are in the path.
func (m *Medeina) Is(path string, handle httprouter.Handle, methods ...Method) {
m.path.Append(path)
fullPath := joinDeque(m.path)
m.path.Pop()
// If any method is provided, it overrides the default one.
if len(methods) > 0 {
for _, method := range methods {
sm := string(method)
m.router.Handle(sm, fullPath, handle)
}
} else {
method := m.methods.Head()
if method == nil {
panic(fmt.Errorf("you cannot set an endpoint outside a HTTP method scope or without passing methods by parameter"))
}
m.router.Handle(string(method.(Method)), fullPath, handle)
}
}
// As Is but delegateing on a standard http.Handler.
// There is no equivalent functions for specific HTTP methods, so you must use
// this in order to add standard http.Handlers.
func (m *Medeina) Handler(path string, handle http.Handler, methods ...Method) {
m.path.Append(path)
fullPath := joinDeque(m.path)
m.path.Pop()
// If any method is provided, it overrides the default one.
if len(methods) > 0 {
for _, method := range methods {
sm := string(method)
m.router.Handler(sm, fullPath, handle)
}
} else {
method := m.methods.Head()
if method == nil {
panic(fmt.Errorf("you cannot set an endpoint outside a HTTP method scope or without passing methods by parameter"))
}
m.router.Handler(string(method.(Method)), fullPath, handle)
}
}
// Utility function to use with http.Handler compatible routers. Modifies
// the request's URL in order to make subrouters relative to the prefix.
// If you use a router as subrouter without this they need to match the full
// path.
func HandlerPathPrefix(prefix string, handle http.Handler) http.Handler {
if !strings.HasPrefix(prefix, "/") {
prefix = fmt.Sprintf("/%s", prefix)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
old := r.URL
r.URL, _ = url.ParseRequestURI(strings.Replace(old.Path, prefix, "", 1))
handle.ServeHTTP(w, r)
r.URL = old
})
}
// Makes the routing tree implement the http.Handler interface.
func (m *Medeina) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m.router.ServeHTTP(w, r)
}