• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nuxy / lambda-lambda-lambda / 137

pending completion
137

push

travis-ci-com

nuxy
Added moving note / Removed stale docs

149 of 158 branches covered (94.3%)

Branch coverage included in aggregate %.

216 of 216 relevant lines covered (100.0%)

303.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.21
/src/Router.js
1
/**
2
 *  lambda-lambda-lambda
3
 *  AWS Lambda@Edge serverless application router.
4
 *
5
 *  Copyright 2021-2023, Marc S. Brooks (https://mbrooks.info)
6
 *  Licensed under the MIT license:
7
 *  http://www.opensource.org/licenses/mit-license.php
8
 */
9

10
'use strict';
11

12
const fs   = require('fs');
83✔
13
const path = require('path');
83✔
14

15
// Local modules.
16
const Request  = require('./router/Request');
83✔
17
const Response = require('./router/Response');
83✔
18
const Route    = require('./router/Route');
83✔
19
const Stack    = require('./router/Stack');
83✔
20

21
const {
22
  isPromise,
23
  isValidFunc,
24
  isValidPath,
25
  isValidRoute,
26
  moduleParent,
27
  setFuncName
28
} = require('./router/Common');
83✔
29

30
/**
31
 * Provides HTTP request/response handling.
32
 */
33
class Router {
34

35
  /**
36
   * @param {CloudFrontRequest} request
37
   *   CloudFront request object.
38
   *
39
   * @param {CloudFrontResponse|undefined} response
40
   *   CloudFront response object (optional).
41
   *
42
   * @example
43
   * exports.handler = (event, context, callback) => {
44
   *   const {request, response} = event.Records[0].cf;
45
   *
46
   *   const router = new Router(request, response);
47
   *
48
   *   callback(null, router.response());
49
   * };
50
   *
51
   *   ..
52
   *
53
   * exports.handler = async (event) => {
54
   *   const {request, response} = event.Records[0].cf;
55
   *
56
   *   const router = new Router(request, response);
57
   *
58
   *   return await router.response();
59
   * };
60
   */
61
  constructor(request, response) {
62
    this.req   = new Request (request);
99✔
63
    this.res   = new Response(response);
99✔
64
    this.stack = new Stack();
99✔
65

66
    this.prefix = '';
99✔
67
  }
68

69
  /**
70
   * Return CloudFront response object.
71
   *
72
   * @return {CloudFrontResponse|Promise<CloudFrontResponse>}
73
   */
74
  response() {
75
    loadRoutes(this);
82✔
76

77
    const result = this.stack.exec(this.req, this.res);
82✔
78

79
    /* istanbul ignore if (tested in: test/e2e/async) */
80
    if (isPromise(result)) {
82✔
81
      return result.then(() => this.res.data());
82
    }
83

84
    return this.res.data();
67✔
85
  }
86

87
  /**
88
   * Handle the Route/Middleware request (add to stack).
89
   *
90
   * @param {String} path
91
   *   Request URI.
92
   *
93
   * @param {Function} func
94
   *   Route/Middleware function.
95
   */
96
  handle(path, func) {
97
    let uri = `${this.prefix}${path}`;
264✔
98

99
    if (isValidRoute(this.req.uri(), uri, func)) {
264✔
100
      if (!func.name) {
109✔
101
        setFuncName(func, 'route::undefined');
3✔
102
      }
103

104
      this.stack.add(func);
109✔
105
    }
106
  }
107

108
  /**
109
   * Load the Route (e.g. Middleware) handler.
110
   *
111
   * @param {Function|String} arg
112
   *   Route/Middleware or Request URI.
113
   *
114
   * @param {Function} func
115
   *   Route/Middleware function (optional).
116
   *
117
   * @example
118
   * router.use(function(req, res, next) {
119
   *   if (req.method() === 'POST') {
120
   *     res.status(405).send();
121
   *   } else {
122
   *     next();
123
   *   }
124
   * });
125
   *
126
   *   ..
127
   *
128
   * router.use('/api/test', function(req, res) {
129
   *   res.setHeader('Content-Type', 'text/html');
130
   *   res.status(200).send('Hello World');
131
   * });
132
   */
133
  use(arg, func) {
134

135
    // Route middleware handler.
136
    if (isValidPath(arg) && isValidFunc(func)) {
251✔
137
      setFuncName(func, `middleware:${arg}`);
84✔
138

139
      this.handle(arg, func);
84✔
140
    } else
141

142
    // General middleware.
143
    if (isValidFunc(arg)) {
167✔
144
      if (arg.length === 3 && !arg.name) {
166✔
145
        setFuncName(arg, 'middleware');
83✔
146
      }
147

148
      this.stack.add(arg);
166✔
149
    }
150
  }
151

152
  /**
153
   * Set URI path prefix.
154
   *
155
   * @param {String} value
156
   *   Request URI.
157
   *
158
   * @example
159
   * router.setPrefix('/api');
160
   */
161
  setPrefix(value) {
162
    if (isValidPath(value) && value !== '/') {
84✔
163
      this.prefix = value;
83✔
164
    }
165
  }
166

167
  /**
168
   * Set router fallback (default route).
169
   *
170
   * @param {Function} route
171
   *   Route function.
172
   *
173
   * @example
174
   * router.default(function(req, res) {
175
   *   res.status(404).send();
176
   * });
177
   */
178
  default(route) {
179
    if (isValidFunc(route)) {
84✔
180
      const func = (req, res, next) => {
83✔
181
        route(req, res, next);
24✔
182
      };
183

184
      setFuncName(func, 'fallback');
83✔
185

186
      this.use(func);
83✔
187
    }
188
  }
189

190
  /**
191
   * Handle HTTP GET requests.
192
   *
193
   * @param {String} path
194
   *   Request URI.
195
   *
196
   * @param {Function} route
197
   *   Route function.
198
   *
199
   * @example
200
   * router.get('/api/test', function(req, res) {
201
   *   res.setHeader('Content-Type', 'text/html');
202
   *   res.status(200).send('Hello World');
203
   * });
204
   */
205
  get(path, route) {
206
    if (this.req.method() === 'GET' && isValidFunc(route)) {
242✔
207
      this.handle(path, route);
76✔
208
    }
209
  }
210

211
  /**
212
   * Handle HTTP POST requests.
213
   *
214
   * @param {String} path
215
   *   Request URI.
216
   *
217
   * @param {Function} route
218
   *   Route function.
219
   *
220
   * @example
221
   * router.post('/api/test', function(req, res) {
222
   *   res.status(201).send();
223
   * });
224
   */
225
  post(path, route) {
226
    if (this.req.method() === 'POST' && isValidFunc(route)) {
160✔
227
      this.handle(path, route);
26✔
228
    }
229
  }
230

231
  /**
232
   * Handle HTTP PUT requests.
233
   *
234
   * @param {String} path
235
   *   Request URI.
236
   *
237
   * @param {Function} route
238
   *   Route function.
239
   *
240
   * @example
241
   * router.put('/api/test', function(req, res) {
242
   *   res.status(201).send();
243
   * });
244
   */
245
  put(path, route) {
246
    if (this.req.method() === 'PUT' && isValidFunc(route)) {
160✔
247
      this.handle(path, route);
26✔
248
    }
249
  }
250

251
  /**
252
   * Handle HTTP PATCH requests.
253
   *
254
   * @param {String} path
255
   *   Request URI.
256
   *
257
   * @param {Function} route
258
   *   Route function.
259
   *
260
   * @example
261
   * router.patch('/api/test', function(req, res) {
262
   *   res.status(204).send();
263
   * });
264
   */
265
  patch(path, route) {
266
    if (this.req.method() === 'PATCH' && isValidFunc(route)) {
160✔
267
      this.handle(path, route);
26✔
268
    }
269
  }
270

271
  /**
272
   * Handle HTTP DELETE requests.
273
   *
274
   * @param {String} path
275
   *   Request URI.
276
   *
277
   * @param {Function} route
278
   *   Route function.
279
   *
280
   * @example
281
   * router.delete('/api/test', function(req, res) {
282
   *   res.status(200).send();
283
   * });
284
   */
285
  delete(path, route) {
286
    if (this.req.method() === 'DELETE' && isValidFunc(route)) {
160✔
287
      this.handle(path, route);
24✔
288
    }
289
  }
290
};
291

