-
Notifications
You must be signed in to change notification settings - Fork 26
/
routes.go
273 lines (242 loc) · 6.26 KB
/
routes.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
package elasticsearch
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"strings"
"sync"
log "github.com/sirupsen/logrus"
"github.com/appbaseio/reactivesearch-api/middleware"
"github.com/appbaseio/reactivesearch-api/model/acl"
"github.com/appbaseio/reactivesearch-api/model/category"
"github.com/appbaseio/reactivesearch-api/model/op"
"github.com/appbaseio/reactivesearch-api/plugins"
"github.com/appbaseio/reactivesearch-api/plugins/auth"
"github.com/appbaseio/reactivesearch-api/util"
"github.com/gobuffalo/packr"
)
var (
routes []plugins.Route
routeSpecs = make(map[string]api)
acls = make(map[category.Category]map[acl.ACL]bool)
)
type api struct {
name string
category category.Category
acl acl.ACL
op op.Operation
spec *spec
}
type spec struct {
Documentation string `json:"documentation"`
Methods []string `json:"methods"`
URL struct {
Path string `json:"path"`
Paths []string `json:"paths,omitempty"`
Parts interface{} `json:"parts,omitempty"`
Params interface{} `json:"params,omitempty"`
} `json:"url"`
Body struct {
Description string `json:"description"`
Required bool `json:"required,omitempty"`
Serialize string `json:"serialize,omitempty"`
} `json:"body,omitempty"`
}
func (es *elasticsearch) preprocess(mw []middleware.Middleware) error {
files := make(chan string)
apis := make(chan api)
box := packr.NewBox("./api")
go fetchSpecFiles(&box, files)
go decodeSpecFiles(&box, files, apis)
middlewareFunction := (&chain{}).Wrap
for api := range apis {
for _, path := range api.spec.URL.Paths {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if path == "/" {
continue
}
r := plugins.Route{
Name: api.name,
Methods: api.spec.Methods,
Path: path,
HandlerFunc: middlewareFunction(mw, es.handler()),
Description: api.spec.Documentation,
}
routes = append(routes, r)
for _, method := range api.spec.Methods {
key := fmt.Sprintf("%s:%s", method, path)
routeSpecs[key] = api
}
}
if _, ok := acls[api.category]; !ok {
acls[api.category] = make(map[acl.ACL]bool)
}
if _, ok := acls[api.category][api.acl]; !ok {
acls[api.category][api.acl] = true
}
}
// sort the routes
criteria := func(r1, r2 plugins.Route) bool {
f1, c1 := util.CountComponents(r1.Path)
f2, c2 := util.CountComponents(r2.Path)
if f1 == f2 {
return c1 < c2
}
return f1 > f2
}
plugins.RouteBy(criteria).RouteSort(routes)
// append index route last in order to avoid early matches for other specific routes
indexRoute := plugins.Route{
Name: "ping",
Methods: []string{http.MethodGet, http.MethodHead},
Path: "/",
HandlerFunc: (&chain{}).Adapt(es.pingES(), classifyCategory, classifyOp, auth.BasicAuth()),
Description: "You know, for search",
}
healthCheckRoute := plugins.Route{
Name: "health check",
Methods: []string{http.MethodGet, http.MethodHead},
Path: "/arc/health",
HandlerFunc: es.healthCheck(),
Description: "Retrieve the cluster health, both appbase.io and Elasticsearch",
}
routes = append(routes, indexRoute, healthCheckRoute)
return nil
}
func (es *elasticsearch) routes() []plugins.Route {
return routes
}
func fetchSpecFiles(box *packr.Box, files chan<- string) {
defer close(files)
for _, file := range box.List() {
if filepath.Ext(file) == ".json" && !strings.HasPrefix(file, "_") {
files <- file
}
}
}
func decodeSpecFiles(box *packr.Box, files <-chan string, apis chan<- api) {
var wg sync.WaitGroup
for file := range files {
wg.Add(1)
go decodeSpecFile(box, file, &wg, apis)
}
go func() {
wg.Wait()
close(apis)
}()
}
func decodeSpecFile(box *packr.Box, file string, wg *sync.WaitGroup, apis chan<- api) {
defer wg.Done()
content, err := box.Find(file)
if err != nil {
log.Errorln("can't read file:", err)
return
}
decoder := json.NewDecoder(bytes.NewReader(content))
_, err = decoder.Token() // skip opening braces
if err != nil {
log.Fatal(err)
return
}
_, err = decoder.Token() // skip object name
if err != nil {
log.Fatal(err)
return
}
var s spec
err = decoder.Decode(&s)
if err != nil {
log.Fatal(err)
return
}
specName := strings.TrimSuffix(filepath.Base(file), ".json")
specCategory := decodeCategory(&s)
specOp := decodeOp(&s)
specACL, err := decodeACL(specName, &s)
if err != nil {
// info, ping specs don't have ACLs
if !(specName == "info" || specName == "ping") {
log.Errorln(logTag, ": unable to categorize spec", specName, ":", err)
}
}
apis <- api{
name: specName,
category: specCategory,
op: specOp,
acl: *specACL,
spec: &s,
}
}
func decodeCategory(spec *spec) category.Category {
docTokens := strings.Split(spec.Documentation, "/")
tag := strings.TrimSuffix(docTokens[len(docTokens)-1], ".html")
tagTokens := strings.Split(tag, "-")
tagName := tagTokens[0]
return category.FromString(tagName)
}
func decodeACL(specName string, spec *spec) (*acl.ACL, error) {
pathTokens := strings.Split(spec.URL.Path, "/")
for _, pathToken := range pathTokens {
if strings.HasPrefix(pathToken, "_") {
pathToken = strings.TrimPrefix(pathToken, "_")
c, err := acl.FromString(pathToken)
if err != nil {
return nil, err
}
return &c, nil
}
}
aclString := strings.Split(specName, ".")[0]
a, err := acl.FromString(aclString)
if err != nil {
defaultACL := acl.Get
return &defaultACL, err
}
return &a, nil
}
func decodeOp(spec *spec) op.Operation {
var specOp op.Operation
methods := spec.Methods
out:
for _, method := range methods {
switch method {
case http.MethodPut:
specOp = op.Write
break out
case http.MethodPatch:
specOp = op.Write
break out
case http.MethodDelete:
specOp = op.Delete
break out
case http.MethodGet:
specOp = op.Read
break out
case http.MethodHead:
specOp = op.Read
break out
case http.MethodPost:
specOp = op.Write
default:
specOp = op.Read
break out
}
}
return specOp
}
func printCategoryACLMDTable() {
log.Println("| **Category** | **ACLs** |")
log.Println("|----------|------|")
for c, a := range acls {
log.Println("| ", c, " | ")
log.Println("<ul>")
for k := range a {
log.Println("<li>", k, "</li>")
}
log.Println("</ul> |")
}
}