/
router.go
239 lines (205 loc) · 5.68 KB
/
router.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
package bluerpc
import (
"fmt"
"net/http"
"strings"
"time"
)
type Router struct {
procedures map[string]*ProcedureInfo
app *App
routes map[string]*Router
mux *http.ServeMux
absPath string
mws []Handler
validatorFn *validatorFn
authorizer *Authorizer
protected bool
}
func (router *Router) isAuthorized() bool {
return router.protected
}
func (router *Router) getAuthorizer() *Authorizer {
return router.authorizer
}
func (router *Router) getAbsPath() string {
if router.absPath == "" {
return "/"
}
return router.absPath
}
func (router *Router) Protected() *Router {
router.protected = true
return router
}
func (router *Router) Authorizer(a *Authorizer) *Router {
router.authorizer = a
return router
}
func (router *Router) addProcedure(slug string, info *ProcedureInfo) {
router.procedures[slug] = info
}
func (router *Router) getValidatorFn() *validatorFn {
return router.validatorFn
}
func (router *Router) getApp() *App {
return router.app
}
// changes the validator function for all of the connected procedures unless those procedures directly have validator functions set
func (r *Router) Validator(fn validatorFn) {
r.validatorFn = &fn
}
// Creates a new route or returns an existing one if that route was already created
func (r *Router) Router(slug string) *Router {
// Cannot have an empty prefix
if slug == "" {
panic("no relative path provided")
}
// Prefix always start with a '/' or '*'
if slug[0] != '/' {
slug = "/" + slug
}
// Check if the key exists in the map
route, exists := r.routes[slug]
if exists {
// Return the existing route
return route
}
//this handles the case where the user passes in a route with multiple slashes. Something like /users/image/info
//it just creates all of the necessary routes up to /info in the background
slugs, err := splitStringOnSlash(slug)
if err != nil {
panic(err)
}
currentRoute := r
if len(slugs) > 1 {
lastSlugIndex := len(slugs) - 1
for i := 0; i < lastSlugIndex; i++ {
prevRoute := currentRoute
currentRoute = prevRoute.Router(slugs[i])
}
}
newRouter := &Router{
absPath: r.absPath + slug,
mux: http.NewServeMux(),
routes: map[string]*Router{},
procedures: map[string]*ProcedureInfo{},
mws: []Handler{},
validatorFn: r.validatorFn,
app: r.app,
authorizer: r.authorizer,
}
if strings.ContainsRune(slug, ':') {
panic("You are not allowed to create dynamic routes. Read the docs from here :")
}
currentRoute.routes[slug] = newRouter
return newRouter
}
// prefix is the ROUTE PREFIX
// root is the ROOT folder
func (r *Router) Static(prefix, root string, config ...*Static) {
if root == "" {
root = "."
}
// Cannot have an empty prefix
if prefix == "" {
prefix = "/"
}
// Prefix always start with a '/' or '*'
if prefix[0] != '/' {
prefix = "/" + prefix
}
var actualConfig *Static
if len(config) > 0 {
actualConfig = config[0]
} else {
actualConfig = &Static{}
}
if actualConfig.CacheDuration == 0 {
actualConfig.CacheDuration = time.Second * 10
}
if actualConfig.Index == "" {
actualConfig.Index = "/index.html"
}
if actualConfig.Index[0] != '/' {
actualConfig.Index = "/" + actualConfig.Index
}
slugs, _ := splitStringOnSlash(prefix)
//if the prefix is not "/" then loop through each sub routes until you get to "/", then attach the static method to the last "/"
if prefix != "/" {
loopRoute := r
for i := 0; i < len(slugs); i++ {
prevRoute := loopRoute
loopRoute = prevRoute.Router(slugs[i])
}
loopRoute.Static("/", root)
return
}
r.addProcedure("/", &ProcedureInfo{
method: STATIC,
validatorFn: r.validatorFn,
handler: createStaticFunction(prefix, root, actualConfig),
})
}
func (r *Router) Use(middlewares ...Handler) {
if len(middlewares) == 0 {
panic("Use called without any middleware arguments")
}
r.mws = append(r.mws, middlewares...)
}
// prints all of the info for this route, including its procedures attached and all of its nested routes
func (r *Router) PrintInfo() {
absPath := r.getAbsPath()
if absPath == "" {
absPath = "/"
}
generalInfo := fmt.Sprintf("%s Route : %s %s", DefaultColors.Green, absPath, DefaultColors.Reset)
if len(r.procedures) == 0 {
generalInfo += fmt.Sprintf("%s No Procedures %s", DefaultColors.Red, DefaultColors.Reset)
}
fmt.Println(generalInfo)
for slug, procInfo := range r.procedures {
pathAndMethod := fmt.Sprintf(" %s : %s ", procInfo.method, slug)
inputsAndOutputs := &strings.Builder{}
inputsAndOutputs.WriteString("(")
switch procInfo.method {
case QUERY:
queryType := getType(procInfo.querySchema)
inputsAndOutputs.WriteString(goToTsObj(queryType))
case MUTATION:
inputsAndOutputs.WriteString("{")
inputsAndOutputs.WriteString("query:")
queryType := getType(procInfo.querySchema)
inputsAndOutputs.WriteString(goToTsObj(queryType))
inputsAndOutputs.WriteString(",")
inputType := getType(procInfo.inputSchema)
inputsAndOutputs.WriteString("input:")
inputsAndOutputs.WriteString(goToTsObj(inputType))
inputsAndOutputs.WriteString("}")
}
if procInfo.method == QUERY || procInfo.method == MUTATION {
inputsAndOutputs.WriteString(")=>")
outputType := getType(procInfo.outputSchema)
inputsAndOutputs.WriteString(goTypeToTSType(outputType))
}
fmt.Println(pathAndMethod + inputsAndOutputs.String())
}
for _, nestedRoute := range r.routes {
nestedRoute.PrintInfo()
}
}
func createCtx(w http.ResponseWriter, r *http.Request) *Ctx {
return &Ctx{
httpW: w,
httpR: r,
}
}
func methodsMatch(httpMethod string, bluerpcMethod Method) bool {
switch bluerpcMethod {
case QUERY:
return httpMethod == "GET"
case MUTATION:
return httpMethod == "POST"
}
return false
}