11import { FabrixApp } from '@fabrix/fabrix'
2+ import { FabrixController } from '@fabrix/fabrix/dist/common'
3+ import { FabrixPolicy } from '@fabrix/fabrix/dist/common'
24import { get , omit , isString } from 'lodash'
35import { Router } from 'call'
46import { IRoute } from './interfaces/IRoute'
57
8+
69export const Utils = {
710 methods : [
811 'GET' ,
@@ -23,10 +26,143 @@ export const Utils = {
2326 return isString ( strOrArray ) ? [ strOrArray ] : strOrArray
2427 } ,
2528
29+ /**
30+ * If a route has nested methods that have a seperate prefix, then it needs to be split into
31+ * multiple routes
32+ */
33+ splitRoute ( app : FabrixApp , path : string , rawRoute : IRoute ) : { path : string , route : any } [ ] {
34+ const methods = [ '*' , ...Utils . methods ]
35+ const routeMethods = new Set ( )
36+ const usedPrefixes = new Set ( )
37+ const orgRoute = Object . assign ( { } , rawRoute )
38+ const parentPrefix = get ( orgRoute , 'config.prefix' )
39+
40+ // The route must have a config to be set
41+ orgRoute . config = orgRoute . config || ( orgRoute . config = { } )
42+ orgRoute . config . pre = orgRoute . config . pre || ( orgRoute . config . pre = [ ] )
43+
44+ // Add a prefix if given
45+ if ( typeof parentPrefix !== 'undefined' ) {
46+ usedPrefixes . add ( parentPrefix )
47+ }
48+
49+ // Scenario 1: A wild card has a an object config different then the top level config = method prefix
50+ // Scenario 2: Prefix is set on the top level config and not on any methods = all have same prefix
51+ // Scenario 3: An Identical Prefix is set on the methods and not on the parent = all have same prefix
52+ // Scenario 4: A method has a different prefix then top level config = generate 2+ routes
53+ // Scenario 5: There is only one method and it's prefix is different then the top level = method prefix
54+ const cases = new Set ( )
55+
56+ methods . forEach ( method => {
57+ if ( orgRoute . hasOwnProperty ( method ) ) {
58+ const methodPrefix = get ( rawRoute [ method ] , 'config.prefix' )
59+ routeMethods . add ( { [ method ] : methodPrefix } )
60+
61+ // Add a prefix if given
62+ if ( typeof methodPrefix !== 'undefined' ) {
63+ usedPrefixes . add ( methodPrefix )
64+ }
65+
66+ // Scenario 1
67+ if ( method === '*' && methodPrefix ) {
68+ cases . add ( 1 )
69+ }
70+ // Scenario 4
71+ if ( typeof parentPrefix === 'undefined' && methodPrefix && usedPrefixes . size > 1 ) {
72+ cases . add ( 4 )
73+ }
74+ }
75+ } )
76+
77+ // Scenario 2
78+ if (
79+ typeof parentPrefix !== 'undefined'
80+ && usedPrefixes . size === 1
81+ ) {
82+ cases . add ( 2 )
83+ }
84+
85+ // Scenario 3
86+ if (
87+ typeof parentPrefix === 'undefined'
88+ && usedPrefixes . size === 1
89+ ) {
90+ cases . add ( 3 )
91+ }
92+
93+ // Scenario 5
94+ if (
95+ typeof parentPrefix !== 'undefined'
96+ && routeMethods . size === 1
97+ && usedPrefixes . size > 1
98+ ) {
99+ cases . add ( 5 )
100+ }
101+
102+ const scenarios = {
103+ '1' : cases . has ( 1 ) ,
104+ '2' : cases . has ( 2 ) ,
105+ '3' : cases . has ( 3 ) ,
106+ '4' : cases . has ( 4 ) ,
107+ '5' : cases . has ( 5 )
108+ }
109+
110+ const type = Object . keys ( scenarios ) . find ( ( key ) => scenarios [ key ] )
111+
112+ switch ( type ) {
113+ case '1' : {
114+ orgRoute . config . prefix = orgRoute [ '*' ] . config . prefix
115+ return Utils . buildRoute ( app , path , orgRoute )
116+ }
117+ case '2' : {
118+ return Utils . buildRoute ( app , path , orgRoute )
119+ }
120+ case '3' : {
121+ orgRoute . config . prefix = usedPrefixes . values ( ) . next ( ) . value
122+ return Utils . buildRoute ( app , path , orgRoute )
123+ }
124+ case '4' : {
125+ const routes = new Set ( )
126+ const newRoutes = new Map ( )
127+ routeMethods . forEach ( route => {
128+ const method : string = Object . keys ( route ) [ 0 ]
129+ const prefix : any = route [ method ]
130+ if ( prefix !== parentPrefix ) {
131+ const newRoute : IRoute = {
132+ [ method ] : orgRoute [ method ] ,
133+ config : {
134+ prefix : prefix
135+ }
136+ }
137+ if ( newRoutes . has ( prefix ) ) {
138+ newRoutes . set ( prefix , { ...newRoute , ...newRoutes . get ( prefix ) } )
139+ }
140+ else {
141+ newRoutes . set ( prefix , newRoute )
142+ }
143+ delete orgRoute [ method ]
144+ }
145+ } )
146+ newRoutes . forEach ( newRoute => {
147+ routes . add ( Utils . buildRoute ( app , path , newRoute ) )
148+ } )
149+ routes . add ( Utils . buildRoute ( app , path , orgRoute ) )
150+ return Array . from ( routes ) . reduce ( ( a , b ) => a . concat ( b ) , [ ] )
151+ }
152+ case '5' : {
153+ orgRoute . config . prefix = Array . from ( usedPrefixes ) . pop ( )
154+ return Utils . buildRoute ( app , path , orgRoute )
155+ }
156+ default : {
157+ return Utils . buildRoute ( app , path , orgRoute )
158+ }
159+ }
160+ } ,
161+
26162 /**
27163 * Build a complete route, with bound handler and attached preconditions
28164 */
29- buildRoute ( app : FabrixApp , path : string , rawRoute : IRoute ) {
165+ buildRoute ( app : FabrixApp , path : string , rawRoute : IRoute ) : { path : string , route : any } [ ] {
30166 const orgRoute = Object . assign ( { } , rawRoute )
31167 orgRoute . config = orgRoute . config || ( orgRoute . config = { } )
32168 orgRoute . config . pre = orgRoute . config . pre || ( orgRoute . config . pre = [ ] )
@@ -53,7 +189,7 @@ export const Utils = {
53189 if ( ! orgRouteHandlers . some ( v => Utils . methods . indexOf ( v ) >= 0 || ! ! orgRoute [ v ] ) ) {
54190 app . log . error ( 'spool-router: route ' , path , ' handler [' , orgRouteHandlers . join ( ', ' ) , ']' ,
55191 'does not correspond to any defined Controller handler' )
56- return { }
192+ return [ ]
57193 }
58194
59195 orgRouteHandlers . forEach ( method => {
@@ -69,15 +205,16 @@ export const Utils = {
69205
70206 const route = omit ( orgRoute , 'config' )
71207
72- return { path, route }
208+ return [ { path, route } ]
73209 } ,
74210
75211 /**
76212 * Expands the search for the prefix to the route or config.* level
77213 */
78- getRouteLevelPrefix ( app : FabrixApp , route ) {
214+ getRouteLevelPrefix ( app : FabrixApp , route ) : string | null {
79215 const configuredPrefix = app . config . get ( route . config . prefix )
80- const routePrefix = route . config . prefix
216+ const routePrefix = get ( route , 'config.prefix' )
217+
81218 if ( typeof configuredPrefix !== 'undefined' ) {
82219 if ( configuredPrefix ) {
83220 return ( configuredPrefix ) . replace ( / $ \/ / , '' )
@@ -110,7 +247,7 @@ export const Utils = {
110247 /**
111248 * Build the Path from the Route config
112249 */
113- getPathFromRoute ( app : FabrixApp , path , route ) {
250+ getPathFromRoute ( app : FabrixApp , path , route ) : string {
114251 if ( ! route || ! ( route instanceof Object ) ) {
115252 throw new RangeError ( 'Expected a route object' )
116253 }
@@ -121,14 +258,14 @@ export const Utils = {
121258 return `${ prefix } /${ path } `
122259 } ,
123260
124- getPolicyFromString ( app : FabrixApp , handler ) {
261+ getPolicyFromString ( app : FabrixApp , handler ) : FabrixPolicy {
125262 return get ( app . policies , handler )
126263 } ,
127264
128265 /**
129266 * Get a Controller's method's policies
130267 */
131- getControllerPolicy ( app : FabrixApp , handler , routeMethod , pre = [ ] ) {
268+ getControllerPolicy ( app : FabrixApp , handler , routeMethod , pre = [ ] ) : FabrixPolicy [ ] {
132269 const controller = Utils . getControllerFromHandler ( handler )
133270
134271 if ( app . config . get ( 'policies.*.*' ) ) {
@@ -176,11 +313,17 @@ export const Utils = {
176313 return handler
177314 } ,
178315
179- getControllerFromString ( app : FabrixApp , handler ) {
316+ /**
317+ *
318+ */
319+ getControllerFromString ( app : FabrixApp , handler ) : FabrixController {
180320 return get ( app . controllers , handler )
181321 } ,
182322
183- getControllerFromHandler ( handler ) {
323+ /**
324+ *
325+ */
326+ getControllerFromHandler ( handler ) : string {
184327 return isString ( handler ) ? handler . split ( '.' ) [ 0 ] : handler
185328 } ,
186329
@@ -237,9 +380,10 @@ export const Utils = {
237380 * Build a route collection
238381 */
239382 buildRoutes ( app : FabrixApp , routes , toReturn = { } ) {
240- Object . keys ( routes ) . forEach ( r => {
241- const { path, route } = Utils . buildRoute ( app , r , routes [ r ] )
242- toReturn [ path ] = route
383+ Object . keys ( routes ) . forEach ( p => {
384+ Utils . splitRoute ( app , p , routes [ p ] ) . forEach ( ( { path, route } ) => {
385+ toReturn [ path ] = route
386+ } )
243387 } )
244388 return Utils . sortRoutes ( toReturn , app . config . get ( 'router.sortOrder' ) )
245389 } ,
0 commit comments