@terreno/api 0.11.9 → 0.12.1
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/dist/__tests__/openApiCompat.test.d.ts +1 -0
- package/dist/__tests__/openApiCompat.test.js +311 -0
- package/dist/githubAuth.js +2 -1
- package/dist/openApiCompat.d.ts +3 -2
- package/dist/openApiCompat.js +8 -15
- package/dist/transformers.js +4 -2
- package/package.json +1 -1
- package/src/__tests__/openApiCompat.test.ts +299 -0
- package/src/githubAuth.ts +2 -1
- package/src/openApiCompat.ts +39 -15
- package/src/permissions.ts +2 -2
- package/src/transformers.ts +4 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __values = (this && this.__values) || function(o) {
|
|
39
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
40
|
+
if (m) return m.call(o);
|
|
41
|
+
if (o && typeof o.length === "number") return {
|
|
42
|
+
next: function () {
|
|
43
|
+
if (o && i >= o.length) o = void 0;
|
|
44
|
+
return { value: o && o[i++], done: !o };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
48
|
+
};
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
var bun_test_1 = require("bun:test");
|
|
54
|
+
var express_1 = __importDefault(require("express"));
|
|
55
|
+
var supertest_1 = __importDefault(require("supertest"));
|
|
56
|
+
var openApiCompat_1 = require("../openApiCompat");
|
|
57
|
+
var getRouterStack = function (app) {
|
|
58
|
+
var _a;
|
|
59
|
+
var internal = app;
|
|
60
|
+
var router = (_a = internal._router) !== null && _a !== void 0 ? _a : internal.router;
|
|
61
|
+
if (!router) {
|
|
62
|
+
throw new Error("Express app has no router");
|
|
63
|
+
}
|
|
64
|
+
return router.stack;
|
|
65
|
+
};
|
|
66
|
+
var findLayer = function (stack, predicate) {
|
|
67
|
+
var e_1, _a;
|
|
68
|
+
var _b, _c;
|
|
69
|
+
try {
|
|
70
|
+
for (var stack_1 = __values(stack), stack_1_1 = stack_1.next(); !stack_1_1.done; stack_1_1 = stack_1.next()) {
|
|
71
|
+
var layer = stack_1_1.value;
|
|
72
|
+
if (predicate(layer)) {
|
|
73
|
+
return layer;
|
|
74
|
+
}
|
|
75
|
+
if ((_b = layer.handle) === null || _b === void 0 ? void 0 : _b.stack) {
|
|
76
|
+
var nested = findLayer(layer.handle.stack, predicate);
|
|
77
|
+
if (nested) {
|
|
78
|
+
return nested;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if ((_c = layer.route) === null || _c === void 0 ? void 0 : _c.stack) {
|
|
82
|
+
var nested = findLayer(layer.route.stack, predicate);
|
|
83
|
+
if (nested) {
|
|
84
|
+
return nested;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
90
|
+
finally {
|
|
91
|
+
try {
|
|
92
|
+
if (stack_1_1 && !stack_1_1.done && (_a = stack_1.return)) _a.call(stack_1);
|
|
93
|
+
}
|
|
94
|
+
finally { if (e_1) throw e_1.error; }
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
var runMiddleware = function (app) {
|
|
99
|
+
var middlewareReq = { app: app };
|
|
100
|
+
var middlewareRes = {};
|
|
101
|
+
var called = false;
|
|
102
|
+
(0, openApiCompat_1.openApiCompatMiddleware)(middlewareReq, middlewareRes, function () {
|
|
103
|
+
called = true;
|
|
104
|
+
});
|
|
105
|
+
if (!called) {
|
|
106
|
+
throw new Error("next() was not called by openApiCompatMiddleware");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
(0, bun_test_1.describe)("openApiCompat", function () {
|
|
110
|
+
(0, bun_test_1.describe)("patchAppUse", function () {
|
|
111
|
+
(0, bun_test_1.it)("annotates layers added via app.use(mountPath, ...) with the mount path", function () {
|
|
112
|
+
var app = (0, express_1.default)();
|
|
113
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
114
|
+
var subRouter = express_1.default.Router();
|
|
115
|
+
subRouter.get("/list", function (_req, res) {
|
|
116
|
+
res.json({ ok: true });
|
|
117
|
+
});
|
|
118
|
+
app.use("/sub", subRouter);
|
|
119
|
+
var stack = getRouterStack(app);
|
|
120
|
+
var mountedLayer = stack.find(function (layer) { return layer.__openApiMountPath !== undefined; });
|
|
121
|
+
(0, bun_test_1.expect)(mountedLayer).toBeDefined();
|
|
122
|
+
(0, bun_test_1.expect)(mountedLayer === null || mountedLayer === void 0 ? void 0 : mountedLayer.__openApiMountPath).toBe("/sub");
|
|
123
|
+
});
|
|
124
|
+
(0, bun_test_1.it)("strips trailing slashes from the recorded mount path", function () {
|
|
125
|
+
var app = (0, express_1.default)();
|
|
126
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
127
|
+
var subRouter = express_1.default.Router();
|
|
128
|
+
subRouter.get("/", function (_req, res) {
|
|
129
|
+
res.send("ok");
|
|
130
|
+
});
|
|
131
|
+
app.use("/api/v1///", subRouter);
|
|
132
|
+
var stack = getRouterStack(app);
|
|
133
|
+
var mountedLayer = stack.find(function (layer) { return layer.__openApiMountPath !== undefined; });
|
|
134
|
+
(0, bun_test_1.expect)(mountedLayer === null || mountedLayer === void 0 ? void 0 : mountedLayer.__openApiMountPath).toBe("/api/v1");
|
|
135
|
+
});
|
|
136
|
+
(0, bun_test_1.it)("does not annotate layers when mount path is missing or '/'", function () {
|
|
137
|
+
var e_2, _a;
|
|
138
|
+
var app = (0, express_1.default)();
|
|
139
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
140
|
+
app.use(function (_req, _res, next) { return next(); });
|
|
141
|
+
var subRouter = express_1.default.Router();
|
|
142
|
+
subRouter.get("/x", function (_req, res) { return res.send("x"); });
|
|
143
|
+
app.use("/", subRouter);
|
|
144
|
+
var stack = getRouterStack(app);
|
|
145
|
+
try {
|
|
146
|
+
for (var stack_2 = __values(stack), stack_2_1 = stack_2.next(); !stack_2_1.done; stack_2_1 = stack_2.next()) {
|
|
147
|
+
var layer = stack_2_1.value;
|
|
148
|
+
(0, bun_test_1.expect)(layer.__openApiMountPath).toBeUndefined();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
152
|
+
finally {
|
|
153
|
+
try {
|
|
154
|
+
if (stack_2_1 && !stack_2_1.done && (_a = stack_2.return)) _a.call(stack_2);
|
|
155
|
+
}
|
|
156
|
+
finally { if (e_2) throw e_2.error; }
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
(0, bun_test_1.it)("returns the underlying use() return value", function () {
|
|
160
|
+
var app = (0, express_1.default)();
|
|
161
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
162
|
+
var result = app.use(function (_req, _res, next) { return next(); });
|
|
163
|
+
(0, bun_test_1.expect)(result).toBe(app);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
(0, bun_test_1.describe)("openApiCompatMiddleware", function () {
|
|
167
|
+
(0, bun_test_1.it)("calls next() and is a no-op when the app has no router yet", function () {
|
|
168
|
+
var fakeReq = { app: {} };
|
|
169
|
+
var fakeRes = {};
|
|
170
|
+
var nextCalled = false;
|
|
171
|
+
(0, openApiCompat_1.openApiCompatMiddleware)(fakeReq, fakeRes, function () {
|
|
172
|
+
nextCalled = true;
|
|
173
|
+
});
|
|
174
|
+
(0, bun_test_1.expect)(nextCalled).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
(0, bun_test_1.it)("sets fast_slash regexp on layers that use Express 5 .slash", function () {
|
|
177
|
+
var app = (0, express_1.default)();
|
|
178
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
179
|
+
app.use(function (_req, _res, next) { return next(); });
|
|
180
|
+
var stack = getRouterStack(app);
|
|
181
|
+
var slashLayer = stack.find(function (layer) { return layer.slash === true; });
|
|
182
|
+
(0, bun_test_1.expect)(slashLayer).toBeDefined();
|
|
183
|
+
runMiddleware(app);
|
|
184
|
+
var patchedRegexp = slashLayer === null || slashLayer === void 0 ? void 0 : slashLayer.regexp;
|
|
185
|
+
(0, bun_test_1.expect)(patchedRegexp === null || patchedRegexp === void 0 ? void 0 : patchedRegexp.fast_slash).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
(0, bun_test_1.it)("builds a regexp for sub-routers mounted at a non-root path", function () {
|
|
188
|
+
var app = (0, express_1.default)();
|
|
189
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
190
|
+
var subRouter = express_1.default.Router();
|
|
191
|
+
subRouter.get("/foo", function (_req, res) { return res.send("foo"); });
|
|
192
|
+
app.use("/sub", subRouter);
|
|
193
|
+
runMiddleware(app);
|
|
194
|
+
var stack = getRouterStack(app);
|
|
195
|
+
var subLayer = stack.find(function (layer) { return layer.__openApiMountPath === "/sub"; });
|
|
196
|
+
(0, bun_test_1.expect)(subLayer).toBeDefined();
|
|
197
|
+
var regexp = subLayer === null || subLayer === void 0 ? void 0 : subLayer.regexp;
|
|
198
|
+
(0, bun_test_1.expect)(regexp).toBeInstanceOf(RegExp);
|
|
199
|
+
(0, bun_test_1.expect)(regexp.test("/sub/foo")).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
(0, bun_test_1.it)("builds a regexp from layer.path and extracts :param keys", function () {
|
|
202
|
+
var app = (0, express_1.default)();
|
|
203
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
204
|
+
var stack = getRouterStack(app);
|
|
205
|
+
stack.push({
|
|
206
|
+
name: "boundDispatch",
|
|
207
|
+
path: "/users/:userId",
|
|
208
|
+
});
|
|
209
|
+
runMiddleware(app);
|
|
210
|
+
var inserted = stack[stack.length - 1];
|
|
211
|
+
(0, bun_test_1.expect)(inserted.regexp).toBeInstanceOf(RegExp);
|
|
212
|
+
(0, bun_test_1.expect)(inserted.regexp.test("/users/abc")).toBe(true);
|
|
213
|
+
(0, bun_test_1.expect)(inserted.keys).toEqual([{ name: "userId", optional: false }]);
|
|
214
|
+
});
|
|
215
|
+
(0, bun_test_1.it)("builds a regexp from route.path for plain route layers", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
216
|
+
var app, stack, routeLayer, res;
|
|
217
|
+
return __generator(this, function (_a) {
|
|
218
|
+
switch (_a.label) {
|
|
219
|
+
case 0:
|
|
220
|
+
app = (0, express_1.default)();
|
|
221
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
222
|
+
app.get("/items/:itemId", function (_req, res) {
|
|
223
|
+
res.json({ ok: true });
|
|
224
|
+
});
|
|
225
|
+
runMiddleware(app);
|
|
226
|
+
stack = getRouterStack(app);
|
|
227
|
+
routeLayer = findLayer(stack, function (layer) { var _a; return ((_a = layer.route) === null || _a === void 0 ? void 0 : _a.path) === "/items/:itemId"; });
|
|
228
|
+
(0, bun_test_1.expect)(routeLayer).toBeDefined();
|
|
229
|
+
(0, bun_test_1.expect)(routeLayer === null || routeLayer === void 0 ? void 0 : routeLayer.regexp).toBeInstanceOf(RegExp);
|
|
230
|
+
(0, bun_test_1.expect)((routeLayer === null || routeLayer === void 0 ? void 0 : routeLayer.regexp).test("/items/123")).toBe(true);
|
|
231
|
+
(0, bun_test_1.expect)(routeLayer === null || routeLayer === void 0 ? void 0 : routeLayer.keys).toEqual([{ name: "itemId", optional: false }]);
|
|
232
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).get("/items/123").expect(200)];
|
|
233
|
+
case 1:
|
|
234
|
+
res = _a.sent();
|
|
235
|
+
(0, bun_test_1.expect)(res.body).toEqual({ ok: true });
|
|
236
|
+
return [2 /*return*/];
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}); });
|
|
240
|
+
(0, bun_test_1.it)("falls back to /^\\/?$/ when no path information is available", function () {
|
|
241
|
+
var app = (0, express_1.default)();
|
|
242
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
243
|
+
var stack = getRouterStack(app);
|
|
244
|
+
stack.push({ name: "anonymous" });
|
|
245
|
+
runMiddleware(app);
|
|
246
|
+
var inserted = stack[stack.length - 1];
|
|
247
|
+
(0, bun_test_1.expect)(inserted.regexp).toBeInstanceOf(RegExp);
|
|
248
|
+
(0, bun_test_1.expect)(inserted.regexp.source).toBe("^\\/?$");
|
|
249
|
+
(0, bun_test_1.expect)(inserted.keys).toEqual([]);
|
|
250
|
+
});
|
|
251
|
+
(0, bun_test_1.it)("skips layers that already have a regexp set", function () {
|
|
252
|
+
var app = (0, express_1.default)();
|
|
253
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
254
|
+
var stack = getRouterStack(app);
|
|
255
|
+
var existing = /^prebuilt$/;
|
|
256
|
+
stack.push({
|
|
257
|
+
name: "preset",
|
|
258
|
+
path: "/should-be-ignored/:id",
|
|
259
|
+
regexp: existing,
|
|
260
|
+
});
|
|
261
|
+
runMiddleware(app);
|
|
262
|
+
var inserted = stack[stack.length - 1];
|
|
263
|
+
(0, bun_test_1.expect)(inserted.regexp).toBe(existing);
|
|
264
|
+
(0, bun_test_1.expect)(inserted.keys).toBeUndefined();
|
|
265
|
+
});
|
|
266
|
+
(0, bun_test_1.it)("converts Express 5 string keys arrays into {name, optional} objects", function () {
|
|
267
|
+
var app = (0, express_1.default)();
|
|
268
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
269
|
+
var stack = getRouterStack(app);
|
|
270
|
+
stack.push({
|
|
271
|
+
keys: ["userId", "postId"],
|
|
272
|
+
name: "boundDispatch",
|
|
273
|
+
path: "/users/:userId/posts/:postId",
|
|
274
|
+
});
|
|
275
|
+
runMiddleware(app);
|
|
276
|
+
var inserted = stack[stack.length - 1];
|
|
277
|
+
(0, bun_test_1.expect)(inserted.keys).toEqual([
|
|
278
|
+
{ name: "userId", optional: false },
|
|
279
|
+
{ name: "postId", optional: false },
|
|
280
|
+
]);
|
|
281
|
+
});
|
|
282
|
+
(0, bun_test_1.it)("recurses into nested router stacks added via patchAppUse", function () {
|
|
283
|
+
var app = (0, express_1.default)();
|
|
284
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
285
|
+
var inner = express_1.default.Router();
|
|
286
|
+
inner.get("/widgets/:widgetId", function (_req, res) { return res.send("widget"); });
|
|
287
|
+
app.use("/api", inner);
|
|
288
|
+
runMiddleware(app);
|
|
289
|
+
var stack = getRouterStack(app);
|
|
290
|
+
var widgetLayer = findLayer(stack, function (layer) { var _a; return ((_a = layer.route) === null || _a === void 0 ? void 0 : _a.path) === "/widgets/:widgetId"; });
|
|
291
|
+
(0, bun_test_1.expect)(widgetLayer).toBeDefined();
|
|
292
|
+
(0, bun_test_1.expect)(widgetLayer === null || widgetLayer === void 0 ? void 0 : widgetLayer.regexp).toBeInstanceOf(RegExp);
|
|
293
|
+
(0, bun_test_1.expect)((widgetLayer === null || widgetLayer === void 0 ? void 0 : widgetLayer.regexp).test("/widgets/42")).toBe(true);
|
|
294
|
+
(0, bun_test_1.expect)(widgetLayer === null || widgetLayer === void 0 ? void 0 : widgetLayer.keys).toEqual([{ name: "widgetId", optional: false }]);
|
|
295
|
+
});
|
|
296
|
+
(0, bun_test_1.it)("escapes regex metacharacters in static path segments", function () {
|
|
297
|
+
var app = (0, express_1.default)();
|
|
298
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
299
|
+
var stack = getRouterStack(app);
|
|
300
|
+
stack.push({
|
|
301
|
+
name: "boundDispatch",
|
|
302
|
+
path: "/foo.bar/baz",
|
|
303
|
+
});
|
|
304
|
+
runMiddleware(app);
|
|
305
|
+
var inserted = stack[stack.length - 1];
|
|
306
|
+
var regexp = inserted.regexp;
|
|
307
|
+
(0, bun_test_1.expect)(regexp.test("/foo.bar/baz")).toBe(true);
|
|
308
|
+
(0, bun_test_1.expect)(regexp.test("/fooXbar/baz")).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
package/dist/githubAuth.js
CHANGED
|
@@ -40,6 +40,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
42
|
exports.addGitHubAuthRoutes = exports.setupGitHubAuth = exports.githubUserPlugin = void 0;
|
|
43
|
+
var express_1 = require("express");
|
|
43
44
|
var passport_1 = __importDefault(require("passport"));
|
|
44
45
|
var passport_github2_1 = require("passport-github2");
|
|
45
46
|
var auth_1 = require("./auth");
|
|
@@ -178,7 +179,7 @@ exports.setupGitHubAuth = setupGitHubAuth;
|
|
|
178
179
|
* - DELETE /auth/github/unlink - Unlinks GitHub account from authenticated user (requires JWT auth)
|
|
179
180
|
*/
|
|
180
181
|
var addGitHubAuthRoutes = function (app, userModel, githubOptions, authOptions) {
|
|
181
|
-
var router =
|
|
182
|
+
var router = (0, express_1.Router)();
|
|
182
183
|
// Initiate GitHub OAuth flow
|
|
183
184
|
router.get("/github", function (req, _res, next) {
|
|
184
185
|
// Store the return URL in session or query for redirect after auth
|
package/dist/openApiCompat.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @see https://github.com/wesleytodd/express-openapi/issues/70
|
|
10
10
|
*/
|
|
11
|
+
import type { Application, NextFunction, Request, Response } from "express";
|
|
11
12
|
/**
|
|
12
13
|
* Wraps an Express app's `use` method to record the mount path on each
|
|
13
14
|
* layer added to the router stack. This runs at setup time so that
|
|
@@ -15,9 +16,9 @@
|
|
|
15
16
|
*
|
|
16
17
|
* Must be called before any routes are registered.
|
|
17
18
|
*/
|
|
18
|
-
export declare const patchAppUse: (app:
|
|
19
|
+
export declare const patchAppUse: (app: Application) => void;
|
|
19
20
|
/**
|
|
20
21
|
* Express middleware that patches the router stack before OpenAPI doc
|
|
21
22
|
* generation. Must be mounted before the openapi middleware.
|
|
22
23
|
*/
|
|
23
|
-
export declare const openApiCompatMiddleware: (req:
|
|
24
|
+
export declare const openApiCompatMiddleware: (req: Request, _res: Response, next: NextFunction) => void;
|
package/dist/openApiCompat.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Patches the Express router stack to add `.regexp` on layers for
|
|
4
|
-
* compatibility with @wesleytodd/openapi, which expects Express 4-style
|
|
5
|
-
* layers with `.regexp.fast_slash`.
|
|
6
|
-
*
|
|
7
|
-
* In Express 5 (router@2.x), layers use `.slash` (boolean) and `.matchers`
|
|
8
|
-
* (array of functions) instead of `.regexp`.
|
|
9
|
-
*
|
|
10
|
-
* @see https://github.com/wesleytodd/express-openapi/issues/70
|
|
11
|
-
*/
|
|
12
2
|
var __values = (this && this.__values) || function(o) {
|
|
13
3
|
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
14
4
|
if (m) return m.call(o);
|
|
@@ -157,19 +147,20 @@ var patchRouterStack = function (stack) {
|
|
|
157
147
|
* Must be called before any routes are registered.
|
|
158
148
|
*/
|
|
159
149
|
var patchAppUse = function (app) {
|
|
150
|
+
var internal = app;
|
|
160
151
|
var originalUse = app.use.bind(app);
|
|
161
152
|
var patchedUse = function () {
|
|
162
|
-
var _a, _b;
|
|
153
|
+
var _a, _b, _c, _d;
|
|
163
154
|
var args = [];
|
|
164
155
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
165
156
|
args[_i] = arguments[_i];
|
|
166
157
|
}
|
|
167
158
|
// Track stack length before the call
|
|
168
|
-
var router =
|
|
169
|
-
var stackBefore = (
|
|
159
|
+
var router = (_a = internal._router) !== null && _a !== void 0 ? _a : internal.router;
|
|
160
|
+
var stackBefore = (_c = (_b = router === null || router === void 0 ? void 0 : router.stack) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
|
|
170
161
|
var result = originalUse.apply(void 0, __spreadArray([], __read(args), false));
|
|
171
162
|
// After use(), check if new layers were added and annotate them
|
|
172
|
-
var routerAfter =
|
|
163
|
+
var routerAfter = (_d = internal._router) !== null && _d !== void 0 ? _d : internal.router;
|
|
173
164
|
if (routerAfter === null || routerAfter === void 0 ? void 0 : routerAfter.stack) {
|
|
174
165
|
var stackAfter = routerAfter.stack.length;
|
|
175
166
|
// The first arg is the mount path if it's a string
|
|
@@ -190,7 +181,9 @@ exports.patchAppUse = patchAppUse;
|
|
|
190
181
|
* generation. Must be mounted before the openapi middleware.
|
|
191
182
|
*/
|
|
192
183
|
var openApiCompatMiddleware = function (req, _res, next) {
|
|
193
|
-
var
|
|
184
|
+
var _a;
|
|
185
|
+
var internal = req.app;
|
|
186
|
+
var router = (_a = internal._router) !== null && _a !== void 0 ? _a : internal.router;
|
|
194
187
|
if (router === null || router === void 0 ? void 0 : router.stack) {
|
|
195
188
|
patchRouterStack(router.stack);
|
|
196
189
|
}
|
package/dist/transformers.js
CHANGED
|
@@ -197,6 +197,7 @@ function serialize(req, options, data) {
|
|
|
197
197
|
*/
|
|
198
198
|
function defaultResponseHandler(doc, method, request, options) {
|
|
199
199
|
return __awaiter(this, void 0, void 0, function () {
|
|
200
|
+
var errorObj;
|
|
200
201
|
return __generator(this, function (_a) {
|
|
201
202
|
if (!doc) {
|
|
202
203
|
return [2 /*return*/, null];
|
|
@@ -205,10 +206,11 @@ function defaultResponseHandler(doc, method, request, options) {
|
|
|
205
206
|
return [2 /*return*/, serialize(request, options, doc)];
|
|
206
207
|
}
|
|
207
208
|
catch (error) {
|
|
209
|
+
errorObj = error;
|
|
208
210
|
throw new errors_1.APIError({
|
|
209
|
-
error:
|
|
211
|
+
error: errorObj,
|
|
210
212
|
status: 400,
|
|
211
|
-
title: "Error serializing ".concat(method, " response: ").concat(
|
|
213
|
+
title: "Error serializing ".concat(method, " response: ").concat(errorObj.message),
|
|
212
214
|
});
|
|
213
215
|
}
|
|
214
216
|
return [2 /*return*/];
|
package/package.json
CHANGED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import {describe, expect, it} from "bun:test";
|
|
2
|
+
import express, {type Application, type Request, type Response} from "express";
|
|
3
|
+
import supertest from "supertest";
|
|
4
|
+
|
|
5
|
+
import {openApiCompatMiddleware, patchAppUse} from "../openApiCompat";
|
|
6
|
+
|
|
7
|
+
interface PatchedLayer {
|
|
8
|
+
name?: string;
|
|
9
|
+
regexp?: {fast_slash?: boolean} | RegExp;
|
|
10
|
+
slash?: boolean;
|
|
11
|
+
path?: string;
|
|
12
|
+
route?: {path?: string; stack?: PatchedLayer[]};
|
|
13
|
+
handle?: {stack?: PatchedLayer[]};
|
|
14
|
+
keys?: Array<{name: string; optional: boolean}> | string[];
|
|
15
|
+
__openApiMountPath?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AppWithRouter {
|
|
19
|
+
_router?: {stack: PatchedLayer[]};
|
|
20
|
+
router?: {stack: PatchedLayer[]};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getRouterStack = (app: Application): PatchedLayer[] => {
|
|
24
|
+
const internal = app as unknown as AppWithRouter;
|
|
25
|
+
const router = internal._router ?? internal.router;
|
|
26
|
+
if (!router) {
|
|
27
|
+
throw new Error("Express app has no router");
|
|
28
|
+
}
|
|
29
|
+
return router.stack as PatchedLayer[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const findLayer = (
|
|
33
|
+
stack: PatchedLayer[],
|
|
34
|
+
predicate: (layer: PatchedLayer) => boolean
|
|
35
|
+
): PatchedLayer | undefined => {
|
|
36
|
+
for (const layer of stack) {
|
|
37
|
+
if (predicate(layer)) {
|
|
38
|
+
return layer;
|
|
39
|
+
}
|
|
40
|
+
if (layer.handle?.stack) {
|
|
41
|
+
const nested = findLayer(layer.handle.stack, predicate);
|
|
42
|
+
if (nested) {
|
|
43
|
+
return nested;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (layer.route?.stack) {
|
|
47
|
+
const nested = findLayer(layer.route.stack, predicate);
|
|
48
|
+
if (nested) {
|
|
49
|
+
return nested;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const runMiddleware = (app: Application): void => {
|
|
57
|
+
const middlewareReq = {app} as unknown as Request;
|
|
58
|
+
const middlewareRes = {} as Response;
|
|
59
|
+
let called = false;
|
|
60
|
+
openApiCompatMiddleware(middlewareReq, middlewareRes, () => {
|
|
61
|
+
called = true;
|
|
62
|
+
});
|
|
63
|
+
if (!called) {
|
|
64
|
+
throw new Error("next() was not called by openApiCompatMiddleware");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
describe("openApiCompat", () => {
|
|
69
|
+
describe("patchAppUse", () => {
|
|
70
|
+
it("annotates layers added via app.use(mountPath, ...) with the mount path", () => {
|
|
71
|
+
const app = express();
|
|
72
|
+
patchAppUse(app);
|
|
73
|
+
|
|
74
|
+
const subRouter = express.Router();
|
|
75
|
+
subRouter.get("/list", (_req, res) => {
|
|
76
|
+
res.json({ok: true});
|
|
77
|
+
});
|
|
78
|
+
app.use("/sub", subRouter);
|
|
79
|
+
|
|
80
|
+
const stack = getRouterStack(app);
|
|
81
|
+
const mountedLayer = stack.find((layer) => layer.__openApiMountPath !== undefined) as
|
|
82
|
+
| PatchedLayer
|
|
83
|
+
| undefined;
|
|
84
|
+
expect(mountedLayer).toBeDefined();
|
|
85
|
+
expect(mountedLayer?.__openApiMountPath).toBe("/sub");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("strips trailing slashes from the recorded mount path", () => {
|
|
89
|
+
const app = express();
|
|
90
|
+
patchAppUse(app);
|
|
91
|
+
|
|
92
|
+
const subRouter = express.Router();
|
|
93
|
+
subRouter.get("/", (_req, res) => {
|
|
94
|
+
res.send("ok");
|
|
95
|
+
});
|
|
96
|
+
app.use("/api/v1///", subRouter);
|
|
97
|
+
|
|
98
|
+
const stack = getRouterStack(app);
|
|
99
|
+
const mountedLayer = stack.find((layer) => layer.__openApiMountPath !== undefined) as
|
|
100
|
+
| PatchedLayer
|
|
101
|
+
| undefined;
|
|
102
|
+
expect(mountedLayer?.__openApiMountPath).toBe("/api/v1");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("does not annotate layers when mount path is missing or '/'", () => {
|
|
106
|
+
const app = express();
|
|
107
|
+
patchAppUse(app);
|
|
108
|
+
|
|
109
|
+
app.use((_req, _res, next) => next());
|
|
110
|
+
|
|
111
|
+
const subRouter = express.Router();
|
|
112
|
+
subRouter.get("/x", (_req, res) => res.send("x"));
|
|
113
|
+
app.use("/", subRouter);
|
|
114
|
+
|
|
115
|
+
const stack = getRouterStack(app);
|
|
116
|
+
for (const layer of stack) {
|
|
117
|
+
expect(layer.__openApiMountPath).toBeUndefined();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns the underlying use() return value", () => {
|
|
122
|
+
const app = express();
|
|
123
|
+
patchAppUse(app);
|
|
124
|
+
const result = app.use((_req, _res, next) => next());
|
|
125
|
+
expect(result).toBe(app);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("openApiCompatMiddleware", () => {
|
|
130
|
+
it("calls next() and is a no-op when the app has no router yet", () => {
|
|
131
|
+
const fakeReq = {app: {}} as unknown as Request;
|
|
132
|
+
const fakeRes = {} as Response;
|
|
133
|
+
let nextCalled = false;
|
|
134
|
+
openApiCompatMiddleware(fakeReq, fakeRes, () => {
|
|
135
|
+
nextCalled = true;
|
|
136
|
+
});
|
|
137
|
+
expect(nextCalled).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("sets fast_slash regexp on layers that use Express 5 .slash", () => {
|
|
141
|
+
const app = express();
|
|
142
|
+
patchAppUse(app);
|
|
143
|
+
app.use((_req, _res, next) => next());
|
|
144
|
+
|
|
145
|
+
const stack = getRouterStack(app);
|
|
146
|
+
const slashLayer = stack.find((layer) => layer.slash === true) as PatchedLayer | undefined;
|
|
147
|
+
expect(slashLayer).toBeDefined();
|
|
148
|
+
|
|
149
|
+
runMiddleware(app);
|
|
150
|
+
|
|
151
|
+
const patchedRegexp = slashLayer?.regexp as {fast_slash?: boolean} | undefined;
|
|
152
|
+
expect(patchedRegexp?.fast_slash).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("builds a regexp for sub-routers mounted at a non-root path", () => {
|
|
156
|
+
const app = express();
|
|
157
|
+
patchAppUse(app);
|
|
158
|
+
|
|
159
|
+
const subRouter = express.Router();
|
|
160
|
+
subRouter.get("/foo", (_req, res) => res.send("foo"));
|
|
161
|
+
app.use("/sub", subRouter);
|
|
162
|
+
|
|
163
|
+
runMiddleware(app);
|
|
164
|
+
|
|
165
|
+
const stack = getRouterStack(app);
|
|
166
|
+
const subLayer = stack.find((layer) => layer.__openApiMountPath === "/sub") as
|
|
167
|
+
| PatchedLayer
|
|
168
|
+
| undefined;
|
|
169
|
+
expect(subLayer).toBeDefined();
|
|
170
|
+
const regexp = subLayer?.regexp as RegExp;
|
|
171
|
+
expect(regexp).toBeInstanceOf(RegExp);
|
|
172
|
+
expect(regexp.test("/sub/foo")).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("builds a regexp from layer.path and extracts :param keys", () => {
|
|
176
|
+
const app = express();
|
|
177
|
+
patchAppUse(app);
|
|
178
|
+
|
|
179
|
+
const stack = getRouterStack(app);
|
|
180
|
+
stack.push({
|
|
181
|
+
name: "boundDispatch",
|
|
182
|
+
path: "/users/:userId",
|
|
183
|
+
} as PatchedLayer);
|
|
184
|
+
|
|
185
|
+
runMiddleware(app);
|
|
186
|
+
|
|
187
|
+
const inserted = stack[stack.length - 1];
|
|
188
|
+
expect(inserted.regexp).toBeInstanceOf(RegExp);
|
|
189
|
+
expect((inserted.regexp as RegExp).test("/users/abc")).toBe(true);
|
|
190
|
+
expect(inserted.keys).toEqual([{name: "userId", optional: false}]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("builds a regexp from route.path for plain route layers", async () => {
|
|
194
|
+
const app = express();
|
|
195
|
+
patchAppUse(app);
|
|
196
|
+
app.get("/items/:itemId", (_req, res) => {
|
|
197
|
+
res.json({ok: true});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
runMiddleware(app);
|
|
201
|
+
|
|
202
|
+
const stack = getRouterStack(app);
|
|
203
|
+
const routeLayer = findLayer(stack, (layer) => layer.route?.path === "/items/:itemId");
|
|
204
|
+
expect(routeLayer).toBeDefined();
|
|
205
|
+
expect(routeLayer?.regexp).toBeInstanceOf(RegExp);
|
|
206
|
+
expect((routeLayer?.regexp as RegExp).test("/items/123")).toBe(true);
|
|
207
|
+
expect(routeLayer?.keys).toEqual([{name: "itemId", optional: false}]);
|
|
208
|
+
|
|
209
|
+
const res = await supertest(app).get("/items/123").expect(200);
|
|
210
|
+
expect(res.body).toEqual({ok: true});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("falls back to /^\\/?$/ when no path information is available", () => {
|
|
214
|
+
const app = express();
|
|
215
|
+
patchAppUse(app);
|
|
216
|
+
const stack = getRouterStack(app);
|
|
217
|
+
stack.push({name: "anonymous"} as PatchedLayer);
|
|
218
|
+
|
|
219
|
+
runMiddleware(app);
|
|
220
|
+
|
|
221
|
+
const inserted = stack[stack.length - 1];
|
|
222
|
+
expect(inserted.regexp).toBeInstanceOf(RegExp);
|
|
223
|
+
expect((inserted.regexp as RegExp).source).toBe("^\\/?$");
|
|
224
|
+
expect(inserted.keys).toEqual([]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("skips layers that already have a regexp set", () => {
|
|
228
|
+
const app = express();
|
|
229
|
+
patchAppUse(app);
|
|
230
|
+
const stack = getRouterStack(app);
|
|
231
|
+
const existing = /^prebuilt$/;
|
|
232
|
+
stack.push({
|
|
233
|
+
name: "preset",
|
|
234
|
+
path: "/should-be-ignored/:id",
|
|
235
|
+
regexp: existing,
|
|
236
|
+
} as PatchedLayer);
|
|
237
|
+
|
|
238
|
+
runMiddleware(app);
|
|
239
|
+
|
|
240
|
+
const inserted = stack[stack.length - 1];
|
|
241
|
+
expect(inserted.regexp).toBe(existing);
|
|
242
|
+
expect(inserted.keys).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("converts Express 5 string keys arrays into {name, optional} objects", () => {
|
|
246
|
+
const app = express();
|
|
247
|
+
patchAppUse(app);
|
|
248
|
+
const stack = getRouterStack(app);
|
|
249
|
+
stack.push({
|
|
250
|
+
keys: ["userId", "postId"],
|
|
251
|
+
name: "boundDispatch",
|
|
252
|
+
path: "/users/:userId/posts/:postId",
|
|
253
|
+
} as PatchedLayer);
|
|
254
|
+
|
|
255
|
+
runMiddleware(app);
|
|
256
|
+
|
|
257
|
+
const inserted = stack[stack.length - 1];
|
|
258
|
+
expect(inserted.keys).toEqual([
|
|
259
|
+
{name: "userId", optional: false},
|
|
260
|
+
{name: "postId", optional: false},
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("recurses into nested router stacks added via patchAppUse", () => {
|
|
265
|
+
const app = express();
|
|
266
|
+
patchAppUse(app);
|
|
267
|
+
|
|
268
|
+
const inner = express.Router();
|
|
269
|
+
inner.get("/widgets/:widgetId", (_req, res) => res.send("widget"));
|
|
270
|
+
app.use("/api", inner);
|
|
271
|
+
|
|
272
|
+
runMiddleware(app);
|
|
273
|
+
|
|
274
|
+
const stack = getRouterStack(app);
|
|
275
|
+
const widgetLayer = findLayer(stack, (layer) => layer.route?.path === "/widgets/:widgetId");
|
|
276
|
+
expect(widgetLayer).toBeDefined();
|
|
277
|
+
expect(widgetLayer?.regexp).toBeInstanceOf(RegExp);
|
|
278
|
+
expect((widgetLayer?.regexp as RegExp).test("/widgets/42")).toBe(true);
|
|
279
|
+
expect(widgetLayer?.keys).toEqual([{name: "widgetId", optional: false}]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("escapes regex metacharacters in static path segments", () => {
|
|
283
|
+
const app = express();
|
|
284
|
+
patchAppUse(app);
|
|
285
|
+
const stack = getRouterStack(app);
|
|
286
|
+
stack.push({
|
|
287
|
+
name: "boundDispatch",
|
|
288
|
+
path: "/foo.bar/baz",
|
|
289
|
+
} as PatchedLayer);
|
|
290
|
+
|
|
291
|
+
runMiddleware(app);
|
|
292
|
+
|
|
293
|
+
const inserted = stack[stack.length - 1];
|
|
294
|
+
const regexp = inserted.regexp as RegExp;
|
|
295
|
+
expect(regexp.test("/foo.bar/baz")).toBe(true);
|
|
296
|
+
expect(regexp.test("/fooXbar/baz")).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
package/src/githubAuth.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type express from "express";
|
|
2
|
+
import {Router} from "express";
|
|
2
3
|
import passport from "passport";
|
|
3
4
|
import {Strategy as GitHubStrategy, type Profile} from "passport-github2";
|
|
4
5
|
import {generateTokens, type UserModel} from "./auth";
|
|
@@ -213,7 +214,7 @@ export const addGitHubAuthRoutes = (
|
|
|
213
214
|
githubOptions: GitHubAuthOptions,
|
|
214
215
|
authOptions?: AuthOptions
|
|
215
216
|
): void => {
|
|
216
|
-
const router =
|
|
217
|
+
const router = Router();
|
|
217
218
|
|
|
218
219
|
// Initiate GitHub OAuth flow
|
|
219
220
|
router.get(
|
package/src/openApiCompat.ts
CHANGED
|
@@ -8,16 +8,38 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @see https://github.com/wesleytodd/express-openapi/issues/70
|
|
10
10
|
*/
|
|
11
|
+
import type {Application, NextFunction, Request, Response} from "express";
|
|
11
12
|
|
|
12
13
|
const MOUNT_PATH_KEY = "__openApiMountPath";
|
|
13
14
|
|
|
15
|
+
interface ExpressKey {
|
|
16
|
+
name: string;
|
|
17
|
+
optional: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PatchedLayer {
|
|
21
|
+
name?: string;
|
|
22
|
+
regexp?: {fast_slash?: boolean} | RegExp;
|
|
23
|
+
slash?: boolean;
|
|
24
|
+
path?: string;
|
|
25
|
+
route?: {path?: string; stack?: PatchedLayer[]};
|
|
26
|
+
handle?: {stack?: PatchedLayer[]};
|
|
27
|
+
keys?: ExpressKey[] | string[];
|
|
28
|
+
[MOUNT_PATH_KEY]?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AppWithRouter {
|
|
32
|
+
_router?: {stack: PatchedLayer[]};
|
|
33
|
+
router?: {stack: PatchedLayer[]};
|
|
34
|
+
}
|
|
35
|
+
|
|
14
36
|
/**
|
|
15
37
|
* Extract Express 4-style keys from a path string.
|
|
16
38
|
* Parses `:paramName` and `*paramName` segments into `{name, optional}` objects
|
|
17
39
|
* that @wesleytodd/openapi expects.
|
|
18
40
|
*/
|
|
19
|
-
const extractKeysFromPath = (path: string):
|
|
20
|
-
const keys:
|
|
41
|
+
const extractKeysFromPath = (path: string): ExpressKey[] => {
|
|
42
|
+
const keys: ExpressKey[] = [];
|
|
21
43
|
const paramRegex = /[:*](\w+)\??/g;
|
|
22
44
|
let match: RegExpExecArray | null;
|
|
23
45
|
// biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop
|
|
@@ -52,7 +74,7 @@ const buildRegexpForPath = (pathStr: string, isMount: boolean): RegExp => {
|
|
|
52
74
|
return new RegExp(`^${pattern}\\/?$`);
|
|
53
75
|
};
|
|
54
76
|
|
|
55
|
-
const patchRouterStack = (stack:
|
|
77
|
+
const patchRouterStack = (stack: PatchedLayer[]): void => {
|
|
56
78
|
for (const layer of stack) {
|
|
57
79
|
if (layer.regexp !== undefined) {
|
|
58
80
|
continue;
|
|
@@ -66,13 +88,13 @@ const patchRouterStack = (stack: any[]): void => {
|
|
|
66
88
|
// Express 5 layers use .slash instead of .regexp.fast_slash
|
|
67
89
|
layer.regexp = {fast_slash: true};
|
|
68
90
|
} else if (layer[MOUNT_PATH_KEY]) {
|
|
69
|
-
pathStr = layer[MOUNT_PATH_KEY]
|
|
91
|
+
pathStr = layer[MOUNT_PATH_KEY];
|
|
70
92
|
layer.regexp = buildRegexpForPath(pathStr, isMount);
|
|
71
93
|
} else if (layer.path && typeof layer.path === "string") {
|
|
72
|
-
pathStr = layer.path
|
|
94
|
+
pathStr = layer.path;
|
|
73
95
|
layer.regexp = buildRegexpForPath(pathStr, false);
|
|
74
96
|
} else if (layer.route?.path && typeof layer.route.path === "string") {
|
|
75
|
-
pathStr = layer.route.path
|
|
97
|
+
pathStr = layer.route.path;
|
|
76
98
|
layer.regexp = buildRegexpForPath(pathStr, false);
|
|
77
99
|
} else {
|
|
78
100
|
layer.regexp = /^\/?$/;
|
|
@@ -88,7 +110,7 @@ const patchRouterStack = (stack: any[]): void => {
|
|
|
88
110
|
}
|
|
89
111
|
} else if (Array.isArray(layer.keys) && typeof layer.keys[0] === "string") {
|
|
90
112
|
// Express 5 stores keys as plain strings after match() — convert to objects
|
|
91
|
-
layer.keys = layer.keys.map((k
|
|
113
|
+
layer.keys = (layer.keys as string[]).map((k) => ({name: k, optional: false}));
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
// Recursively patch nested stacks
|
|
@@ -108,17 +130,18 @@ const patchRouterStack = (stack: any[]): void => {
|
|
|
108
130
|
*
|
|
109
131
|
* Must be called before any routes are registered.
|
|
110
132
|
*/
|
|
111
|
-
export const patchAppUse = (app:
|
|
133
|
+
export const patchAppUse = (app: Application): void => {
|
|
134
|
+
const internal = app as unknown as AppWithRouter;
|
|
112
135
|
const originalUse = app.use.bind(app);
|
|
113
|
-
const patchedUse = (...args:
|
|
136
|
+
const patchedUse = (...args: unknown[]): unknown => {
|
|
114
137
|
// Track stack length before the call
|
|
115
|
-
const router =
|
|
138
|
+
const router = internal._router ?? internal.router;
|
|
116
139
|
const stackBefore = router?.stack?.length ?? 0;
|
|
117
140
|
|
|
118
|
-
const result = originalUse(...args);
|
|
141
|
+
const result = (originalUse as (...a: unknown[]) => unknown)(...args);
|
|
119
142
|
|
|
120
143
|
// After use(), check if new layers were added and annotate them
|
|
121
|
-
const routerAfter =
|
|
144
|
+
const routerAfter = internal._router ?? internal.router;
|
|
122
145
|
if (routerAfter?.stack) {
|
|
123
146
|
const stackAfter = routerAfter.stack.length;
|
|
124
147
|
// The first arg is the mount path if it's a string
|
|
@@ -132,15 +155,16 @@ export const patchAppUse = (app: any): void => {
|
|
|
132
155
|
|
|
133
156
|
return result;
|
|
134
157
|
};
|
|
135
|
-
app.use = patchedUse;
|
|
158
|
+
(app as unknown as {use: typeof patchedUse}).use = patchedUse;
|
|
136
159
|
};
|
|
137
160
|
|
|
138
161
|
/**
|
|
139
162
|
* Express middleware that patches the router stack before OpenAPI doc
|
|
140
163
|
* generation. Must be mounted before the openapi middleware.
|
|
141
164
|
*/
|
|
142
|
-
export const openApiCompatMiddleware = (req:
|
|
143
|
-
const
|
|
165
|
+
export const openApiCompatMiddleware = (req: Request, _res: Response, next: NextFunction): void => {
|
|
166
|
+
const internal = req.app as unknown as AppWithRouter;
|
|
167
|
+
const router = internal._router ?? internal.router;
|
|
144
168
|
if (router?.stack) {
|
|
145
169
|
patchRouterStack(router.stack);
|
|
146
170
|
}
|
package/src/permissions.ts
CHANGED
|
@@ -150,9 +150,9 @@ export function permissionMiddleware<T>(
|
|
|
150
150
|
let data;
|
|
151
151
|
try {
|
|
152
152
|
data = await populatedQuery.exec();
|
|
153
|
-
} catch (error:
|
|
153
|
+
} catch (error: unknown) {
|
|
154
154
|
throw new APIError({
|
|
155
|
-
error,
|
|
155
|
+
error: error as Error,
|
|
156
156
|
status: 500,
|
|
157
157
|
title: `GET failed on ${req.params.id}`,
|
|
158
158
|
});
|
package/src/transformers.ts
CHANGED
|
@@ -160,11 +160,12 @@ export async function defaultResponseHandler<T>(
|
|
|
160
160
|
}
|
|
161
161
|
try {
|
|
162
162
|
return serialize(request, options, doc);
|
|
163
|
-
} catch (error:
|
|
163
|
+
} catch (error: unknown) {
|
|
164
|
+
const errorObj = error as Error;
|
|
164
165
|
throw new APIError({
|
|
165
|
-
error,
|
|
166
|
+
error: errorObj,
|
|
166
167
|
status: 400,
|
|
167
|
-
title: `Error serializing ${method} response: ${
|
|
168
|
+
title: `Error serializing ${method} response: ${errorObj.message}`,
|
|
168
169
|
});
|
|
169
170
|
}
|
|
170
171
|
}
|