8000 close #87 by adding new execution stack processing · jthomerson/lambda-api@ebbb62e · GitHub
[go: up one dir, main page]

Skip to content

Commit ebbb62e

Browse files
committed
close jeremydaly#87 by adding new execution stack processing
1 parent 87a718f commit ebbb62e

File tree

6 files changed

+328
-163
lines changed

6 files changed

+328
-163
lines changed

index.js

Lines changed: 159 additions & 86 deletions
73
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ class API {
5858
// Init callback
5959
this._cb
6060

61-
// Middleware stack
62-
this._middleware = []
63-
6461
// Error middleware stack
6562
this._errors = []
6663

@@ -73,27 +70,31 @@ class API {
7370
// Global error status (used for response parsing errors)
7471
this._errorStatus = 500
7572

76-
} // end constructor
+
// Methods
74+
this._methods = ['get','post','put','patch','delete','options','head','any']
7775

76+
// Convenience methods for METHOD
77+
this._methods.forEach(m => {
78+
this[m] = (...a) => this.METHOD(m.toUpperCase(),...a)
79+
})
7880

81+
} // end constructor
7982

80-
// Convenience methods (path, handler)
81-
get(p,h) { this.METHOD('GET',p,h) }
82-
post(p,h) { this.METHOD('POST',p,h) }
83-
put(p,h) { this.METHOD('PUT',p,h) }
84-
patch(p,h) { this.METHOD('PATCH',p,h) }
85-
delete(p,h) { this.METHOD('DELETE',p,h) }
86-
options(p,h) { this.METHOD('OPTIONS',p,h) }
87-
head(p,h) { this.METHOD('HEAD',p,h) }
88-
any(p,h) { this.METHOD('ANY',p,h) }
83+
// METHOD: Adds method, middleware, and handlers to routes
84+
METHOD(method,...args) {
8985

86+
// Extract path if provided, otherwise default to global wildcard
87+
let path = typeof args[0] === 'string' ? args.shift() : '/*'
9088

91-
// METHOD: Adds method and handler to routes
92-
METHOD(method, path, handler) {
89+
// Extract the execution stack
90+
let stack = args.map((fn,i) => {
91+
if (typeof fn === 'function' && (fn.length === 3 || (fn.length === 2 && i === args.length-1)))
92+
return fn
93+
throw new ConfigurationError('Route-based middleware must have 3 parameters')
94+
})
9395

94-
if (typeof handler !== 'function') {
95-
throw new ConfigurationError(`No route handler specified for ${method} method on ${path} route.`)
96-
}
96+
if (stack.length === 0)
97+
throw new ConfigurationError(`No handler or middleware specified for ${method} method on ${path} route.`)
9798

9899
// Ensure method is an array
99100
let methods = Array.isArray(method) ? method : method.split(',')
@@ -105,46 +106,100 @@ class API {
105106
let route = this._prefix.concat(parsedPath)
106107

107108
// For root path support
108-
if (route.length === 0) { route.push('')}
109+
if (route.length === 0) { route.push('') }
109110

110111
// Keep track of path variables
111112
let pathVars = {}
112113

114+
// Make a local copy of routes
115+
let routes = this._routes
116+
117+
// Create a local stack for inheritance
118+
let _stack = {}
119+
113120
// Loop through the paths
114121
for (let i=0; i<route.length; i++) {
115122

123+
let end = i === route.length-1
124+
116125
// If this is a variable
117126
if (/^:(.*)$/.test(route[i])) {
118127
// Assign it to the pathVars (trim off the : at the beginning)
119-
pathVars[i] = route[i].substr(1)
128+
pathVars[i] = [route[i].substr(1)]
120129
// Set the route to __VAR__
121130
route[i] = '__VAR__'
122131
} // end if variable
123132

133+
// Add methods to routess
124134
methods.forEach(_method => {
125135
if (typeof _method === 'string') {
136+
137+
if (routes['ROUTES']) {
138+
139+
// Wildcard routes
140+
if (routes['ROUTES']['*']) {
141+
142+
// Inherit middleware
143+
if (routes['ROUTES']['*']['MIDDLEWARE']) {
144+
_stack[method] = _stack[method] ?
145+
< 10000 span class=pl-s1>_stack[method].concat(routes['ROUTES']['*']['MIDDLEWARE'].stack)
146+
: routes['ROUTES']['*']['MIDDLEWARE'].stack
147+
}
148+
149+
// Inherit methods and ANY
150+
if (routes['ROUTES']['*']['METHODS'] && routes['ROUTES']['*']['METHODS']) {
151+
['ANY',method].forEach(m => {
152+
if (routes['ROUTES']['*']['METHODS'][m]) {
153+
_stack[method] = _stack[method] ?
154+
_stack[method].concat(routes['ROUTES']['*']['METHODS'][m].stack)
155+
: routes['ROUTES']['*']['METHODS'][m].stack
156+
}
157+
}) // end for
158+
}
159+
}
160+
161+
// Matching routes
162+
if (routes['ROUTES'][route[i]]) {
163+
164+
// Inherit middleware
165+
if (end && routes['ROUTES'][route[i]]['MIDDLEWARE']) {
166+
_stack[method] = _stack[method] ?
167+
_stack[method].concat(routes['ROUTES'][route[i]]['MIDDLEWARE'].stack)
168+
: routes['ROUTES'][route[i]]['MIDDLEWARE'].stack
169+
}
170+
171+
// Inherit ANY methods (DISABLED)
172+
// if (end && routes['ROUTES'][route[i]]['METHODS'] && routes['ROUTES'][route[i]]['METHODS']['ANY']) {
173+
// _stack[method] = _stack[method] ?
174+
// _stack[method].concat(routes['ROUTES'][route[i]]['METHODS']['ANY'].stack)
175+
// : routes['ROUTES'][route[i]]['METHODS']['ANY'].stack
176+
// }
177+
}
178+
}
179+
126180
// Add the route to the global _routes
127181
this.setRoute(
128182
this._routes,
129-
(i === route.length-1 ? {
130-
['__'+_method.trim().toUpperCase()]: {
131-
vars: pathVars,
132-
handler: handler,
133-
route: '/'+parsedPath.join('/'),
134-
path: '/'+this._prefix.concat(parsedPath).join('/') }
135-
} : {}),
183+
_method.trim().toUpperCase(),
184+
(end ? {
185+
vars: pathVars,
186+
stack,
187+
inherited: _stack[method] ? _stack[method] : [],
188+
route: '/'+parsedPath.join('/'),
189+
path: '/'+this._prefix.concat(parsedPath).join('/')
190+
} : null),
136191
route.slice(0,i+1)
137192
)
193+
138194
}
139195
}) // end methods loop
140196

197+
routes = routes['ROUTES'][route[i]]
141198

142199
} // end for loop
143200

144201
} // end main METHOD function
145202

146-
147-
148203
// RUN: This runs the routes
149204
async run(event,context,cb) {
150205

@@ -162,44 +217,24 @@ class API {
162217
// Parse the request
163218
await request.parseRequest()
164219

165-
// Loop through the middleware and await response
166-
for (const mw of this._middleware) {
167-
// Only run middleware if in processing state
220+
// Loop through the execution stack
221+
for (const fn of request._stack) {
222+
// Only run if in processing state
168223
if (response._state !== 'processing') break
169224

170-
// Init for matching routes
171-
let matched = false
172-
173-
// Test paths if they are supplied
174-
for (const path of mw[0]) {
175-
if (
176-
path === request.path || // If exact path match
177-
path === request.route || // If exact route match
178-
// If a wildcard match
179-
(path.substr(-1) === '*' && new RegExp('^' + path.slice(0, -1) + '.*$').test(request.route))
180-
) {
181-
matched = true
182-
break
183-
}
184-
}
185-
186-
if (mw[0].length > 0 && !matched) continue
187-
188-
// Promisify middleware
189225
await new Promise(async r => {
190-
let rtn = await mw[1](request,response,() => { r() })
191-
if (rtn) response.send(rtn)
192-
if (response._state === 'done') r() // if state is done, resolve promise
226+
try {
227+
let rtn = await fn(request,response,() => { r() })
228+
if (rtn) response.send(rtn)
229+
if (response._state === 'done') r() // if state is done, resolve promise
230+
} catch(e) {
231+
await this.catchErrors(e,response)
232+
r() // resolve the promise
233+
}
193234
})
194235

195236
} // end for
196237

197-
// Execute the primary handler if in processing state
198-
if (response._state === 'processing') {
199-
let rtn = await request._handler(request,response)
200-
if (rtn) response.send(rtn)
201-
}
202-
203238
} catch(e) {
204239
await this.catchErrors(e,response)
205240
}
@@ -214,8 +249,6 @@ class API {
214249
// Catch all async/sync errors
215250
async catchErrors(e,response,code,detail) {
216251

217-
// console.log('\n\n------------------------\n',e,'\n------------------------\n\n');
218-
219252
// Error messages should never be base64 encoded
220253
response._isBase64 = false
221254

@@ -297,24 +330,34 @@ class API {
297330

298331

299332
// Middleware handler
300-
use(path) {
333+
use(...args) {
301334

302335
// Extract routes
303-
let routes = typeof path === 'string' ? Array.of(path) : (Array.isArray(path) ? path : [])
336+
let routes = typeof args[0] === 'string' ? Array.of(args.shift()) : (Array.isArray(args[0]) ? args.shift() : ['/*'])
337+
338+
// Init middleware stack
339+
let middleware = []
304340

305341
// Add func args as middleware
306-
for (let arg in arguments) {
307-
if (typeof arguments[arg] === 'function') {
308-
if (arguments[arg].length === 3) {
309-
this._middleware.push([routes,arguments[arg]])
310-
} else if (arguments[arg].length === 4) {
311-
this._errors.push(arguments[arg])
342+
for (let arg in args) {
343+
if (typeof args[arg] === 'function') {
344+
if (args[arg].length === 3) {
345+
middleware.push(args[arg])
346+
} else if (args[arg].length === 4) {
347+
this._errors.push(args[arg])
312348
} else {
313349
throw new ConfigurationError('Middleware must have 3 or 4 parameters')
314350
}
315351
}
316352
}
317353

354+
// Add middleware to path
355+
if (middleware.length > 0) {
356+
routes.forEach(route => {
357+
this.METHOD('__MW__',route,...middleware)
358+
})
359+
}
360+
318361
} // end use
319362

320363

@@ -333,28 +376,58 @@ class API {
333376
return path.trim().replace(/^\/(.*?)(\/)*$/,'$1').split('/').filter(x => x.trim() !== '')
334377
}
335378

336-
// Recursive function to create routes object
337-
setRoute(obj, value, path) {
338-
if (typeof path === 'string') {
339-
path = path.split('.')
340-
}
341-
342-
if (path.length > 1){
379+
// Recursive function to create/merge routes object
380+
setRoute(obj, method, value, path) {
381+
if (path.length > 1) {
343382
let p = path.shift()
344-
if (obj[p] === null) {
345-
obj[p] = {}
346-
}
347-
this.setRoute(obj[p], value, path)
383+
if (p === '*') { throw new ConfigurationError('Wildcards can only be at the end of a route definition.') }
384+
this.setRoute(obj['ROUTES'][p], method, value, path)
348385
} else {
349-
if (obj[path[0]] === null) {
350-
obj[path[0]] = value
351-
} else {
352-
obj[path[0]] = Object.assign(value,obj[path[0]])
386+
// Create routes and add path if they don't exist
387+
if (!obj['ROUTES']) obj['ROUTES'] = {}
388+
if (!obj['ROUTES'][path[0]]) obj['ROUTES'][path[0]] = {}
389+
390+
// If a value exists in this iteration
391+
if (value !== null) {
392+
393+
// TEMP: debug
394+
// value._STACK = value.stack.map(x => x.name)
395+
// value._STACK2 = value.inherited.map(x => x.name)
396+
397+
// If mounting middleware
398+
if (method === '__MW__') {
399+
// Merge stacks if middleware exists
400+
if (obj['ROUTES'][path[0]]['MIDDLEWARE']) {
401+
value.stack = obj['ROUTES'][path[0]]['MIDDLEWARE'].stack.concat(value.stack)
402+
value.vars = UTILS.mergeObjects(obj['ROUTES'][path[0]]['MIDDLEWARE'].vars,value.vars)
403+
}
404+
405+
// Add/Update the middleware
406+
obj['ROUTES'][path[0]]['MIDDLEWARE'] = value
407+
408+
// Else if mounting a regular route
409+
} else {
410+
411+
// Create the methods section if it doesn't exist
412+
if (!obj['ROUTES'][path[0]]['METHODS']) obj['ROUTES'][path[0]]['METHODS'] = {}
413+
414+
// Merge stacks if method exists
415+
if (obj['ROUTES'][path[0]]['METHODS'][method]) {
416+
value.stack = obj['ROUTES'][path[0]]['METHODS'][method].stack.concat(value.stack)
417+
value.vars = UTILS.mergeObjects(obj['ROUTES'][path[0]]['METHODS'][method].vars,value.vars)
418+
}
419+
420+
// Add/Update the method
421+
obj['ROUTES'][path[0]]['METHODS'] = Object.assign(
422+
{},obj['ROUTES'][path[0]]['METHODS'],{ [method]: value }
423+
)
424+
425+
}
353426
}
427+
354428
}
355429
} // end setRoute
356430

357-
358431
// Load app packages
359432
app(packages) {
360433

0 commit comments

Comments
 (0)
0