forked from evergreen-ci/gimlet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp_merge.go
156 lines (130 loc) · 4.25 KB
/
app_merge.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
package gimlet
import (
"net/http"
"github.com/gorilla/mux"
"github.com/mongodb/grip"
"github.com/pkg/errors"
"github.com/urfave/negroni"
)
// AssembleHandler takes a router and one or more applications and
// returns an application.
//
// Eventually the router will become an implementation detail of
// this/related functions.
func AssembleHandler(router *mux.Router, apps ...*APIApp) (http.Handler, error) {
catcher := grip.NewBasicCatcher()
mws := []Middleware{}
seenPrefixes := make(map[string]struct{})
for _, app := range apps {
if app.prefix != "" {
if _, ok := seenPrefixes[app.prefix]; ok {
catcher.Add(errors.Errorf("route prefix '%s' defined more than once", app.prefix))
}
seenPrefixes[app.prefix] = struct{}{}
n := negroni.New()
for _, m := range app.middleware {
n.Use(m)
}
r := router.PathPrefix(app.prefix).Subrouter()
catcher.Add(app.attachRoutes(r, false)) // this adds wrapper middlware
n.UseHandler(r)
router.PathPrefix(app.prefix).Handler(n)
} else {
mws = append(mws, app.middleware...)
catcher.Add(app.attachRoutes(router, true))
}
}
if catcher.HasErrors() {
return nil, catcher.Resolve()
}
n := negroni.New()
for _, m := range mws {
n.Use(m)
}
n.UseHandler(router)
return n, nil
}
// MergeApplications takes a number of gimlet applications and
// resolves them, returning an http.Handler.
func MergeApplications(apps ...*APIApp) (http.Handler, error) {
if len(apps) == 0 {
return nil, errors.New("must specify at least one application")
}
return AssembleHandler(mux.NewRouter().UseEncodedPath(), apps...)
}
// Merge takes multiple application instances and merges all of their
// routes into a single application.
//
// You must only call Merge once per base application, and you must
// pass more than one or more application to merge. Additionally, it
// is not possible to merge applications into a base application that
// has a prefix specified.
//
// When the merging application does not have a prefix, the merge
// operation will error if you attempt to merge applications that have
// duplicate cases. Similarly, you cannot merge multiple applications
// that have the same prefix: you should treat these errors as fatal.
func (a *APIApp) Merge(apps ...*APIApp) error {
if a.prefix != "" {
return errors.New("cannot merge applications into an application with a prefix")
}
if apps == nil {
return errors.New("must specify apps to merge")
}
if a.hasMerged {
return errors.New("can only call merge once per root application")
}
catcher := grip.NewBasicCatcher()
seenPrefixes := make(map[string]struct{})
for _, app := range apps {
if app.prefix != "" {
if _, ok := seenPrefixes[app.prefix]; ok {
catcher.Errorf("route prefix '%s' defined more than once", app.prefix)
}
seenPrefixes[app.prefix] = struct{}{}
for _, route := range app.routes {
r := a.PrefixRoute(app.prefix).Route(route.route).Version(route.version).Handler(route.handler)
for _, m := range route.methods {
r = r.Method(m.String())
}
r.overrideAppPrefix = route.overrideAppPrefix
r.wrappers = append(app.middleware, route.wrappers...)
}
} else if app.middleware == nil {
for _, r := range app.routes {
if a.containsRoute(r.route, r.version, r.methods) {
catcher.Errorf("cannot merge route '%s' with existing application that already has this route defined", r.route)
}
}
a.routes = append(a.routes, app.routes...)
} else {
for _, route := range app.routes {
if a.containsRoute(route.route, route.version, route.methods) {
catcher.Errorf("cannot merge route '%s' with existing application that already has this route defined", route.route)
}
r := a.Route().Route(route.route).Version(route.version)
for _, m := range route.methods {
r = r.Method(m.String())
}
r.overrideAppPrefix = route.overrideAppPrefix
r.wrappers = append(app.middleware, route.wrappers...)
}
}
}
a.hasMerged = true
return catcher.Resolve()
}
func (a *APIApp) containsRoute(path string, version int, methods []httpMethod) bool {
for _, r := range a.routes {
if r.route == path && r.version == version {
for _, m := range r.methods {
for _, rm := range methods {
if m == rm {
return true
}
}
}
}
}
return false
}