@flink-app/flink 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,6 +107,26 @@ export interface FlinkOptions {
107
107
  */
108
108
  enabled?: boolean;
109
109
  };
110
+ /**
111
+ * If true, the HTTP server will be disabled.
112
+ * Only useful when starting a Flink app for testing purposes.
113
+ */
114
+ disableHttpServer?: boolean;
115
+ /**
116
+ * Configuration for access logs.
117
+ */
118
+ accessLog?: {
119
+ /**
120
+ * Enables access logs for all requests.
121
+ * Defaults to true.
122
+ */
123
+ enabled?: boolean;
124
+ /**
125
+ * Optional custom format.
126
+ * Uses `morgan` format and defaults to `dev`.
127
+ */
128
+ format?: string;
129
+ };
110
130
  }
111
131
  export interface HandlerConfig {
112
132
  schema?: {
@@ -145,12 +165,14 @@ export declare class FlinkApp<C extends FlinkContext> {
145
165
  private routingConfigured;
146
166
  private jsonOptions?;
147
167
  private schedulingOptions?;
168
+ private disableHttpServer;
148
169
  private repos;
149
170
  /**
150
171
  * Internal cache used to track registered handlers and potentially any overlapping routes
151
172
  */
152
173
  private handlerRouteCache;
153
174
  scheduler?: ToadScheduler;
175
+ private accessLog;
154
176
  constructor(opts: FlinkOptions);
155
177
  get ctx(): C;
156
178
  start(): Promise<this>;
@@ -57,6 +57,7 @@ var body_parser_1 = __importDefault(require("body-parser"));
57
57
  var cors_1 = __importDefault(require("cors"));
58
58
  var express_1 = __importDefault(require("express"));
59
59
  var mongodb_1 = __importDefault(require("mongodb"));
60
+ var morgan_1 = __importDefault(require("morgan"));
60
61
  var ms_1 = __importDefault(require("ms"));
61
62
  var toad_scheduler_1 = require("toad-scheduler");
62
63
  var uuid_1 = require("uuid");
@@ -93,6 +94,7 @@ var FlinkApp = /** @class */ (function () {
93
94
  this.debug = false;
94
95
  this.plugins = [];
95
96
  this.routingConfigured = false;
97
+ this.disableHttpServer = false;
96
98
  this.repos = {};
97
99
  /**
98
100
  * Internal cache used to track registered handlers and potentially any overlapping routes
@@ -108,6 +110,8 @@ var FlinkApp = /** @class */ (function () {
108
110
  this.auth = opts.auth;
109
111
  this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
110
112
  this.schedulingOptions = opts.scheduling;
113
+ this.disableHttpServer = !!opts.disableHttpServer;
114
+ this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
111
115
  }
112
116
  Object.defineProperty(FlinkApp.prototype, "ctx", {
113
117
  get: function () {
@@ -146,13 +150,21 @@ var FlinkApp = /** @class */ (function () {
146
150
  if (this.isSchedulingEnabled) {
147
151
  this.scheduler = new toad_scheduler_1.ToadScheduler();
148
152
  }
149
- this.expressApp = express_1.default();
150
- this.expressApp.use(cors_1.default(this.corsOpts));
151
- this.expressApp.use(body_parser_1.default.json(this.jsonOptions));
152
- this.expressApp.use(function (req, res, next) {
153
- req.reqId = uuid_1.v4();
154
- next();
155
- });
153
+ else {
154
+ FlinkLog_1.log.info("🚫 Scheduling is disabled");
155
+ }
156
+ if (!this.disableHttpServer) {
157
+ this.expressApp = express_1.default();
158
+ this.expressApp.use(cors_1.default(this.corsOpts));
159
+ this.expressApp.use(body_parser_1.default.json(this.jsonOptions));
160
+ if (this.accessLog.enabled) {
161
+ this.expressApp.use(morgan_1.default(this.accessLog.format));
162
+ }
163
+ this.expressApp.use(function (req, res, next) {
164
+ req.reqId = uuid_1.v4();
165
+ next();
166
+ });
167
+ }
156
168
  _i = 0, _b = this.plugins;
157
169
  _c.label = 3;
158
170
  case 3:
@@ -196,15 +208,23 @@ var FlinkApp = /** @class */ (function () {
196
208
  // Register 404 with slight delay to allow all manually added routes to be added
197
209
  // TODO: Is there a better solution to force this handler to always run last?
198
210
  setTimeout(function () {
199
- _this.expressApp.use(function (req, res, next) {
200
- res.status(404).json(FlinkErrors_1.notFound());
201
- });
211
+ if (!_this.disableHttpServer) {
212
+ _this.expressApp.use(function (req, res, next) {
213
+ res.status(404).json(FlinkErrors_1.notFound());
214
+ });
215
+ }
202
216
  _this.routingConfigured = true;
203
217
  });
204
- (_a = this.expressApp) === null || _a === void 0 ? void 0 : _a.listen(this.port, function () {
205
- FlinkLog_1.log.fontColorLog("magenta", "\u26A1\uFE0F HTTP server '" + _this.name + "' is running and waiting for connections on " + _this.port);
206
- _this.started = true;
207
- });
218
+ if (this.disableHttpServer) {
219
+ FlinkLog_1.log.info("🚧 HTTP server is disabled, but flink app is running");
220
+ this.started = true;
221
+ }
222
+ else {
223
+ (_a = this.expressApp) === null || _a === void 0 ? void 0 : _a.listen(this.port, function () {
224
+ FlinkLog_1.log.fontColorLog("magenta", "\u26A1\uFE0F HTTP server '" + _this.name + "' is running and waiting for connections on " + _this.port);
225
+ _this.started = true;
226
+ });
227
+ }
208
228
  return [2 /*return*/, this];
209
229
  }
210
230
  });
@@ -258,14 +278,24 @@ var FlinkApp = /** @class */ (function () {
258
278
  this.handlers.push(handlerConfig);
259
279
  var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
260
280
  var method = routeProps.method;
261
- var app = this.expressApp;
262
281
  if (!method) {
263
282
  FlinkLog_1.log.error("Route " + routeProps.path + " is missing http method");
264
283
  }
265
284
  if (method) {
266
285
  var methodAndRoute_1 = method.toUpperCase() + " " + routeProps.path;
267
- app[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
268
- var validate, valid, data, handlerRes, err_1, validate, valid;
286
+ if (this.disableHttpServer) {
287
+ return;
288
+ }
289
+ var validateReq_1;
290
+ var validateRes_1;
291
+ if (schema.reqSchema) {
292
+ validateReq_1 = ajv.compile(schema.reqSchema);
293
+ }
294
+ if (schema.resSchema) {
295
+ validateRes_1 = ajv.compile(schema.resSchema);
296
+ }
297
+ this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
298
+ var valid, data, handlerRes, err_1, valid;
269
299
  return __generator(this, function (_a) {
270
300
  switch (_a.label) {
271
301
  case 0:
@@ -277,18 +307,17 @@ var FlinkApp = /** @class */ (function () {
277
307
  }
278
308
  _a.label = 2;
279
309
  case 2:
280
- if (schema.reqSchema) {
281
- validate = ajv.compile(schema.reqSchema);
282
- valid = validate(req.body);
310
+ if (validateReq_1) {
311
+ valid = validateReq_1(req.body);
283
312
  if (!valid) {
284
- FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validate.errors, null, 2));
313
+ FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validateReq_1.errors, null, 2));
285
314
  FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(req.body));
286
315
  return [2 /*return*/, res.status(400).json({
287
316
  status: 400,
288
317
  error: {
289
318
  id: uuid_1.v4(),
290
319
  title: "Bad request",
291
- detail: "Schema did not validate " + JSON.stringify(validate.errors),
320
+ detail: "Schema did not validate " + JSON.stringify(validateReq_1.errors),
292
321
  },
293
322
  })];
294
323
  }
@@ -320,11 +349,10 @@ var FlinkApp = /** @class */ (function () {
320
349
  console.error(err_1);
321
350
  return [2 /*return*/, res.status(500).json(FlinkErrors_1.internalServerError(err_1))];
322
351
  case 6:
323
- if (schema.resSchema && !utils_1.isError(handlerRes)) {
324
- validate = ajv.compile(schema.resSchema);
325
- valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
352
+ if (validateRes_1 && !utils_1.isError(handlerRes)) {
353
+ valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
326
354
  if (!valid) {
327
- FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validate.errors, null, 2));
355
+ FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validateRes_1.errors, null, 2));
328
356
  FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(handlerRes.data));
329
357
  // log.debug(JSON.stringify(schema, null, 2));
330
358
  return [2 /*return*/, res.status(500).json({
@@ -332,7 +360,7 @@ var FlinkApp = /** @class */ (function () {
332
360
  error: {
333
361
  id: uuid_1.v4(),
334
362
  title: "Bad response",
335
- detail: "Schema did not validate " + JSON.stringify(validate.errors),
363
+ detail: "Schema did not validate " + JSON.stringify(validateRes_1.errors),
336
364
  },
337
365
  })];
338
366
  }
@@ -360,12 +388,12 @@ var FlinkApp = /** @class */ (function () {
360
388
  * Will not register any handlers added programmatically.
361
389
  */
362
390
  FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
363
- var _a, _b;
391
+ var _a, _b, _c;
364
392
  return __awaiter(this, void 0, void 0, function () {
365
- var _i, autoRegisteredHandlers_1, _c, handler, assumedHttpMethod;
366
- return __generator(this, function (_d) {
393
+ var _i, autoRegisteredHandlers_1, _d, handler, assumedHttpMethod, pathParams, _e, _f, param;
394
+ return __generator(this, function (_g) {
367
395
  for (_i = 0, autoRegisteredHandlers_1 = exports.autoRegisteredHandlers; _i < autoRegisteredHandlers_1.length; _i++) {
368
- _c = autoRegisteredHandlers_1[_i], handler = _c.handler, assumedHttpMethod = _c.assumedHttpMethod;
396
+ _d = autoRegisteredHandlers_1[_i], handler = _d.handler, assumedHttpMethod = _d.assumedHttpMethod;
369
397
  if (!handler.Route) {
370
398
  FlinkLog_1.log.error("Missing Props in handler " + handler.__file);
371
399
  continue;
@@ -374,11 +402,24 @@ var FlinkApp = /** @class */ (function () {
374
402
  FlinkLog_1.log.error("Missing exported handler function in handler " + handler.__file);
375
403
  continue;
376
404
  }
405
+ if (!!((_a = handler.__params) === null || _a === void 0 ? void 0 : _a.length)) {
406
+ pathParams = utils_1.getPathParams(handler.Route.path);
407
+ for (_e = 0, _f = handler.__params; _e < _f.length; _e++) {
408
+ param = _f[_e];
409
+ if (!pathParams.includes(param.name)) {
410
+ FlinkLog_1.log.error("Handler " + handler.__file + " has param " + param.name + " but it is not present in the path '" + handler.Route.path + "'");
411
+ throw new Error("Invalid/missing handler path param");
412
+ }
413
+ }
414
+ if (pathParams.length !== handler.__params.length) {
415
+ FlinkLog_1.log.warn("Handler " + handler.__file + " has " + handler.__params.length + " typed params but the path '" + handler.Route.path + "' has " + pathParams.length + " params");
416
+ }
417
+ }
377
418
  this.registerHandler({
378
419
  routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
379
420
  schema: {
380
- reqSchema: (_a = handler.__schemas) === null || _a === void 0 ? void 0 : _a.reqSchema,
381
- resSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.resSchema,
421
+ reqSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.reqSchema,
422
+ resSchema: (_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.resSchema,
382
423
  },
383
424
  queryMetadata: handler.__query || [],
384
425
  paramsMetadata: handler.__params || [],
@@ -18,3 +18,10 @@ export declare function getRepoInstanceName(fn: string): string;
18
18
  */
19
19
  export declare function getHttpMethodFromHandlerName(handlerFilename: string): HttpMethod | undefined;
20
20
  export declare function getJsDocComment(comment: string): string;
21
+ /**
22
+ * Returns array of path params from path string.
23
+ * For example `/user/:id` will return `["id"]`
24
+ * @param path
25
+ * @returns
26
+ */
27
+ export declare function getPathParams(path: string): string[];
package/dist/src/utils.js CHANGED
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.getJsDocComment = exports.getHttpMethodFromHandlerName = exports.getRepoInstanceName = exports.getCollectionNameForRepo = exports.getSchemaFiles = exports.getHandlerFiles = exports.isError = exports.isRouteMatch = exports.schemasPath = exports.handlersPath = void 0;
42
+ exports.getPathParams = exports.getJsDocComment = exports.getHttpMethodFromHandlerName = exports.getRepoInstanceName = exports.getCollectionNameForRepo = exports.getSchemaFiles = exports.getHandlerFiles = exports.isError = exports.isRouteMatch = exports.schemasPath = exports.handlersPath = void 0;
43
43
  var path_1 = require("path");
44
44
  var tiny_glob_1 = __importDefault(require("tiny-glob"));
45
45
  var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
@@ -149,3 +149,15 @@ function getJsDocComment(comment) {
149
149
  return rows.join("\n").trim();
150
150
  }
151
151
  exports.getJsDocComment = getJsDocComment;
152
+ var pathParamsRegex = /:([a-zA-Z0-9]+)/g;
153
+ /**
154
+ * Returns array of path params from path string.
155
+ * For example `/user/:id` will return `["id"]`
156
+ * @param path
157
+ * @returns
158
+ */
159
+ function getPathParams(path) {
160
+ var _a;
161
+ return ((_a = path.match(pathParamsRegex)) === null || _a === void 0 ? void 0 : _a.map(function (match) { return match.slice(1); })) || [];
162
+ }
163
+ exports.getPathParams = getPathParams;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/flink",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "main": "dist/src/index.js",
@@ -40,6 +40,7 @@
40
40
  "mkdirp": "^1.0.4",
41
41
  "mock-json-schema": "^1.0.8",
42
42
  "mongodb": "^3.6.6",
43
+ "morgan": "^1.10.0",
43
44
  "ms": "^2.0.0",
44
45
  "node-color-log": "^10.0.2",
45
46
  "passport": "^0.4.1",
@@ -57,6 +58,7 @@
57
58
  "@types/jasmine": "^3.7.1",
58
59
  "@types/json-schema": "^7.0.7",
59
60
  "@types/mkdirp": "^1.0.1",
61
+ "@types/morgan": "^1.9.4",
60
62
  "@types/node": "^15.0.1",
61
63
  "jasmine": "^3.7.0",
62
64
  "jasmine-spec-reporter": "^7.0.0",
@@ -65,5 +67,5 @@
65
67
  "rimraf": "^3.0.2",
66
68
  "ts-node": "^9.1.1"
67
69
  },
68
- "gitHead": "fc4e2f5febd2b8594d3ade42beb4e85eb7ada4ed"
70
+ "gitHead": "3c877c210da19119c074c7482e97a485d2334e80"
69
71
  }
package/src/FlinkApp.ts CHANGED
@@ -1,10 +1,11 @@
1
- import Ajv from "ajv";
1
+ import Ajv, { ValidateFunction } from "ajv";
2
2
  import addFormats from "ajv-formats";
3
3
  import bodyParser, { OptionsJson } from "body-parser";
4
4
  import cors from "cors";
5
5
  import express, { Express, Request } from "express";
6
6
  import { JSONSchema7 } from "json-schema";
7
7
  import mongodb, { Db } from "mongodb";
8
+ import morgan from "morgan";
8
9
  import ms from "ms";
9
10
  import { AsyncTask, CronJob, SimpleIntervalJob, ToadScheduler } from "toad-scheduler";
10
11
  import { v4 } from "uuid";
@@ -18,7 +19,7 @@ import { FlinkPlugin } from "./FlinkPlugin";
18
19
  import { FlinkRepo } from "./FlinkRepo";
19
20
  import { FlinkResponse } from "./FlinkResponse";
20
21
  import generateMockData from "./mock-data-generator";
21
- import { isError } from "./utils";
22
+ import { getPathParams, isError } from "./utils";
22
23
 
23
24
  const ajv = new Ajv();
24
25
  addFormats(ajv);
@@ -162,6 +163,29 @@ export interface FlinkOptions {
162
163
  // */
163
164
  // autoAssignCollection?: string;
164
165
  };
166
+
167
+ /**
168
+ * If true, the HTTP server will be disabled.
169
+ * Only useful when starting a Flink app for testing purposes.
170
+ */
171
+ disableHttpServer?: boolean;
172
+
173
+ /**
174
+ * Configuration for access logs.
175
+ */
176
+ accessLog?: {
177
+ /**
178
+ * Enables access logs for all requests.
179
+ * Defaults to true.
180
+ */
181
+ enabled?: boolean;
182
+
183
+ /**
184
+ * Optional custom format.
185
+ * Uses `morgan` format and defaults to `dev`.
186
+ */
187
+ format?: string;
188
+ };
165
189
  }
166
190
 
167
191
  export interface HandlerConfig {
@@ -203,6 +227,7 @@ export class FlinkApp<C extends FlinkContext> {
203
227
  private routingConfigured = false;
204
228
  private jsonOptions?: OptionsJson;
205
229
  private schedulingOptions?: FlinkOptions["scheduling"];
230
+ private disableHttpServer = false;
206
231
 
207
232
  private repos: { [x: string]: FlinkRepo<C> } = {};
208
233
 
@@ -213,6 +238,8 @@ export class FlinkApp<C extends FlinkContext> {
213
238
 
214
239
  public scheduler?: ToadScheduler;
215
240
 
241
+ private accessLog: { enabled: boolean; format: string };
242
+
216
243
  constructor(opts: FlinkOptions) {
217
244
  this.name = opts.name;
218
245
  this.port = opts.port || 3333;
@@ -224,6 +251,8 @@ export class FlinkApp<C extends FlinkContext> {
224
251
  this.auth = opts.auth;
225
252
  this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
226
253
  this.schedulingOptions = opts.scheduling;
254
+ this.disableHttpServer = !!opts.disableHttpServer;
255
+ this.accessLog = { enabled: true, format: "dev", ...opts.accessLog };
227
256
  }
228
257
 
229
258
  get ctx() {
@@ -253,18 +282,24 @@ export class FlinkApp<C extends FlinkContext> {
253
282
 
254
283
  if (this.isSchedulingEnabled) {
255
284
  this.scheduler = new ToadScheduler();
285
+ } else {
286
+ log.info("🚫 Scheduling is disabled");
256
287
  }
257
288
 
258
- this.expressApp = express();
259
-
260
- this.expressApp.use(cors(this.corsOpts));
289
+ if (!this.disableHttpServer) {
290
+ this.expressApp = express();
291
+ this.expressApp.use(cors(this.corsOpts));
292
+ this.expressApp.use(bodyParser.json(this.jsonOptions));
261
293
 
262
- this.expressApp.use(bodyParser.json(this.jsonOptions));
294
+ if (this.accessLog.enabled) {
295
+ this.expressApp.use(morgan(this.accessLog.format));
296
+ }
263
297
 
264
- this.expressApp.use((req, res, next) => {
265
- req.reqId = v4();
266
- next();
267
- });
298
+ this.expressApp.use((req, res, next) => {
299
+ req.reqId = v4();
300
+ next();
301
+ });
302
+ }
268
303
 
269
304
  // TODO: Add better more fine grained control when plugins are initialized, i.e. in what order
270
305
 
@@ -301,18 +336,24 @@ export class FlinkApp<C extends FlinkContext> {
301
336
  // Register 404 with slight delay to allow all manually added routes to be added
302
337
  // TODO: Is there a better solution to force this handler to always run last?
303
338
  setTimeout(() => {
304
- this.expressApp!.use((req, res, next) => {
305
- res.status(404).json(notFound());
306
- });
339
+ if (!this.disableHttpServer) {
340
+ this.expressApp!.use((req, res, next) => {
341
+ res.status(404).json(notFound());
342
+ });
343
+ }
307
344
 
308
345
  this.routingConfigured = true;
309
346
  });
310
347
 
311
- this.expressApp?.listen(this.port, () => {
312
- log.fontColorLog("magenta", `⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`);
313
-
348
+ if (this.disableHttpServer) {
349
+ log.info("🚧 HTTP server is disabled, but flink app is running");
314
350
  this.started = true;
315
- });
351
+ } else {
352
+ this.expressApp?.listen(this.port, () => {
353
+ log.fontColorLog("magenta", `⚡️ HTTP server '${this.name}' is running and waiting for connections on ${this.port}`);
354
+ this.started = true;
355
+ });
356
+ }
316
357
 
317
358
  return this;
318
359
  }
@@ -381,7 +422,6 @@ export class FlinkApp<C extends FlinkContext> {
381
422
 
382
423
  const { routeProps, schema = {} } = handlerConfig;
383
424
  const { method } = routeProps;
384
- const app = this.expressApp!;
385
425
 
386
426
  if (!method) {
387
427
  log.error(`Route ${routeProps.path} is missing http method`);
@@ -390,19 +430,33 @@ export class FlinkApp<C extends FlinkContext> {
390
430
  if (method) {
391
431
  const methodAndRoute = `${method.toUpperCase()} ${routeProps.path}`;
392
432
 
393
- app[method](routeProps.path, async (req, res) => {
433
+ if (this.disableHttpServer) {
434
+ return;
435
+ }
436
+
437
+ let validateReq: ValidateFunction<any> | undefined;
438
+ let validateRes: ValidateFunction<any> | undefined;
439
+
440
+ if (schema.reqSchema) {
441
+ validateReq = ajv.compile(schema.reqSchema);
442
+ }
443
+
444
+ if (schema.resSchema) {
445
+ validateRes = ajv.compile(schema.resSchema);
446
+ }
447
+
448
+ this.expressApp![method](routeProps.path, async (req, res) => {
394
449
  if (routeProps.permissions) {
395
450
  if (!(await this.authenticate(req, routeProps.permissions))) {
396
451
  return res.status(401).json(unauthorized());
397
452
  }
398
453
  }
399
454
 
400
- if (schema.reqSchema) {
401
- const validate = ajv.compile(schema.reqSchema);
402
- const valid = validate(req.body);
455
+ if (validateReq) {
456
+ const valid = validateReq(req.body);
403
457
 
404
458
  if (!valid) {
405
- log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validate.errors, null, 2)}`);
459
+ log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validateReq.errors, null, 2)}`);
406
460
 
407
461
  log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
408
462
 
@@ -411,7 +465,7 @@ export class FlinkApp<C extends FlinkContext> {
411
465
  error: {
412
466
  id: v4(),
413
467
  title: "Bad request",
414
- detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
468
+ detail: `Schema did not validate ${JSON.stringify(validateReq.errors)}`,
415
469
  },
416
470
  });
417
471
  }
@@ -444,12 +498,11 @@ export class FlinkApp<C extends FlinkContext> {
444
498
  return res.status(500).json(internalServerError(err as any));
445
499
  }
446
500
 
447
- if (schema.resSchema && !isError(handlerRes)) {
448
- const validate = ajv.compile(schema.resSchema);
449
- const valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
501
+ if (validateRes && !isError(handlerRes)) {
502
+ const valid = validateRes(JSON.parse(JSON.stringify(handlerRes.data)));
450
503
 
451
504
  if (!valid) {
452
- log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validate.errors, null, 2)}`);
505
+ log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validateRes.errors, null, 2)}`);
453
506
  log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
454
507
  // log.debug(JSON.stringify(schema, null, 2));
455
508
 
@@ -458,7 +511,7 @@ export class FlinkApp<C extends FlinkContext> {
458
511
  error: {
459
512
  id: v4(),
460
513
  title: "Bad response",
461
- detail: `Schema did not validate ${JSON.stringify(validate.errors)}`,
514
+ detail: `Schema did not validate ${JSON.stringify(validateRes.errors)}`,
462
515
  },
463
516
  });
464
517
  }
@@ -497,6 +550,23 @@ export class FlinkApp<C extends FlinkContext> {
497
550
  continue;
498
551
  }
499
552
 
553
+ if (!!handler.__params?.length) {
554
+ const pathParams = getPathParams(handler.Route.path);
555
+
556
+ for (const param of handler.__params) {
557
+ if (!pathParams.includes(param.name)) {
558
+ log.error(`Handler ${handler.__file} has param ${param.name} but it is not present in the path '${handler.Route.path}'`);
559
+ throw new Error("Invalid/missing handler path param");
560
+ }
561
+ }
562
+
563
+ if (pathParams.length !== handler.__params.length) {
564
+ log.warn(
565
+ `Handler ${handler.__file} has ${handler.__params.length} typed params but the path '${handler.Route.path}' has ${pathParams.length} params`
566
+ );
567
+ }
568
+ }
569
+
500
570
  this.registerHandler(
501
571
  {
502
572
  routeProps: {
package/src/utils.ts CHANGED
@@ -88,3 +88,15 @@ export function getJsDocComment(comment: string) {
88
88
 
89
89
  return rows.join("\n").trim();
90
90
  }
91
+
92
+ const pathParamsRegex = /:([a-zA-Z0-9]+)/g;
93
+
94
+ /**
95
+ * Returns array of path params from path string.
96
+ * For example `/user/:id` will return `["id"]`
97
+ * @param path
98
+ * @returns
99
+ */
100
+ export function getPathParams(path: string) {
101
+ return path.match(pathParamsRegex)?.map((match) => match.slice(1)) || [];
102
+ }