292
/**
293
 * Load routes from a pre-configured directory.
294
 *
295
 * @param {Router} router
296
 *   Router instance.
297
 */
298
function loadRoutes(router) {
299
  const routeDir = moduleParent() + '/routes';
82✔
300

301
  if (fs.existsSync(routeDir)) {
82!
302
    const files = getRoutes(routeDir);
82✔
303

304
    files.forEach(file => {
82✔
305
      file = path.relative(routeDir, file);
328✔
306
      const {dir, name} = path.parse(file);
328✔
307

308
      const filePath = [dir, name].join('/');
328✔
309
      const route = require(`${routeDir}/${filePath}`);
328✔
310

311
      route.path = (
328✔
312
        filePath[0] === '/' ? filePath : `/${filePath}`
328✔
313
      ).toLowerCase();
314

315
      Route(router, route);
328✔
316
    });
317
  }
318
}
319

320
/**
321
 * Return list of route files for a given directory.
322
 *
323
 * @param {String} dir
324
 *   Files directory.
325
 *
326
 * @param {Array} files
327
 *   List of files (optional).
328
 *
329
 * @return {Array<String>}
330
 */
331
function getRoutes(dir, files = []) {
82✔
332
  fs.readdirSync(dir).forEach(function(file) {
328✔
333
    const filePath = path.join(dir, file);
574✔
334

335
    if (fs.lstatSync(filePath).isDirectory()) {
574✔
336

337
      // Perform recursive traversal.
338
      getRoutes(filePath, files);
246✔
339
    } else if (path.extname(filePath) === '.js') {
328!
340
      files.push(filePath);
328✔
341
    }
342
  });
343

344
  return files;
328✔
345
}
346

347
module.exports = Router;
83✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc