@@ -6,65 +6,248 @@ const _ = require('lodash');
66module . exports = {
77
88 compileResources ( ) {
9- const resourcePaths = this . getResourcePaths ( ) ;
9+ this . apiGatewayResources = this . getResourcePaths ( ) ;
1010
11- this . apiGatewayResourceNames = { } ;
12- this . apiGatewayResourceLogicalIds = { } ;
11+ // ['users', 'users/create', 'users/create/something']
12+ _ . keys ( this . apiGatewayResources ) . forEach ( ( path ) => {
13+ const resource = this . apiGatewayResources [ path ] ;
14+ if ( resource . resourceId ) {
15+ return ;
16+ }
1317
14- resourcePaths . forEach ( path => {
15- const pathArray = path . split ( '/' ) ;
16- const resourceName = this . provider . naming . normalizePath ( path ) ;
17- const resourceLogicalId = this . provider . naming . getResourceLogicalId ( path ) ;
18- const pathPart = pathArray . pop ( ) ;
19- const parentPath = pathArray . join ( '/' ) ;
20- const parentRef = this . getResourceId ( parentPath ) ;
18+ resource . resourceLogicalId = this . provider . naming . getResourceLogicalId ( path ) ;
19+ resource . resourceId = { Ref : resource . resourceLogicalId } ;
2120
22- this . apiGatewayResourceNames [ path ] = resourceName ;
23- this . apiGatewayResourceLogicalIds [ path ] = resourceLogicalId ;
21+ const parentRef = resource . parent
22+ ? resource . parent . resourceId : this . getResourceId ( ) ;
2423
2524 _ . merge ( this . serverless . service . provider . compiledCloudFormationTemplate . Resources , {
26- [ resourceLogicalId ] : {
25+ [ resource . resourceLogicalId ] : {
2726 Type : 'AWS::ApiGateway::Resource' ,
2827 Properties : {
2928 ParentId : parentRef ,
30- PathPart : pathPart ,
31- RestApiId : { Ref : this . apiGatewayRestApiLogicalId } ,
29+ PathPart : resource . pathPart ,
30+ RestApiId : this . provider . getApiGatewayRestApiId ( ) ,
3231 } ,
3332 } ,
3433 } ) ;
3534 } ) ;
35+
3636 return BbPromise . resolve ( ) ;
3737 } ,
3838
39+ combineResourceTrees ( trees ) {
40+ const self = this ;
41+
42+ function getNodePaths ( result , node ) {
43+ const r = result ;
44+ r [ node . path ] = node ;
45+ if ( ! node . name ) {
46+ r [ node . path ] . name = self . provider . naming . normalizePath ( node . path ) ;
47+ }
48+
49+ node . children . forEach ( ( child ) => getNodePaths ( result , child ) ) ;
50+ }
51+
52+ return _ . reduce ( trees , ( result , tree ) => {
53+ getNodePaths ( result , tree ) ;
54+ return result ;
55+ } , { } ) ;
56+ } ,
57+
3958 getResourcePaths ( ) {
40- const paths = _ . reduce ( this . pluginhttpValidated . events , ( resourcePaths , event ) => {
41- let path = event . http . path ;
59+ const trees = [ ] ;
60+ const predefinedResourceNodes = [ ] ;
61+ const methodNodes = [ ] ;
62+ const predefinedResources = this . provider . getApiGatewayPredefinedResources ( ) ;
63+
64+
65+ function cutBranch ( node ) {
66+ if ( ! node . parent ) {
67+ return ;
68+ }
69+
70+ const n = node ;
71+ if ( node . parent . children . length <= 1 ) {
72+ n . parent . children = [ ] ;
73+ } else {
74+ n . parent . children = node . parent . children . filter ( ( c ) => c . path !== n . path ) ;
75+ n . parent . isCut = true ;
76+ }
77+ n . parent = null ;
78+ }
79+
80+ // organize all resource paths into N-ary tree
81+ function applyResource ( resource , isMethod ) {
82+ let root ;
83+ let parent ;
84+ let currentPath ;
85+ const path = resource . path . replace ( / ^ \/ / , '' ) . replace ( / \/ $ / , '' ) ;
86+ const pathParts = path . split ( '/' ) ;
87+
88+ function applyNodeResource ( node , parts , index ) {
89+ const n = node ;
90+ if ( index === parts . length - 1 ) {
91+ n . name = resource . name ;
92+ if ( resource . resourceId ) {
93+ n . resourceId = resource . resourceId ;
94+ if ( _ . every ( predefinedResourceNodes , ( iter ) => iter . path !== n . path ) ) {
95+ predefinedResourceNodes . push ( node ) ;
96+ }
97+ }
98+ if ( isMethod && ! node . hasMethod ) {
99+ n . hasMethod = true ;
100+ if ( _ . every ( methodNodes , ( iter ) => iter . path !== n . path ) ) {
101+ methodNodes . push ( node ) ;
102+ }
103+ }
104+ }
105+
106+ parent = node ;
107+ }
108+
109+ pathParts . forEach ( ( pathPart , index ) => {
110+ currentPath = currentPath ? `${ currentPath } /${ pathPart } ` : pathPart ;
111+ root = root || _ . find ( trees , ( node ) => node . path === currentPath ) ;
112+ parent = parent || root ;
113+
114+ let node ;
115+ if ( parent ) {
116+ if ( parent . path === currentPath ) {
117+ applyNodeResource ( parent , pathParts , index ) ;
118+ return ;
119+ } else if ( parent . children . length > 0 ) {
120+ node = _ . find ( parent . children , ( n ) => n . path === currentPath ) ;
121+ if ( node ) {
122+ applyNodeResource ( node , pathParts , index ) ;
123+ return ;
124+ }
125+ }
126+ }
127+
128+ node = {
129+ path : currentPath ,
130+ pathPart,
131+ parent,
132+
133+ level : index ,
134+ children : [ ] ,
135+ } ;
136+
137+ if ( parent ) {
138+ parent . children . push ( node ) ;
139+ }
140+
141+ if ( ! root ) {
142+ root = node ;
143+ trees . push ( root ) ;
144+ }
145+
146+ applyNodeResource ( node , pathParts , index ) ;
147+ } ) ;
148+ }
149+
150+ predefinedResources . forEach ( applyResource ) ;
151+ this . pluginhttpValidated . events . forEach ( ( event ) => {
152+ if ( event . http . path ) {
153+ applyResource ( event . http , true ) ;
154+ }
155+ } ) ;
156+
157+ // if predefinedResources array is empty, return all paths
158+ if ( predefinedResourceNodes . length === 0 ) {
159+ return this . combineResourceTrees ( trees ) ;
160+ }
161+
162+ // if all methods have resource ID already, no need to validate resource trees
163+ if ( _ . every ( this . pluginhttpValidated . events , ( event ) =>
164+ _ . some ( predefinedResourceNodes , ( node ) =>
165+ node . path === event . http . path ) ) ) {
166+ return _ . reduce ( predefinedResources , ( resourceMap , resource ) => {
167+ const r = resourceMap ;
168+ r [ resource . path ] = resource ;
169+
170+ if ( ! resource . name ) {
171+ r [ resource . path ] . name = this . provider . naming . normalizePath ( resource . path ) ;
172+ }
173+ return r ;
174+ } , { } ) ;
175+ }
176+
177+ // cut resource branches from trees
178+ const sortedResourceNodes = _ . sortBy ( predefinedResourceNodes ,
179+ node => node . level ) ;
180+ const validatedTrees = [ ] ;
181+
182+ for ( let i = sortedResourceNodes . length - 1 ; i >= 0 ; i -- ) {
183+ const node = sortedResourceNodes [ i ] ;
184+ let parent = node ;
42185
43- while ( path !== '' ) {
44- if ( resourcePaths . indexOf ( path ) === - 1 ) {
45- resourcePaths . push ( path ) ;
186+ while ( parent && parent . parent ) {
187+ if ( parent . parent . hasMethod && ! parent . parent . resourceId ) {
188+ throw new Error ( `Resource ID for path ${ parent . parent . path } is required` ) ;
46189 }
47190
48- const splittedPath = path . split ( '/' ) ;
49- splittedPath . pop ( ) ;
50- path = splittedPath . join ( '/' ) ;
191+ if ( parent . parent . resourceId || parent . parent . children . length > 1 ) {
192+ cutBranch ( parent ) ;
193+ break ;
194+ }
195+
196+ parent = parent . parent ;
51197 }
52- return resourcePaths ;
53- } , [ ] ) ;
54- return _ . sortBy ( paths , path => path . split ( '/' ) . length ) ;
198+ }
199+
200+ // get branches that begin from root resource
201+ methodNodes . forEach ( ( node ) => {
202+ let iter = node ;
203+ while ( iter ) {
204+ if ( iter . resourceId ) {
205+ cutBranch ( iter ) ;
206+ if ( _ . every ( validatedTrees , ( t ) => t . path !== node . path ) ) {
207+ validatedTrees . push ( iter ) ;
208+ }
209+
210+ break ;
211+ }
212+
213+ if ( iter . isCut || ( ! iter . parent && iter . level > 0 ) ) {
214+ throw new Error ( `Resource ID for path ${ iter . path } is required` ) ;
215+ }
216+
217+ if ( ! iter . parent ) {
218+ validatedTrees . push ( iter ) ;
219+ break ;
220+ }
221+
222+ iter = iter . parent ;
223+ }
224+ } ) ;
225+
226+ return this . combineResourceTrees ( validatedTrees ) ;
55227 } ,
56228
57229 getResourceId ( path ) {
58- if ( path === '' ) {
59- return { 'Fn::GetAtt' : [ this . apiGatewayRestApiLogicalId , 'RootResourceId' ] } ;
230+ if ( ! path ) {
231+ return this . provider . getApiGatewayRestApiRootResourceId ( ) ;
232+ }
233+
234+ if ( ! this . apiGatewayResources || ! this . apiGatewayResources [ path ] ) {
235+ throw new Error ( `Can not find API Gateway resource from path ${ path } ` ) ;
60236 }
61- return { Ref : this . apiGatewayResourceLogicalIds [ path ] } ;
237+
238+ if ( ! this . apiGatewayResources [ path ] . resourceId
239+ && this . apiGatewayResources [ path ] . resourceLogicalId ) {
240+ this . apiGatewayResources [ path ] . resourceId =
241+ { Ref : this . apiGatewayResources [ path ] . resourceLogicalId } ;
242+ }
243+ return this . apiGatewayResources [ path ] . resourceId ;
62244 } ,
63245
64246 getResourceName ( path ) {
65- if ( path === '' ) {
247+ if ( path === '' || ! this . apiGatewayResources ) {
66248 return '' ;
67249 }
68- return this . apiGatewayResourceNames [ path ] ;
250+
251+ return this . apiGatewayResources [ path ] . name ;
69252 } ,
70253} ;
0 commit comments