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

lambda-lambda-lambda / router / 8086199887

28 Feb 2024 07:39PM UTC coverage: 98.228%. Remained the same
8086199887

push

github

nuxy
Updated package version (0.8.1)

155 of 162 branches covered (95.68%)

Branch coverage included in aggregate %.

233 of 233 relevant lines covered (100.0%)

871.51 hits per line

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

98.26
/src/Router.js
1
/**
2
 *  lambda-lambda-lambda/router
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');
498✔
13
const path = require('path');
498✔
14

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

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

30
// Global variables.
31
global.APP_ROOT = process.env.LAMBDA_TASK_ROOT || `${process.cwd()}/src`;
498✔
32

33
/**
34
 * Provides HTTP request/response handling.
35
 */
36
class Router {
37

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

69
    this.prefix = '';
594✔
70
  }
71

72
  /**
73
   * Return CloudFront response object.
74
   *
75
   * @return {CloudFrontResponse|Promise<CloudFrontResponse>}
76
   */
77
  response() {
78
    loadRoutes(this);
492✔
79

80
    const result = this.stack.exec(this.req, this.res);
492✔
81

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

87
    return this.res.data();
342✔
88
  }
89

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

102
    if (isValidRoute(this.req.uri(), uri, func)) {
1,584✔
103
      if (!func.name) {
654✔
104
        setFuncName(func, 'route::undefined');
18✔
105
      }
106

107
      this.stack.add(func);
654✔
108
    }
109
  }
110

111
  /**
112
   * Load the Route (e.g. Middleware) handler.
113
   *
114
   * @param {Function|String} arg
115
   *   Route/Middleware or Request URI.
116
   *
117
   * @param {Function} func
118
   *   Route/Middleware function (optional).
119
   *
120
   * @example
121
   * // Include function as middleware.
122
   * const Middleware = require('./path/to/middleware');
123
   *
124
   * router.use(Middleware);
125
   *
126
   *   ..
127
   *
128
   *
129
   * // Run function for every request.
130
   * router.use(function(req, res, next) {
131
   *   if (req.method() === 'POST') {
132
   *     res.status(405).send();
133
   *   } else {
134
   *     next();
135
   *   }
136
   * });
137
   *
138
   *   ..
139
   *
140
   * // Run function on URI path only.
141
   * router.use('/api/test', function(req, res) {
142
   *   res.setHeader('Content-Type', 'text/html');
143
   *   res.status(200).send('Hello World');
144
   * });
145
   */
146
  use(arg, func) {
147

148
    // Route middleware handler.
149
    if (isValidPath(arg) && isValidFunc(func)) {
1,506✔
150
      setFuncName(func, `middleware:${arg}`);
504✔
151

152
      this.handle(arg, func);
504✔
153
    } else
154

155
    // General middleware.
156
    if (isValidFunc(arg)) {
1,002✔
157
      if (arg.length === 3 && !arg.name) {
996✔
158
        setFuncName(arg, 'middleware');
498✔
159
      }
160

161
      this.stack.add(arg);
996✔
162
    }
163
  }
164

165
  /**
166
   * Set URI path prefix.
167
   *
168
   * @param {String} value
169
   *   Request URI.
170
   *
171
   * @example
172
   * router.setPrefix('/api');
173
   */
174
  setPrefix(value) {
175
    if (isValidPath(value) && value !== '/') {
504✔
176
      this.prefix = value;
498✔
177
    }
178
  }
179

180
  /**
181
   * Set router fallback (default route).
182
   *
183
   * @param {Function} route
184
   *   Route function.
185
   *
186
   * @example
187
   * router.default(function(req, res) {
188
   *   res.status(404).send();
189
   * });
190
   */
191
  default(route) {
192
    if (isValidFunc(route)) {
504✔
193
      const func = (req, res, next) => {
498✔
194
        route(req, res, next);
204✔
195
      };
196

197
      setFuncName(func, 'fallback');
498✔
198

199
      this.use(func);
498✔
200
    }
201
  }
202

203
  /**
204
   * Handle HTTP GET requests.
205
   *
206
   * @param {String} path
207
   *   Request URI.
208
   *
209
   * @param {Function} route
210
   *   Route function.
211
   *
212
   * @example
213
   * router.get('/api/test', function(req, res) {
214
   *   res.setHeader('Content-Type', 'text/html');
215
   *   res.status(200).send('Hello World');
216
   * });
217
   */
