@@ -9,85 +9,94 @@ import (
99
1010// Cage is a container for all routes and sub-cages to handle routing and wildcard matching for a parrot
1111// Note: Should only be used internally by the parrot.
12- type Cage struct {
13- * CageLevel
12+ type cage struct {
13+ * cageLevel
1414}
1515
16+ // MethodAny will match to any other method
17+ const MethodAny = "ANY"
18+
1619// CageLevel holds a single level of routes and further sub cages
1720// Note: Should only be used internally by the parrot.
18- type CageLevel struct {
19- // Routes contains all of the plain routes at this current cage level
20- // path -> route
21- Routes map [string ]* Route `json:"routes"`
22- routesRWMu sync.RWMutex // sync.Map might be better here, but eh
23- // WildCardRoutes contains all the wildcard routes at this current cage level
24- // path -> route
25- WildCardRoutes map [string ]* Route `json:"wild_card_routes"`
26- wildCardRoutesRWMu sync.RWMutex
27- // SubCages contains sub cages at this current cage level
21+ type cageLevel struct {
22+ // cagePath is the path to this cage level
23+ cagePath string
24+ // rwMu is a read write mutex for the cage level
25+ rwMu sync.RWMutex
26+ // TODO: Make all lowercase
27+ // routes contains all of the plain routes at this current cage level
28+ // route.path -> route.method -> route
29+ routes map [string ]map [string ]* Route
30+ // wildCardRoutes contains all the wildcard routes at this current cage level
31+ // route.path -> route.method -> route
32+ wildCardRoutes map [string ]map [string ]* Route
33+ // subCages contains sub cages at this current cage level
2834 // cage name -> cage level
29- SubCages map [string ]* CageLevel `json:"sub_cages"`
30- subCagesRWMu sync.RWMutex
31- // WildCardSubCages contains wildcard sub cages at this current cage level
35+ subCages map [string ]* cageLevel
36+ // wildCardSubCages contains wildcard sub cages at this current cage level
3237 // cage name -> cage level
33- WildCardSubCages map [string ]* CageLevel `json:"wild_card_sub_cages"`
34- wildCardSubCagesRWMu sync.RWMutex
38+ wildCardSubCages map [string ]* cageLevel
3539}
3640
3741// newCage creates a new cage with an empty cage level for a new parrot instance
38- func newCage () * Cage {
39- return & Cage {
40- CageLevel : newCageLevel (),
42+ func newCage () * cage {
43+ return & cage {
44+ cageLevel : newCageLevel ("/" ),
4145 }
4246}
4347
4448// newCageLevel creates a new cageLevel with empty maps
45- func newCageLevel () * CageLevel {
46- return & CageLevel {
47- Routes : make (map [string ]* Route ),
48- WildCardRoutes : make (map [string ]* Route ),
49- SubCages : make (map [string ]* CageLevel ),
50- WildCardSubCages : make (map [string ]* CageLevel ),
49+ func newCageLevel (cagePath string ) * cageLevel {
50+ return & cageLevel {
51+ cagePath : cagePath ,
52+ routes : make (map [string ]map [string ]* Route ),
53+ wildCardRoutes : make (map [string ]map [string ]* Route ),
54+ subCages : make (map [string ]* cageLevel ),
55+ wildCardSubCages : make (map [string ]* cageLevel ),
5156 }
5257}
5358
5459// cageLevel searches for a cage level based on the path provided
5560// If createMode is true, it will create any cage levels if they don't exist
56- func (c * Cage ) cageLevel (path string , createMode bool ) (cageLevel * CageLevel , routeSegment string , err error ) {
61+ func (c * cage ) getCageLevel (path string , createMode bool ) (cageLevel * cageLevel , err error ) {
5762 splitPath := strings .Split (path , "/" )
58- routeSegment = splitPath [len (splitPath )- 1 ] // The final path segment is the route
59- splitPath = splitPath [:len (splitPath )- 1 ] // Only looking for the cage level, exclude the route
63+ splitPath = splitPath [:len (splitPath )- 1 ] // Only looking for the cage level, exclude the route
6064 if splitPath [0 ] == "" {
6165 splitPath = splitPath [1 :] // Remove the empty string at the beginning of the split
6266 }
63- currentCageLevel := c .CageLevel
67+ currentCageLevel := c .cageLevel
6468
6569 for _ , pathSegment := range splitPath { // Iterate through each path segment to look for matches
6670 cageLevel , found , err := currentCageLevel .subCageLevel (pathSegment , createMode )
6771 if err != nil {
68- return nil , routeSegment , err
72+ return nil , err
6973 }
7074 if found {
7175 currentCageLevel = cageLevel
7276 continue
7377 }
7478
7579 if ! found {
76- return nil , routeSegment , newDynamicError (ErrCageNotFound , fmt .Sprintf ("path: '%s'" , path ))
80+ return nil , newDynamicError (ErrCageNotFound , fmt .Sprintf ("path: '%s'" , path ))
7781 }
7882 }
7983
80- return currentCageLevel , routeSegment , nil
84+ return currentCageLevel , nil
8185}
8286
8387// getRoute searches for a route based on the path provided
84- func (c * Cage ) getRoute (path string ) (* Route , error ) {
85- cageLevel , routeSegment , err := c .cageLevel ( path , false )
88+ func (c * cage ) getRoute (routePath , routeMethod string ) (* Route , error ) {
89+ cageLevel , err := c .getCageLevel ( routePath , false )
8690 if err != nil {
8791 return nil , err
8892 }
93+ routeSegments := strings .Split (routePath , "/" )
94+ if len (routeSegments ) == 0 {
95+ return nil , ErrRouteNotFound
96+ }
97+ routeSegment := routeSegments [len (routeSegments )- 1 ]
8998
90- route , found , err := cageLevel .route (routeSegment )
99+ route , found , err := cageLevel .route (routeSegment , routeMethod )
91100 if err != nil {
92101 return nil , err
93102 }
@@ -99,65 +108,116 @@ func (c *Cage) getRoute(path string) (*Route, error) {
99108}
100109
101110// newRoute creates a new route, creating new cages if necessary
102- func (c * Cage ) newRoute (route * Route ) error {
103- cageLevel , routeSegment , err := c .cageLevel (route .Path , true )
111+ func (c * cage ) newRoute (route * Route ) error {
112+ cageLevel , err := c .getCageLevel (route .Path , true )
104113 if err != nil {
105114 return err
106115 }
107116
108- if strings .Contains (routeSegment , "*" ) {
109- cageLevel .wildCardRoutesRWMu .Lock ()
110- defer cageLevel .wildCardRoutesRWMu .Unlock ()
111- cageLevel .WildCardRoutes [routeSegment ] = route
112- } else {
113- cageLevel .routesRWMu .Lock ()
114- defer cageLevel .routesRWMu .Unlock ()
115- cageLevel .Routes [routeSegment ] = route
116- }
117+ cageLevel .newRoute (route )
117118
118119 return nil
119120}
120121
121- // deleteRoute deletes a route based on the path provided
122- func (c * Cage ) deleteRoute (route * Route ) error {
123- cageLevel , routeSegment , err := c .cageLevel (route .Path , true )
122+ // deleteRoute deletes a route
123+ func (c * cage ) deleteRoute (route * Route ) error {
124+ cageLevel , err := c .getCageLevel (route .Path , false )
124125 if err != nil {
125126 return err
126127 }
127128
128- if strings .Contains (routeSegment , "*" ) {
129- cageLevel .wildCardRoutesRWMu .Lock ()
130- defer cageLevel .wildCardRoutesRWMu .Unlock ()
131- delete (cageLevel .WildCardRoutes , routeSegment )
129+ if strings .Contains (route .Segment (), "*" ) {
130+ cageLevel .rwMu .RLock ()
131+ if _ , found := cageLevel .wildCardRoutes [route .Segment ()][route .Method ]; ! found {
132+ cageLevel .rwMu .RUnlock ()
133+ return ErrRouteNotFound
134+ }
135+ cageLevel .rwMu .RUnlock ()
136+
137+ cageLevel .rwMu .Lock ()
138+ delete (cageLevel .wildCardRoutes [route .Segment ()], route .Method )
139+ cageLevel .rwMu .Unlock ()
132140 } else {
133- cageLevel .routesRWMu .Lock ()
134- defer cageLevel .routesRWMu .Unlock ()
135- delete (cageLevel .Routes , routeSegment )
141+ cageLevel .rwMu .RLock ()
142+ if _ , found := cageLevel .routes [route .Segment ()][route .Method ]; ! found {
143+ cageLevel .rwMu .RUnlock ()
144+ return ErrRouteNotFound
145+ }
146+ cageLevel .rwMu .RUnlock ()
147+
148+ cageLevel .rwMu .Lock ()
149+ delete (cageLevel .routes [route .Segment ()], route .Method )
150+ cageLevel .rwMu .Unlock ()
136151 }
137152
138153 return nil
139154}
140155
156+ // routes returns all the routes in the cage
157+ func (c * cage ) routes () []* Route {
158+ return c .routesRecursive ()
159+ }
160+
161+ // routesRecursive returns all the routes in the cage recursively.
162+ // Should only be used internally by the cage. Use routes() instead.
163+ func (cl * cageLevel ) routesRecursive () (routes []* Route ) {
164+ // Add all the routes at this level
165+ cl .rwMu .RLock ()
166+ for _ , routePath := range cl .routes {
167+ for _ , route := range routePath {
168+ routes = append (routes , route )
169+ }
170+ }
171+
172+ // Add all the wildcard routes at this level
173+ for _ , routePath := range cl .wildCardRoutes {
174+ for _ , route := range routePath {
175+ routes = append (routes , route )
176+ }
177+ }
178+ cl .rwMu .RUnlock ()
179+
180+ for _ , subCage := range cl .subCages {
181+ routes = append (routes , subCage .routesRecursive ()... )
182+ }
183+ for _ , subCage := range cl .wildCardSubCages {
184+ routes = append (routes , subCage .routesRecursive ()... )
185+ }
186+
187+ return routes
188+ }
189+
141190// route searches for a route based on the route segment provided
142- func (cl * CageLevel ) route (routeSegment string ) (route * Route , found bool , err error ) {
191+ func (cl * cageLevel ) route (routeSegment , routeMethod string ) (route * Route , found bool , err error ) {
143192 // First check for an exact match
144- cl .routesRWMu .Lock ()
145- if route , found = cl .Routes [routeSegment ]; found {
146- defer cl .routesRWMu .Unlock ()
147- return route , true , nil
193+ cl .rwMu .RLock ()
194+ defer cl .rwMu .RUnlock ()
195+
196+ if _ , ok := cl .routes [routeSegment ]; ok {
197+ if route , found = cl.routes [routeSegment ][routeMethod ]; found {
198+ return route , true , nil
199+ }
200+ }
201+ if _ , ok := cl .wildCardRoutes [routeSegment ]; ok {
202+ if route , found = cl.wildCardRoutes [routeSegment ][MethodAny ]; found {
203+ return route , true , nil
204+ }
148205 }
149- cl .routesRWMu .Unlock ()
150206
151207 // if not, look for wildcard routes
152- cl .wildCardRoutesRWMu .Lock ()
153- defer cl .wildCardRoutesRWMu .Unlock ()
154- for wildCardPattern , route := range cl .WildCardRoutes {
155- match , err := filepath .Match (wildCardPattern , routeSegment )
208+ for wildCardPattern , routePath := range cl .wildCardRoutes {
209+ pathMatch , err := filepath .Match (wildCardPattern , routeSegment )
156210 if err != nil {
157211 return nil , false , newDynamicError (ErrInvalidPath , err .Error ())
158212 }
159- if match {
160- return route , true , nil
213+ if pathMatch {
214+ // Found a path match, now check for the method
215+ if route , found = routePath [routeMethod ]; found {
216+ return route , true , nil
217+ }
218+ if route , found = routePath [MethodAny ]; found {
219+ return route , true , nil
220+ }
161221 }
162222 }
163223
@@ -166,44 +226,57 @@ func (cl *CageLevel) route(routeSegment string) (route *Route, found bool, err e
166226
167227// subCageLevel searches for a sub cage level based on the segment provided
168228// if createMode is true, it will create the cage level if it doesn't exist
169- func (cl * CageLevel ) subCageLevel (subCageSegment string , createMode bool ) (cageLevel * CageLevel , found bool , err error ) {
229+ func (cl * cageLevel ) subCageLevel (subCageSegment string , createMode bool ) (cageLevel * cageLevel , found bool , err error ) {
170230 // First check for an exact match
171- cl .subCagesRWMu .RLock ()
172- if cageLevel , exists := cl .SubCages [subCageSegment ]; exists {
173- defer cl .subCagesRWMu .RUnlock ()
231+ cl .rwMu .RLock ()
232+ if cageLevel , exists := cl .subCages [subCageSegment ]; exists {
233+ cl .rwMu .RUnlock ()
174234 return cageLevel , true , nil
175235 }
176- cl .subCagesRWMu .RUnlock ()
177236
178237 // if not, look for wildcard cages
179- cl .wildCardSubCagesRWMu .RLock ()
180- for wildCardPattern , cageLevel := range cl .WildCardSubCages {
238+ for wildCardPattern , cageLevel := range cl .wildCardSubCages {
181239 match , err := filepath .Match (wildCardPattern , subCageSegment )
182240 if err != nil {
183- cl .wildCardSubCagesRWMu .RUnlock ()
241+ cl .rwMu .RUnlock ()
184242 return nil , false , newDynamicError (ErrInvalidPath , err .Error ())
185243 }
186244 if match {
187- cl .wildCardSubCagesRWMu .RUnlock ()
245+ cl .rwMu .RUnlock ()
188246 return cageLevel , true , nil
189247 }
190248 }
191- cl .wildCardSubCagesRWMu .RUnlock ()
249+ cl .rwMu .RUnlock ()
192250
193251 // We didn't find a match, so we'll create a new cage level if we're in create mode
194252 if createMode {
195- newCage := newCageLevel ()
253+ newCage := newCageLevel (filepath .Join (cl .cagePath , subCageSegment ))
254+ cl .rwMu .Lock ()
255+ defer cl .rwMu .Unlock ()
196256 if strings .Contains (subCageSegment , "*" ) {
197- cl .wildCardSubCagesRWMu .Lock ()
198- defer cl .wildCardSubCagesRWMu .Unlock ()
199- cl .WildCardSubCages [subCageSegment ] = newCage
257+ cl .wildCardSubCages [subCageSegment ] = newCage
200258 } else {
201- cl .subCagesRWMu .Lock ()
202- defer cl .subCagesRWMu .Unlock ()
203- cl .SubCages [subCageSegment ] = newCage
259+ cl .subCages [subCageSegment ] = newCage
204260 }
205261 return newCage , true , nil
206262 }
207263
208264 return nil , false , nil
209265}
266+
267+ // newRoute creates a new route in the cage level
268+ func (cl * cageLevel ) newRoute (route * Route ) {
269+ cl .rwMu .Lock ()
270+ defer cl .rwMu .Unlock ()
271+ if strings .Contains (route .Segment (), "*" ) {
272+ if _ , found := cl .wildCardRoutes [route .Segment ()]; ! found {
273+ cl .wildCardRoutes [route .Segment ()] = make (map [string ]* Route )
274+ }
275+ cl .wildCardRoutes [route .Segment ()][route .Method ] = route
276+ } else {
277+ if _ , found := cl .routes [route .Segment ()]; ! found {
278+ cl .routes [route .Segment ()] = make (map [string ]* Route )
279+ }
280+ cl .routes [route .Segment ()][route .Method ] = route
281+ }
282+ }
0 commit comments