@flink-app/flink 0.4.7 → 0.6.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.
- package/cli/run.ts +10 -2
- package/dist/cli/run.js +9 -3
- package/dist/src/FlinkApp.d.ts +16 -0
- package/dist/src/FlinkApp.js +53 -17
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/utils.js +13 -1
- package/package.json +4 -2
- package/src/FlinkApp.ts +77 -15
- package/src/utils.ts +12 -0
package/cli/run.ts
CHANGED
|
@@ -40,7 +40,11 @@ module.exports = async function run(args: string[]) {
|
|
|
40
40
|
console.warn("WARNING: --entry is ignored when using --precompiled");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
43
|
+
const forkedProcess = require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
44
|
+
|
|
45
|
+
forkedProcess.on("exit", (code: any) => {
|
|
46
|
+
process.exit(code);
|
|
47
|
+
});
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -58,5 +62,9 @@ module.exports = async function run(args: string[]) {
|
|
|
58
62
|
|
|
59
63
|
compiler.emit();
|
|
60
64
|
|
|
61
|
-
require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
65
|
+
const forkedProcess = require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
66
|
+
|
|
67
|
+
forkedProcess.on("exit", (code: any) => {
|
|
68
|
+
process.exit(code);
|
|
69
|
+
});
|
|
62
70
|
};
|
package/dist/cli/run.js
CHANGED
|
@@ -43,7 +43,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
43
43
|
var TypeScriptCompiler_1 = __importDefault(require("../src/TypeScriptCompiler"));
|
|
44
44
|
module.exports = function run(args) {
|
|
45
45
|
return __awaiter(this, void 0, void 0, function () {
|
|
46
|
-
var startTime, dir, entry, compiler;
|
|
46
|
+
var startTime, dir, entry, forkedProcess_1, compiler, forkedProcess;
|
|
47
47
|
return __generator(this, function (_a) {
|
|
48
48
|
switch (_a.label) {
|
|
49
49
|
case 0:
|
|
@@ -65,7 +65,10 @@ module.exports = function run(args) {
|
|
|
65
65
|
if (args.includes("--entry")) {
|
|
66
66
|
console.warn("WARNING: --entry is ignored when using --precompiled");
|
|
67
67
|
}
|
|
68
|
-
require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
68
|
+
forkedProcess_1 = require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
69
|
+
forkedProcess_1.on("exit", function (code) {
|
|
70
|
+
process.exit(code);
|
|
71
|
+
});
|
|
69
72
|
return [2 /*return*/];
|
|
70
73
|
}
|
|
71
74
|
return [4 /*yield*/, TypeScriptCompiler_1.default.clean(dir)];
|
|
@@ -80,7 +83,10 @@ module.exports = function run(args) {
|
|
|
80
83
|
_a.sent();
|
|
81
84
|
console.log("Compilation done, took " + (Date.now() - startTime) + "ms");
|
|
82
85
|
compiler.emit();
|
|
83
|
-
require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
86
|
+
forkedProcess = require("child_process").fork(dir + "/dist/.flink/start.js");
|
|
87
|
+
forkedProcess.on("exit", function (code) {
|
|
88
|
+
process.exit(code);
|
|
89
|
+
});
|
|
84
90
|
return [2 /*return*/];
|
|
85
91
|
}
|
|
86
92
|
});
|
package/dist/src/FlinkApp.d.ts
CHANGED
|
@@ -112,6 +112,21 @@ export interface FlinkOptions {
|
|
|
112
112
|
* Only useful when starting a Flink app for testing purposes.
|
|
113
113
|
*/
|
|
114
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
|
+
};
|
|
115
130
|
}
|
|
116
131
|
export interface HandlerConfig {
|
|
117
132
|
schema?: {
|
|
@@ -157,6 +172,7 @@ export declare class FlinkApp<C extends FlinkContext> {
|
|
|
157
172
|
*/
|
|
158
173
|
private handlerRouteCache;
|
|
159
174
|
scheduler?: ToadScheduler;
|
|
175
|
+
private accessLog;
|
|
160
176
|
constructor(opts: FlinkOptions);
|
|
161
177
|
get ctx(): C;
|
|
162
178
|
start(): Promise<this>;
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -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");
|
|
@@ -110,6 +111,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
110
111
|
this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
|
|
111
112
|
this.schedulingOptions = opts.scheduling;
|
|
112
113
|
this.disableHttpServer = !!opts.disableHttpServer;
|
|
114
|
+
this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
|
|
113
115
|
}
|
|
114
116
|
Object.defineProperty(FlinkApp.prototype, "ctx", {
|
|
115
117
|
get: function () {
|
|
@@ -155,6 +157,9 @@ var FlinkApp = /** @class */ (function () {
|
|
|
155
157
|
this.expressApp = express_1.default();
|
|
156
158
|
this.expressApp.use(cors_1.default(this.corsOpts));
|
|
157
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
|
+
}
|
|
158
163
|
this.expressApp.use(function (req, res, next) {
|
|
159
164
|
req.reqId = uuid_1.v4();
|
|
160
165
|
next();
|
|
@@ -281,8 +286,16 @@ var FlinkApp = /** @class */ (function () {
|
|
|
281
286
|
if (this.disableHttpServer) {
|
|
282
287
|
return;
|
|
283
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
|
+
}
|
|
284
297
|
this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
285
|
-
var
|
|
298
|
+
var valid, data, handlerRes, err_1, valid;
|
|
286
299
|
return __generator(this, function (_a) {
|
|
287
300
|
switch (_a.label) {
|
|
288
301
|
case 0:
|
|
@@ -294,18 +307,17 @@ var FlinkApp = /** @class */ (function () {
|
|
|
294
307
|
}
|
|
295
308
|
_a.label = 2;
|
|
296
309
|
case 2:
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
valid = validate(req.body);
|
|
310
|
+
if (validateReq_1) {
|
|
311
|
+
valid = validateReq_1(req.body);
|
|
300
312
|
if (!valid) {
|
|
301
|
-
FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(
|
|
313
|
+
FlinkLog_1.log.warn(methodAndRoute_1 + ": Bad request " + JSON.stringify(validateReq_1.errors, null, 2));
|
|
302
314
|
FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(req.body));
|
|
303
315
|
return [2 /*return*/, res.status(400).json({
|
|
304
316
|
status: 400,
|
|
305
317
|
error: {
|
|
306
318
|
id: uuid_1.v4(),
|
|
307
319
|
title: "Bad request",
|
|
308
|
-
detail: "Schema did not validate " + JSON.stringify(
|
|
320
|
+
detail: "Schema did not validate " + JSON.stringify(validateReq_1.errors),
|
|
309
321
|
},
|
|
310
322
|
})];
|
|
311
323
|
}
|
|
@@ -333,15 +345,26 @@ var FlinkApp = /** @class */ (function () {
|
|
|
333
345
|
return [3 /*break*/, 6];
|
|
334
346
|
case 5:
|
|
335
347
|
err_1 = _a.sent();
|
|
348
|
+
// duck typing to check if it is a FlinkError
|
|
349
|
+
if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
|
|
350
|
+
return [2 /*return*/, res.status(err_1.status).json({
|
|
351
|
+
status: err_1.status,
|
|
352
|
+
error: {
|
|
353
|
+
id: err_1.error.id || uuid_1.v4(),
|
|
354
|
+
title: err_1.error.title || "Unhandled error: " + (err_1.error.code || err_1.status),
|
|
355
|
+
detail: err_1.error.detail,
|
|
356
|
+
code: err_1.error.code,
|
|
357
|
+
},
|
|
358
|
+
})];
|
|
359
|
+
}
|
|
336
360
|
FlinkLog_1.log.warn("Handler '" + methodAndRoute_1 + "' threw unhandled exception " + err_1);
|
|
337
361
|
console.error(err_1);
|
|
338
362
|
return [2 /*return*/, res.status(500).json(FlinkErrors_1.internalServerError(err_1))];
|
|
339
363
|
case 6:
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
364
|
+
if (validateRes_1 && !utils_1.isError(handlerRes)) {
|
|
365
|
+
valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
343
366
|
if (!valid) {
|
|
344
|
-
FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(
|
|
367
|
+
FlinkLog_1.log.warn("[" + req.reqId + "] " + methodAndRoute_1 + ": Bad response " + JSON.stringify(validateRes_1.errors, null, 2));
|
|
345
368
|
FlinkLog_1.log.debug("Invalid json: " + JSON.stringify(handlerRes.data));
|
|
346
369
|
// log.debug(JSON.stringify(schema, null, 2));
|
|
347
370
|
return [2 /*return*/, res.status(500).json({
|
|
@@ -349,7 +372,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
349
372
|
error: {
|
|
350
373
|
id: uuid_1.v4(),
|
|
351
374
|
title: "Bad response",
|
|
352
|
-
detail: "Schema did not validate " + JSON.stringify(
|
|
375
|
+
detail: "Schema did not validate " + JSON.stringify(validateRes_1.errors),
|
|
353
376
|
},
|
|
354
377
|
})];
|
|
355
378
|
}
|
|
@@ -377,12 +400,12 @@ var FlinkApp = /** @class */ (function () {
|
|
|
377
400
|
* Will not register any handlers added programmatically.
|
|
378
401
|
*/
|
|
379
402
|
FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
|
|
380
|
-
var _a, _b;
|
|
403
|
+
var _a, _b, _c;
|
|
381
404
|
return __awaiter(this, void 0, void 0, function () {
|
|
382
|
-
var _i, autoRegisteredHandlers_1,
|
|
383
|
-
return __generator(this, function (
|
|
405
|
+
var _i, autoRegisteredHandlers_1, _d, handler, assumedHttpMethod, pathParams, _e, _f, param;
|
|
406
|
+
return __generator(this, function (_g) {
|
|
384
407
|
for (_i = 0, autoRegisteredHandlers_1 = exports.autoRegisteredHandlers; _i < autoRegisteredHandlers_1.length; _i++) {
|
|
385
|
-
|
|
408
|
+
_d = autoRegisteredHandlers_1[_i], handler = _d.handler, assumedHttpMethod = _d.assumedHttpMethod;
|
|
386
409
|
if (!handler.Route) {
|
|
387
410
|
FlinkLog_1.log.error("Missing Props in handler " + handler.__file);
|
|
388
411
|
continue;
|
|
@@ -391,11 +414,24 @@ var FlinkApp = /** @class */ (function () {
|
|
|
391
414
|
FlinkLog_1.log.error("Missing exported handler function in handler " + handler.__file);
|
|
392
415
|
continue;
|
|
393
416
|
}
|
|
417
|
+
if (!!((_a = handler.__params) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
418
|
+
pathParams = utils_1.getPathParams(handler.Route.path);
|
|
419
|
+
for (_e = 0, _f = handler.__params; _e < _f.length; _e++) {
|
|
420
|
+
param = _f[_e];
|
|
421
|
+
if (!pathParams.includes(param.name)) {
|
|
422
|
+
FlinkLog_1.log.error("Handler " + handler.__file + " has param " + param.name + " but it is not present in the path '" + handler.Route.path + "'");
|
|
423
|
+
throw new Error("Invalid/missing handler path param");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (pathParams.length !== handler.__params.length) {
|
|
427
|
+
FlinkLog_1.log.warn("Handler " + handler.__file + " has " + handler.__params.length + " typed params but the path '" + handler.Route.path + "' has " + pathParams.length + " params");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
394
430
|
this.registerHandler({
|
|
395
431
|
routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
|
|
396
432
|
schema: {
|
|
397
|
-
reqSchema: (
|
|
398
|
-
resSchema: (
|
|
433
|
+
reqSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.reqSchema,
|
|
434
|
+
resSchema: (_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.resSchema,
|
|
399
435
|
},
|
|
400
436
|
queryMetadata: handler.__query || [],
|
|
401
437
|
paramsMetadata: handler.__params || [],
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.6.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": "
|
|
70
|
+
"gitHead": "42ab7a99632d1fba08096d51800dfc0deb4b0222"
|
|
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);
|
|
@@ -168,6 +169,23 @@ export interface FlinkOptions {
|
|
|
168
169
|
* Only useful when starting a Flink app for testing purposes.
|
|
169
170
|
*/
|
|
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
|
+
};
|
|
171
189
|
}
|
|
172
190
|
|
|
173
191
|
export interface HandlerConfig {
|
|
@@ -220,6 +238,8 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
220
238
|
|
|
221
239
|
public scheduler?: ToadScheduler;
|
|
222
240
|
|
|
241
|
+
private accessLog: { enabled: boolean; format: string };
|
|
242
|
+
|
|
223
243
|
constructor(opts: FlinkOptions) {
|
|
224
244
|
this.name = opts.name;
|
|
225
245
|
this.port = opts.port || 3333;
|
|
@@ -232,6 +252,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
232
252
|
this.jsonOptions = opts.jsonOptions || { limit: "1mb" };
|
|
233
253
|
this.schedulingOptions = opts.scheduling;
|
|
234
254
|
this.disableHttpServer = !!opts.disableHttpServer;
|
|
255
|
+
this.accessLog = { enabled: true, format: "dev", ...opts.accessLog };
|
|
235
256
|
}
|
|
236
257
|
|
|
237
258
|
get ctx() {
|
|
@@ -267,11 +288,13 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
267
288
|
|
|
268
289
|
if (!this.disableHttpServer) {
|
|
269
290
|
this.expressApp = express();
|
|
270
|
-
|
|
271
291
|
this.expressApp.use(cors(this.corsOpts));
|
|
272
|
-
|
|
273
292
|
this.expressApp.use(bodyParser.json(this.jsonOptions));
|
|
274
293
|
|
|
294
|
+
if (this.accessLog.enabled) {
|
|
295
|
+
this.expressApp.use(morgan(this.accessLog.format));
|
|
296
|
+
}
|
|
297
|
+
|
|
275
298
|
this.expressApp.use((req, res, next) => {
|
|
276
299
|
req.reqId = v4();
|
|
277
300
|
next();
|
|
@@ -411,6 +434,17 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
411
434
|
return;
|
|
412
435
|
}
|
|
413
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
|
+
|
|
414
448
|
this.expressApp => {
|
|
415
449
|
if (routeProps.permissions) {
|
|
416
450
|
if (!(await this.authenticate(req, routeProps.permissions))) {
|
|
@@ -418,12 +452,11 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
418
452
|
}
|
|
419
453
|
}
|
|
420
454
|
|
|
421
|
-
if (
|
|
422
|
-
const
|
|
423
|
-
const valid = validate(req.body);
|
|
455
|
+
if (validateReq) {
|
|
456
|
+
const valid = validateReq(req.body);
|
|
424
457
|
|
|
425
458
|
if (!valid) {
|
|
426
|
-
log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(
|
|
459
|
+
log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validateReq.errors, null, 2)}`);
|
|
427
460
|
|
|
428
461
|
log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
|
|
429
462
|
|
|
@@ -432,7 +465,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
432
465
|
error: {
|
|
433
466
|
id: v4(),
|
|
434
467
|
title: "Bad request",
|
|
435
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
468
|
+
detail: `Schema did not validate ${JSON.stringify(validateReq.errors)}`,
|
|
436
469
|
},
|
|
437
470
|
});
|
|
438
471
|
}
|
|
@@ -459,18 +492,30 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
459
492
|
ctx: this.ctx,
|
|
460
493
|
origin: routeProps.origin,
|
|
461
494
|
});
|
|
462
|
-
} catch (err) {
|
|
495
|
+
} catch (err: any) {
|
|
496
|
+
// duck typing to check if it is a FlinkError
|
|
497
|
+
if (typeof err.status === "number" && err.status >= 400 && err.status < 600 && err.error) {
|
|
498
|
+
return res.status(err.status).json({
|
|
499
|
+
status: err.status,
|
|
500
|
+
error: {
|
|
501
|
+
id: err.error.id || v4(),
|
|
502
|
+
title: err.error.title || `Unhandled error: ${err.error.code || err.status}`,
|
|
503
|
+
detail: err.error.detail,
|
|
504
|
+
code: err.error.code,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
463
509
|
log.warn(`Handler '${methodAndRoute}' threw unhandled exception ${err}`);
|
|
464
510
|
console.error(err);
|
|
465
511
|
return res.status(500).json(internalServerError(err as any));
|
|
466
512
|
}
|
|
467
513
|
|
|
468
|
-
if (
|
|
469
|
-
const
|
|
470
|
-
const valid = validate(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
514
|
+
if (validateRes && !isError(handlerRes)) {
|
|
515
|
+
const valid = validateRes(JSON.parse(JSON.stringify(handlerRes.data)));
|
|
471
516
|
|
|
472
517
|
if (!valid) {
|
|
473
|
-
log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(
|
|
518
|
+
log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validateRes.errors, null, 2)}`);
|
|
474
519
|
log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
|
|
475
520
|
// log.debug(JSON.stringify(schema, null, 2));
|
|
476
521
|
|
|
@@ -479,7 +524,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
479
524
|
error: {
|
|
480
525
|
id: v4(),
|
|
481
526
|
title: "Bad response",
|
|
482
|
-
detail: `Schema did not validate ${JSON.stringify(
|
|
527
|
+
detail: `Schema did not validate ${JSON.stringify(validateRes.errors)}`,
|
|
483
528
|
},
|
|
484
529
|
});
|
|
485
530
|
}
|
|
@@ -518,6 +563,23 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
518
563
|
continue;
|
|
519
564
|
}
|
|
520
565
|
|
|
566
|
+
if (!!handler.__params?.length) {
|
|
567
|
+
const pathParams = getPathParams(handler.Route.path);
|
|
568
|
+
|
|
569
|
+
for (const param of handler.__params) {
|
|
570
|
+
if (!pathParams.includes(param.name)) {
|
|
571
|
+
log.error(`Handler ${handler.__file} has param ${param.name} but it is not present in the path '${handler.Route.path}'`);
|
|
572
|
+
throw new Error("Invalid/missing handler path param");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (pathParams.length !== handler.__params.length) {
|
|
577
|
+
log.warn(
|
|
578
|
+
`Handler ${handler.__file} has ${handler.__params.length} typed params but the path '${handler.Route.path}' has ${pathParams.length} params`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
521
583
|
this.registerHandler(
|
|
522
584
|
{
|
|
523
585
|
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
|
+
}
|