218
  get(path, route) {
219
    if (this.req.method() === 'GET' && isValidFunc(route)) {
1,452✔
220
      this.handle(path, route);
456✔
221
    }
222
  }
223

224
  /**
225
   * Handle HTTP POST requests.
226
   *
227
   * @param {String} path
228
   *   Request URI.
229
   *
230
   * @param {Function} route
231
   *   Route function.
232
   *
233
   * @example
234
   * router.post('/api/test', function(req, res) {
235
   *   res.status(201).send();
236
   * });
237
   */
238
  post(path, route) {
239
    if (this.req.method() === 'POST' && isValidFunc(route)) {
960✔
240
      this.handle(path, route);
156✔
241
    }
242
  }
243

244
  /**
245
   * Handle HTTP PUT requests.
246
   *
247
   * @param {String} path
248
   *   Request URI.
249
   *
250
   * @param {Function} route
251
   *   Route function.
252
   *
253
   * @example
254
   * router.put('/api/test', function(req, res) {
255
   *   res.status(201).send();
256
   * });
257
   */
258
  put(path, route) {
259
    if (this.req.method() === 'PUT' && isValidFunc(route)) {
960✔
260
      this.handle(path, route);
156✔
261
    }
262
  }
263

264
  /**
265
   * Handle HTTP PATCH requests.
266
   *
267
   * @param {String} path
268
   *   Request URI.
269
   *
270
   * @param {Function} route
271
   *   Route function.
272
   *
273
   * @example
274
   * router.patch('/api/test', function(req, res) {
275
   *   res.status(204).send();
276
   * });
277
   */
278
  patch(path, route) {
279
    if (this.req.method() === 'PATCH' && isValidFunc(route)) {
960✔
280
      this.handle(path, route);
156✔
281
    }
282
  }
283

284
  /**
285
   * Handle HTTP DELETE requests.
286
   *
287
   * @param {String} path
288
   *   Request URI.
289
   *
290
   * @param {Function} route
291
   *   Route function.
292
   *
293
   * @example
294
   * router.delete('/api/test', function(req, res) {
295
   *   res.status(200).send();
296
   * });
297
   */
298
  delete(path, route) {
299
    if (this.req.method() === 'DELETE' && isValidFunc(route)) {
960✔
300
      this.handle(path, route);
144✔
301
    }
302
  }
303
};
304

305
/**
306
 * Load routes from a pre-configured directory.
307
 *
308
 * @param {Router} router
309
 *   Router instance.
310
 */
311
function loadRoutes(router) {
312
  const routeDir = moduleParent() + '/routes';
492✔
313

314
  if (fs.existsSync(routeDir)) {
492!
315
    const files = getRoutes(routeDir);
492✔
316

317
    files.forEach(file => {
492✔
318
      file = path.relative(routeDir, file);
1,968✔
319
      const {dir, name} = path.parse(file);
1,968✔
320

321
      const filePath = [dir, name].join('/');
1,968✔
322
      const route = require(`${routeDir}/${filePath}`);
1,968✔
323

324
      route.path = (
1,968✔
325
        filePath[0] === '/' ? filePath : `/${filePath}`
1,968✔
326
      ).toLowerCase();
327

328
      Route(router, route);
1,968✔
329
    });
330
  }
331
}
332

333
/**
334
 * Return list of route files for a given directory.
335
 *
336
 * @param {String} dir
337
 *   Files directory.
338
 *
339
 * @param {Array} files
340
 *   List of files (optional).
341
 *
342
 * @return {Array<String>}
343
 */
344
function getRoutes(dir, files = []) {
492✔
345
  fs.readdirSync(dir).forEach(function(file) {
1,968✔
346
    const filePath = path.join(dir, file); // nosemgrep
3,444✔
347

348
    if (fs.lstatSync(filePath).isDirectory()) {
3,444✔
349

350
      // Perform recursive traversal.
351
      getRoutes(filePath, files);
1,476✔
352
    } else if (path.extname(filePath) === '.js') {
1,968!
353
      files.push(filePath);
1,968✔
354
    }
355
  });
356

357
  return files;
1,968✔
358
}
359

360
module.exports = Router;
498✔
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