@tsed/platform-http 8.0.1 → 8.0.3
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/package.json +18 -16
- package/src/common/builder/PlatformBuilder.spec.ts +331 -0
- package/src/common/builder/PlatformBuilder.ts +426 -0
- package/src/common/builder/__fixtures__/PlatformCustom.ts +39 -0
- package/src/common/config/interfaces/PlatformJsonMapperSettings.ts +11 -0
- package/src/common/config/interfaces/PlatformMulterSettings.ts +21 -0
- package/src/common/config/interfaces/PlatformRouterSettings.ts +11 -0
- package/src/common/config/interfaces/PlatformStaticsSettings.ts +23 -0
- package/src/common/config/services/PlatformConfiguration.spec.ts +181 -0
- package/src/common/config/services/PlatformConfiguration.ts +157 -0
- package/src/common/constants/routerOptions.ts +1 -0
- package/src/common/decorators/PlatformProvider.ts +14 -0
- package/src/common/decorators/multer/multerFileSize.spec.ts +22 -0
- package/src/common/decorators/multer/multerFileSize.ts +32 -0
- package/src/common/decorators/multer/multerOptions.spec.ts +21 -0
- package/src/common/decorators/multer/multerOptions.ts +53 -0
- package/src/common/decorators/multer/multipartFile.spec.ts +259 -0
- package/src/common/decorators/multer/multipartFile.ts +88 -0
- package/src/common/decorators/params/error.spec.ts +15 -0
- package/src/common/decorators/params/error.ts +16 -0
- package/src/common/decorators/params/next.spec.ts +15 -0
- package/src/common/decorators/params/next.ts +31 -0
- package/src/common/decorators/params/request.spec.ts +49 -0
- package/src/common/decorators/params/request.ts +85 -0
- package/src/common/decorators/params/response.spec.ts +36 -0
- package/src/common/decorators/params/response.ts +73 -0
- package/src/common/domain/AnyToPromiseWithCtx.spec.ts +20 -0
- package/src/common/domain/AnyToPromiseWithCtx.ts +30 -0
- package/src/common/domain/EndpointMetadata.ts +1 -0
- package/src/common/domain/PlatformContext.spec.ts +124 -0
- package/src/common/domain/PlatformContext.ts +135 -0
- package/src/common/domain/PlatformRouteDetails.ts +7 -0
- package/src/common/exports.ts +4 -0
- package/src/common/fn/adapter.ts +23 -0
- package/src/common/fn/application.ts +11 -0
- package/src/common/index.ts +62 -0
- package/src/common/interfaces/AfterInit.ts +3 -0
- package/src/common/interfaces/AfterListen.ts +3 -0
- package/src/common/interfaces/AfterRoutesInit.ts +3 -0
- package/src/common/interfaces/BeforeInit.ts +3 -0
- package/src/common/interfaces/BeforeListen.ts +3 -0
- package/src/common/interfaces/BeforeRoutesInit.ts +3 -0
- package/src/common/interfaces/HandlerType.ts +12 -0
- package/src/common/interfaces/IncomingEvent.ts +6 -0
- package/src/common/interfaces/OnReady.ts +3 -0
- package/src/common/interfaces/OnRequest.ts +5 -0
- package/src/common/interfaces/OnResponse.ts +5 -0
- package/src/common/interfaces/OnRoutesInit.ts +3 -0
- package/src/common/interfaces/PlatformRouteOptions.ts +17 -0
- package/src/common/interfaces/ResponseErrorObject.ts +8 -0
- package/src/common/interfaces/Route.ts +12 -0
- package/src/common/middlewares/PlatformAcceptMimesMiddleware.spec.ts +215 -0
- package/src/common/middlewares/PlatformAcceptMimesMiddleware.ts +24 -0
- package/src/common/middlewares/PlatformMulterMiddleware.spec.ts +97 -0
- package/src/common/middlewares/PlatformMulterMiddleware.ts +59 -0
- package/src/common/services/Platform.spec.ts +145 -0
- package/src/common/services/Platform.ts +79 -0
- package/src/common/services/PlatformAdapter.ts +89 -0
- package/src/common/services/PlatformApplication.spec.ts +101 -0
- package/src/common/services/PlatformApplication.ts +54 -0
- package/src/common/services/PlatformHandler.spec.ts +152 -0
- package/src/common/services/PlatformHandler.ts +140 -0
- package/src/common/services/PlatformMiddlewaresChain.spec.ts +96 -0
- package/src/common/services/PlatformMiddlewaresChain.ts +55 -0
- package/src/common/services/PlatformRequest.spec.ts +185 -0
- package/src/common/services/PlatformRequest.ts +181 -0
- package/src/common/services/PlatformResponse.spec.ts +344 -0
- package/src/common/services/PlatformResponse.ts +388 -0
- package/src/common/utils/closeServer.spec.ts +11 -0
- package/src/common/utils/closeServer.ts +7 -0
- package/src/common/utils/createContext.spec.ts +111 -0
- package/src/common/utils/createContext.ts +52 -0
- package/src/common/utils/createHttpServer.spec.ts +74 -0
- package/src/common/utils/createHttpServer.ts +15 -0
- package/src/common/utils/createHttpsServer.spec.ts +73 -0
- package/src/common/utils/createHttpsServer.ts +16 -0
- package/src/common/utils/createInjector.spec.ts +42 -0
- package/src/common/utils/createInjector.ts +55 -0
- package/src/common/utils/createServer.ts +51 -0
- package/src/common/utils/ensureContext.spec.ts +41 -0
- package/src/common/utils/ensureContext.ts +17 -0
- package/src/common/utils/getConfiguration.spec.ts +163 -0
- package/src/common/utils/getConfiguration.ts +34 -0
- package/src/common/utils/getStaticsOptions.spec.ts +27 -0
- package/src/common/utils/getStaticsOptions.ts +24 -0
- package/src/common/utils/listenServer.ts +17 -0
- package/src/common/utils/mapReturnedResponse.spec.ts +35 -0
- package/src/common/utils/mapReturnedResponse.ts +10 -0
- package/src/common/utils/printRoutes.spec.ts +19 -0
- package/src/common/utils/printRoutes.ts +40 -0
- package/src/common/utils/resolveControllers.spec.ts +40 -0
- package/src/common/utils/resolveControllers.ts +54 -0
- package/src/common/utils/setResponseHeaders.spec.ts +84 -0
- package/src/common/utils/setResponseHeaders.ts +31 -0
- package/src/index.ts +2 -0
- package/src/testing/FakeAdapter.ts +69 -0
- package/src/testing/FakeResponse.ts +72 -0
- package/src/testing/PlatformTest.spec.ts +53 -0
- package/src/testing/PlatformTest.ts +157 -0
- package/src/testing/index.ts +5 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {Type} from "@tsed/core";
|
|
2
|
+
import {injectable, ProviderOpts} from "@tsed/di";
|
|
3
|
+
import {PlatformHandlerMetadata, PlatformLayer} from "@tsed/platform-router";
|
|
4
|
+
import {IncomingMessage, ServerResponse} from "http";
|
|
5
|
+
|
|
6
|
+
import {PlatformMulter, PlatformMulterSettings} from "../config/interfaces/PlatformMulterSettings.js";
|
|
7
|
+
import {PlatformStaticsOptions} from "../config/interfaces/PlatformStaticsSettings.js";
|
|
8
|
+
import {application} from "../fn/application.js";
|
|
9
|
+
import {createHttpServer} from "../utils/createHttpServer.js";
|
|
10
|
+
import {createHttpsServer} from "../utils/createHttpsServer.js";
|
|
11
|
+
import {CreateServerReturn} from "../utils/createServer.js";
|
|
12
|
+
import type {PlatformApplication} from "./PlatformApplication.js";
|
|
13
|
+
|
|
14
|
+
export abstract class PlatformAdapter<App = TsED.Application> {
|
|
15
|
+
abstract readonly NAME: string;
|
|
16
|
+
/**
|
|
17
|
+
* Load providers in top priority
|
|
18
|
+
*/
|
|
19
|
+
providers: ProviderOpts[] = [];
|
|
20
|
+
|
|
21
|
+
get app(): PlatformApplication<App> {
|
|
22
|
+
return application();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getServers(): CreateServerReturn[] {
|
|
26
|
+
const app = application<App>();
|
|
27
|
+
return [createHttpServer(app.callback()), createHttpsServer(app.callback())].filter(Boolean) as any[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onInit(): Promise<void> | void {
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
beforeLoadRoutes(): Promise<void> | void {
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
afterLoadRoutes(): Promise<void> | void {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* create initial context
|
|
44
|
+
*/
|
|
45
|
+
abstract useContext(): any;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Map router layer to the targeted framework
|
|
49
|
+
*/
|
|
50
|
+
abstract mapLayers(layer: PlatformLayer[]): void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Map handler to the targeted framework
|
|
54
|
+
*/
|
|
55
|
+
mapHandler(handler: Function, layer: PlatformHandlerMetadata): Function {
|
|
56
|
+
return handler;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return the app instance
|
|
61
|
+
*/
|
|
62
|
+
abstract createApp(): {app: App; callback(): (req: IncomingMessage, res: ServerResponse) => void};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Return the statics middlewares
|
|
66
|
+
* @param endpoint
|
|
67
|
+
* @param options
|
|
68
|
+
*/
|
|
69
|
+
abstract statics(endpoint: string, options: PlatformStaticsOptions): any;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Return the multipart middleware
|
|
73
|
+
* @param options
|
|
74
|
+
*/
|
|
75
|
+
abstract multipart(options: PlatformMulterSettings): PlatformMulter;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Return the body parser for the given
|
|
79
|
+
* @param type
|
|
80
|
+
* @param opts
|
|
81
|
+
*/
|
|
82
|
+
abstract bodyParser(type: string, opts?: Record<string, any>): any;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PlatformBuilderSettings<App = TsED.Application> extends Partial<TsED.Configuration> {
|
|
86
|
+
adapter?: Type<PlatformAdapter<App>>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
injectable(PlatformAdapter).alias("PlatformAdapter");
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {configuration} from "@tsed/di";
|
|
2
|
+
|
|
3
|
+
import {PlatformTest} from "../../testing/PlatformTest.js";
|
|
4
|
+
import {createContext} from "../utils/createContext.js";
|
|
5
|
+
import {PlatformApplication} from "./PlatformApplication.js";
|
|
6
|
+
import {PlatformHandler} from "./PlatformHandler.js";
|
|
7
|
+
|
|
8
|
+
vi.mock("../utils/createContext");
|
|
9
|
+
|
|
10
|
+
function createDriver() {
|
|
11
|
+
return {
|
|
12
|
+
use: vi.fn(),
|
|
13
|
+
all: vi.fn(),
|
|
14
|
+
get: vi.fn(),
|
|
15
|
+
post: vi.fn(),
|
|
16
|
+
put: vi.fn(),
|
|
17
|
+
delete: vi.fn(),
|
|
18
|
+
patch: vi.fn(),
|
|
19
|
+
head: vi.fn(),
|
|
20
|
+
options: vi.fn()
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getPlatformApp() {
|
|
25
|
+
const platformHandler = {
|
|
26
|
+
createHandler: vi.fn().mockImplementation((o) => o)
|
|
27
|
+
};
|
|
28
|
+
const platformApp = await PlatformTest.invoke<PlatformApplication<any>>(PlatformApplication, [
|
|
29
|
+
{
|
|
30
|
+
token: PlatformHandler,
|
|
31
|
+
use: platformHandler
|
|
32
|
+
}
|
|
33
|
+
]);
|
|
34
|
+
configuration().logger = {};
|
|
35
|
+
platformApp.rawApp = createDriver() as any;
|
|
36
|
+
vi.spyOn(platformApp, "multer").mockReturnValue({} as never);
|
|
37
|
+
|
|
38
|
+
return {platformApp, platformHandler};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("PlatformApplication", () => {
|
|
42
|
+
beforeEach(PlatformTest.create);
|
|
43
|
+
afterEach(PlatformTest.reset);
|
|
44
|
+
|
|
45
|
+
describe("getApp()", () => {
|
|
46
|
+
it("should return app", async () => {
|
|
47
|
+
// GIVEN
|
|
48
|
+
const {platformApp} = await getPlatformApp();
|
|
49
|
+
|
|
50
|
+
// WHEN
|
|
51
|
+
expect(platformApp.getApp()).toEqual(platformApp.rawApp);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("statics()", () => {
|
|
56
|
+
it("should call statics", async () => {
|
|
57
|
+
// GIVEN
|
|
58
|
+
const {platformApp} = await getPlatformApp();
|
|
59
|
+
|
|
60
|
+
vi.spyOn(console, "warn");
|
|
61
|
+
|
|
62
|
+
// WHEN
|
|
63
|
+
platformApp.statics("/", {root: "/root"});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe("multer()", () => {
|
|
67
|
+
it("should call statics", async () => {
|
|
68
|
+
// GIVEN
|
|
69
|
+
const {platformApp} = await getPlatformApp();
|
|
70
|
+
|
|
71
|
+
vi.spyOn(console, "warn");
|
|
72
|
+
|
|
73
|
+
// WHEN
|
|
74
|
+
platformApp.multer({});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("callback()", () => {
|
|
79
|
+
it("should return the callback", async () => {
|
|
80
|
+
const $ctx = {
|
|
81
|
+
runInContext: vi.fn().mockImplementation((cb) => {
|
|
82
|
+
cb();
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const {platformApp} = await getPlatformApp();
|
|
87
|
+
|
|
88
|
+
(createContext as any).mockReturnValue(() => Promise.resolve($ctx));
|
|
89
|
+
|
|
90
|
+
// WHEN
|
|
91
|
+
const callback = vi.fn();
|
|
92
|
+
|
|
93
|
+
vi.spyOn(platformApp, "rawCallback").mockReturnValue(callback);
|
|
94
|
+
|
|
95
|
+
await platformApp.callback({} as any, {} as any);
|
|
96
|
+
|
|
97
|
+
expect(platformApp.rawCallback).toHaveBeenCalledWith();
|
|
98
|
+
expect(callback).toHaveBeenCalledWith({}, {});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {inject, injectable, ProviderScope} from "@tsed/di";
|
|
2
|
+
import {PlatformRouter} from "@tsed/platform-router";
|
|
3
|
+
import {IncomingMessage, ServerResponse} from "http";
|
|
4
|
+
|
|
5
|
+
import {PlatformMulterSettings} from "../config/interfaces/PlatformMulterSettings.js";
|
|
6
|
+
import {PlatformAdapter} from "./PlatformAdapter.js";
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
namespace TsED {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
export interface Application {}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `PlatformApplication` is used to provide all routes collected by annotation `@Controller`.
|
|
17
|
+
*
|
|
18
|
+
* @platform
|
|
19
|
+
*/
|
|
20
|
+
export class PlatformApplication<App = TsED.Application> extends PlatformRouter {
|
|
21
|
+
adapter: PlatformAdapter<App> = inject(PlatformAdapter<App>);
|
|
22
|
+
|
|
23
|
+
rawApp: App;
|
|
24
|
+
rawCallback: () => any;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
const {app, callback} = this.adapter.createApp();
|
|
30
|
+
|
|
31
|
+
this.rawApp = app;
|
|
32
|
+
this.rawCallback = callback;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getApp(): App {
|
|
36
|
+
return this.rawApp;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
multer(options: PlatformMulterSettings) {
|
|
40
|
+
return this.adapter.multipart(options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
callback(): (req: IncomingMessage, res: ServerResponse) => any;
|
|
44
|
+
callback(req: IncomingMessage, res: ServerResponse): any;
|
|
45
|
+
callback(req?: IncomingMessage, res?: ServerResponse) {
|
|
46
|
+
if (req && res) {
|
|
47
|
+
return this.callback()(req, res);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.rawCallback();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
injectable(PlatformApplication).scope(ProviderScope.SINGLETON).alias("PlatformApplication");
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {Injectable, runInContext} from "@tsed/di";
|
|
2
|
+
import {PlatformHandlerMetadata, PlatformHandlerType} from "@tsed/platform-router";
|
|
3
|
+
import {EndpointMetadata, Get, View} from "@tsed/schema";
|
|
4
|
+
|
|
5
|
+
import {PlatformTest} from "../../testing/PlatformTest.js";
|
|
6
|
+
import {PlatformHandler} from "./PlatformHandler.js";
|
|
7
|
+
|
|
8
|
+
function getServiceFixture() {
|
|
9
|
+
const service = PlatformTest.get<PlatformHandler>(PlatformHandler);
|
|
10
|
+
return {service};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("PlatformHandler", () => {
|
|
14
|
+
beforeEach(() => PlatformTest.create());
|
|
15
|
+
afterEach(() => PlatformTest.create());
|
|
16
|
+
|
|
17
|
+
describe("createHandler()", () => {
|
|
18
|
+
it("should create handler", async () => {
|
|
19
|
+
const {service} = getServiceFixture();
|
|
20
|
+
const handler = vi.fn();
|
|
21
|
+
const metadata = new PlatformHandlerMetadata({
|
|
22
|
+
handler,
|
|
23
|
+
type: PlatformHandlerType.CTX_FN
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
27
|
+
|
|
28
|
+
const result = service.createHandler(metadata);
|
|
29
|
+
|
|
30
|
+
expect(typeof result).toEqual("function");
|
|
31
|
+
|
|
32
|
+
await result($ctx);
|
|
33
|
+
|
|
34
|
+
expect(handler).toHaveBeenCalledWith($ctx);
|
|
35
|
+
});
|
|
36
|
+
it("should create handler from provider", async () => {
|
|
37
|
+
const {service} = getServiceFixture();
|
|
38
|
+
|
|
39
|
+
@Injectable()
|
|
40
|
+
class Test {
|
|
41
|
+
@Get("/")
|
|
42
|
+
get() {
|
|
43
|
+
return "test";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
PlatformTest.injector.addProvider(Test);
|
|
48
|
+
|
|
49
|
+
const metadata = new PlatformHandlerMetadata({
|
|
50
|
+
provider: PlatformTest.injector.getProvider(Test),
|
|
51
|
+
propertyKey: "get",
|
|
52
|
+
type: PlatformHandlerType.ENDPOINT
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
56
|
+
$ctx.endpoint = EndpointMetadata.get(Test, "get");
|
|
57
|
+
|
|
58
|
+
const result = service.createHandler(metadata);
|
|
59
|
+
|
|
60
|
+
expect(typeof result).toEqual("function");
|
|
61
|
+
|
|
62
|
+
await result($ctx);
|
|
63
|
+
|
|
64
|
+
expect($ctx.data).toEqual("test");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("createCustomHandler()", () => {
|
|
68
|
+
it("should create handler", async () => {
|
|
69
|
+
@Injectable()
|
|
70
|
+
class TestService {
|
|
71
|
+
@View("test")
|
|
72
|
+
use() {
|
|
73
|
+
return Promise.resolve("hello");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const {service} = getServiceFixture();
|
|
78
|
+
|
|
79
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
80
|
+
const testService = await PlatformTest.invoke<TestService>(TestService)!;
|
|
81
|
+
const provider = PlatformTest.injector.getProvider(TestService)!;
|
|
82
|
+
|
|
83
|
+
vi.spyOn(testService, "use").mockResolvedValue("hello");
|
|
84
|
+
|
|
85
|
+
const result = service.createCustomHandler(provider, "use");
|
|
86
|
+
|
|
87
|
+
expect(result).toBeInstanceOf(Function);
|
|
88
|
+
|
|
89
|
+
await runInContext($ctx, () => result($ctx));
|
|
90
|
+
|
|
91
|
+
expect($ctx.data).toEqual("hello");
|
|
92
|
+
expect(testService.use).toHaveBeenCalledWith($ctx);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("onRequest()", () => {
|
|
96
|
+
it("should do nothing when the request is done", async () => {
|
|
97
|
+
const {service} = getServiceFixture();
|
|
98
|
+
const handler = vi.fn();
|
|
99
|
+
|
|
100
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
101
|
+
$ctx.next = vi.fn();
|
|
102
|
+
|
|
103
|
+
await $ctx.finish();
|
|
104
|
+
|
|
105
|
+
$ctx.handlerMetadata = new PlatformHandlerMetadata({
|
|
106
|
+
handler
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await service.onRequest(handler, $ctx);
|
|
110
|
+
|
|
111
|
+
expect(handler).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
it("should apply headers and status", async () => {
|
|
114
|
+
@Injectable()
|
|
115
|
+
class TestService {
|
|
116
|
+
@Get("/")
|
|
117
|
+
use() {
|
|
118
|
+
return Promise.resolve("hello");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const {service} = getServiceFixture();
|
|
123
|
+
const handler = vi.fn().mockResolvedValue({
|
|
124
|
+
status: 201,
|
|
125
|
+
statusText: "CREATED",
|
|
126
|
+
headers: {
|
|
127
|
+
test: "test"
|
|
128
|
+
},
|
|
129
|
+
data: "data"
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
133
|
+
$ctx.next = vi.fn();
|
|
134
|
+
$ctx.endpoint = EndpointMetadata.get(TestService, "use");
|
|
135
|
+
|
|
136
|
+
$ctx.handlerMetadata = new PlatformHandlerMetadata({
|
|
137
|
+
handler: TestService.prototype.use
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
vi.spyOn(service, "flush").mockResolvedValue();
|
|
141
|
+
|
|
142
|
+
await service.onRequest(handler, $ctx);
|
|
143
|
+
|
|
144
|
+
expect(handler).toHaveBeenCalled();
|
|
145
|
+
expect($ctx.response.statusCode).toEqual(201);
|
|
146
|
+
expect($ctx.response.getHeaders()).toEqual({
|
|
147
|
+
test: "test",
|
|
148
|
+
"x-request-id": "id"
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {AnyPromiseResult, AnyToPromiseStatus, catchAsyncError} from "@tsed/core";
|
|
2
|
+
import {inject, injectable, Provider, ProviderScope} from "@tsed/di";
|
|
3
|
+
import {PlatformExceptions} from "@tsed/platform-exceptions";
|
|
4
|
+
import {PlatformParams, PlatformParamsCallback} from "@tsed/platform-params";
|
|
5
|
+
import {PlatformResponseFilter} from "@tsed/platform-response-filter";
|
|
6
|
+
import {
|
|
7
|
+
AlterEndpointHandlersArg,
|
|
8
|
+
PlatformHandlerMetadata,
|
|
9
|
+
PlatformHandlerType,
|
|
10
|
+
PlatformRouters,
|
|
11
|
+
useResponseHandler
|
|
12
|
+
} from "@tsed/platform-router";
|
|
13
|
+
import {JsonOperationRoute} from "@tsed/schema";
|
|
14
|
+
|
|
15
|
+
import {AnyToPromiseWithCtx} from "../domain/AnyToPromiseWithCtx.js";
|
|
16
|
+
import {PlatformContext} from "../domain/PlatformContext.js";
|
|
17
|
+
import {setResponseHeaders} from "../utils/setResponseHeaders.js";
|
|
18
|
+
import {PlatformApplication} from "./PlatformApplication.js";
|
|
19
|
+
import {PlatformMiddlewaresChain} from "./PlatformMiddlewaresChain.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Platform Handler abstraction layer. Wrap original class method to a pure platform handler (Express, Koa, etc...).
|
|
23
|
+
* @platform
|
|
24
|
+
*/
|
|
25
|
+
export class PlatformHandler {
|
|
26
|
+
protected responseFilter = inject(PlatformResponseFilter);
|
|
27
|
+
protected platformParams = inject(PlatformParams);
|
|
28
|
+
protected platformExceptions = inject(PlatformExceptions);
|
|
29
|
+
protected platformApplication = inject(PlatformApplication);
|
|
30
|
+
protected platformMiddlewaresChain = inject(PlatformMiddlewaresChain);
|
|
31
|
+
protected platformRouters = inject(PlatformRouters);
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
// configure the router module
|
|
35
|
+
this.platformRouters.hooks
|
|
36
|
+
.on("alterEndpointHandlers", (handlers: AlterEndpointHandlersArg, operationRoute: JsonOperationRoute) => {
|
|
37
|
+
handlers = this.platformMiddlewaresChain.get(handlers, operationRoute);
|
|
38
|
+
|
|
39
|
+
handlers.after.push(useResponseHandler(this.flush.bind(this)));
|
|
40
|
+
|
|
41
|
+
return handlers;
|
|
42
|
+
})
|
|
43
|
+
.on("alterHandler", (handlerMetadata: PlatformHandlerMetadata) => {
|
|
44
|
+
const handler = handlerMetadata.isInjectable() ? this.createHandler(handlerMetadata) : handlerMetadata.handler;
|
|
45
|
+
|
|
46
|
+
return this.platformApplication.adapter.mapHandler(handler, handlerMetadata);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
createHandler(handlerMetadata: PlatformHandlerMetadata): any {
|
|
51
|
+
const handler = this.platformParams.compileHandler(handlerMetadata);
|
|
52
|
+
|
|
53
|
+
return ($ctx: PlatformContext) => {
|
|
54
|
+
$ctx.handlerMetadata = handlerMetadata;
|
|
55
|
+
|
|
56
|
+
return this.onRequest(handler, $ctx);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param provider
|
|
62
|
+
* @param propertyKey
|
|
63
|
+
*/
|
|
64
|
+
createCustomHandler(provider: Provider, propertyKey: string) {
|
|
65
|
+
const metadata = new PlatformHandlerMetadata({
|
|
66
|
+
provider,
|
|
67
|
+
type: PlatformHandlerType.CUSTOM,
|
|
68
|
+
propertyKey
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return this.createHandler(metadata);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Call handler when a request his handle
|
|
76
|
+
*/
|
|
77
|
+
async onRequest(handler: PlatformParamsCallback, $ctx: PlatformContext) {
|
|
78
|
+
const {handlerMetadata} = $ctx;
|
|
79
|
+
|
|
80
|
+
if (handlerMetadata.type === PlatformHandlerType.CTX_FN) {
|
|
81
|
+
return handler({$ctx});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const resolver = new AnyToPromiseWithCtx($ctx);
|
|
85
|
+
|
|
86
|
+
const response = await resolver.call(handler);
|
|
87
|
+
// Note: restore previous handler metadata (for OIDC)
|
|
88
|
+
$ctx.handlerMetadata = handlerMetadata;
|
|
89
|
+
|
|
90
|
+
if (response.state === AnyToPromiseStatus.RESOLVED && !$ctx.isDone()) {
|
|
91
|
+
return this.onResponse(response, $ctx);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onResponse({status, data, headers}: AnyPromiseResult, $ctx: PlatformContext) {
|
|
96
|
+
if (status) {
|
|
97
|
+
$ctx.response.status(status);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (headers) {
|
|
101
|
+
$ctx.response.setHeaders(headers);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (data !== undefined) {
|
|
105
|
+
$ctx.data = data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$ctx.error = null;
|
|
109
|
+
|
|
110
|
+
// set headers each times that an endpoint is called
|
|
111
|
+
if ($ctx.handlerMetadata.isEndpoint()) {
|
|
112
|
+
setResponseHeaders($ctx);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Send the response to the consumer.
|
|
118
|
+
* @protected
|
|
119
|
+
* @param $ctx
|
|
120
|
+
*/
|
|
121
|
+
async flush($ctx: PlatformContext) {
|
|
122
|
+
if (!$ctx.error) {
|
|
123
|
+
$ctx.error = await catchAsyncError(async () => {
|
|
124
|
+
const {response} = $ctx;
|
|
125
|
+
|
|
126
|
+
if (!$ctx.isDone()) {
|
|
127
|
+
let data = await this.responseFilter.serialize($ctx.data, $ctx as any);
|
|
128
|
+
data = await this.responseFilter.transform(data, $ctx as any);
|
|
129
|
+
response.body(data);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ($ctx.error) {
|
|
135
|
+
this.platformExceptions.catch($ctx.error, $ctx);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
injectable(PlatformHandler).scope(ProviderScope.SINGLETON);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {Controller, ControllerProvider} from "@tsed/di";
|
|
2
|
+
import {Middleware, Use, UseAuth} from "@tsed/platform-middlewares";
|
|
3
|
+
import {AcceptMime, All, EndpointMetadata, Get, getOperationsRoutes, Post} from "@tsed/schema";
|
|
4
|
+
|
|
5
|
+
import {PlatformTest} from "../../testing/PlatformTest.js";
|
|
6
|
+
import {MultipartFile} from "../decorators/multer/multipartFile.js";
|
|
7
|
+
import {PlatformAcceptMimesMiddleware} from "../middlewares/PlatformAcceptMimesMiddleware.js";
|
|
8
|
+
import {PlatformMulterMiddleware} from "../middlewares/PlatformMulterMiddleware.js";
|
|
9
|
+
import {PlatformMiddlewaresChain} from "./PlatformMiddlewaresChain.js";
|
|
10
|
+
|
|
11
|
+
@Middleware()
|
|
12
|
+
class MyAuthMiddleware {
|
|
13
|
+
use() {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Middleware({
|
|
17
|
+
priority: -1
|
|
18
|
+
})
|
|
19
|
+
class TokenMiddleware {
|
|
20
|
+
use() {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Controller("/")
|
|
24
|
+
class TestCtrl {
|
|
25
|
+
@All("/")
|
|
26
|
+
allMethod() {}
|
|
27
|
+
|
|
28
|
+
@Get("/")
|
|
29
|
+
getMethod() {}
|
|
30
|
+
|
|
31
|
+
@Use("/")
|
|
32
|
+
use() {}
|
|
33
|
+
|
|
34
|
+
@Use()
|
|
35
|
+
use2() {}
|
|
36
|
+
|
|
37
|
+
@Post("/file")
|
|
38
|
+
@UseAuth(MyAuthMiddleware, {scope: "admin"})
|
|
39
|
+
@Use(TokenMiddleware)
|
|
40
|
+
@AcceptMime("application/json")
|
|
41
|
+
authEndpoint(@MultipartFile("file") file: MultipartFile) {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parentControllerBefore() {}
|
|
45
|
+
|
|
46
|
+
function getFixture() {
|
|
47
|
+
const provider = PlatformTest.injector.getProvider<ControllerProvider>(TestCtrl)!;
|
|
48
|
+
const platformMiddlewaresChain = PlatformTest.get<PlatformMiddlewaresChain>(PlatformMiddlewaresChain);
|
|
49
|
+
const operationRoutes = getOperationsRoutes<EndpointMetadata>(provider.token);
|
|
50
|
+
return {platformMiddlewaresChain, operationRoutes, provider};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("PlatformMiddlewaresChain", () => {
|
|
54
|
+
beforeEach(() => PlatformTest.create());
|
|
55
|
+
afterEach(() => PlatformTest.reset());
|
|
56
|
+
|
|
57
|
+
it("should return the middlewares for a given endpoint (getMethod)", () => {
|
|
58
|
+
const {platformMiddlewaresChain, operationRoutes} = getFixture();
|
|
59
|
+
const parentsMiddlewares = {
|
|
60
|
+
before: [parentControllerBefore]
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const chain = platformMiddlewaresChain.get(parentsMiddlewares as any, operationRoutes[1]);
|
|
64
|
+
|
|
65
|
+
expect(chain.before[0]).toEqual(parentControllerBefore);
|
|
66
|
+
});
|
|
67
|
+
it("should return the middlewares for a given endpoint (allMethod)", () => {
|
|
68
|
+
const {platformMiddlewaresChain, operationRoutes} = getFixture();
|
|
69
|
+
|
|
70
|
+
const parentsMiddlewares: any = {
|
|
71
|
+
before: [parentControllerBefore]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const chain = platformMiddlewaresChain.get(parentsMiddlewares, operationRoutes[1]);
|
|
75
|
+
|
|
76
|
+
expect(chain.before[0]).toEqual(parentControllerBefore);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should merge middlewares and sort middleware by priority", () => {
|
|
80
|
+
const {platformMiddlewaresChain, operationRoutes} = getFixture();
|
|
81
|
+
|
|
82
|
+
const parentsMiddlewares: any = {
|
|
83
|
+
before: [parentControllerBefore, MyAuthMiddleware, TokenMiddleware]
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const chain = platformMiddlewaresChain.get(parentsMiddlewares, operationRoutes[3]);
|
|
87
|
+
|
|
88
|
+
expect(chain.before).toEqual([
|
|
89
|
+
PlatformAcceptMimesMiddleware,
|
|
90
|
+
parentControllerBefore,
|
|
91
|
+
MyAuthMiddleware,
|
|
92
|
+
TokenMiddleware,
|
|
93
|
+
PlatformMulterMiddleware
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {isClass} from "@tsed/core";
|
|
2
|
+
import {constant, inject, injectable} from "@tsed/di";
|
|
3
|
+
import {ParamTypes} from "@tsed/platform-params";
|
|
4
|
+
import {AlterEndpointHandlersArg} from "@tsed/platform-router";
|
|
5
|
+
import {JsonEntityStore, JsonOperationRoute} from "@tsed/schema";
|
|
6
|
+
|
|
7
|
+
import {PlatformAcceptMimesMiddleware} from "../middlewares/PlatformAcceptMimesMiddleware.js";
|
|
8
|
+
import {PlatformMulterMiddleware} from "../middlewares/PlatformMulterMiddleware.js";
|
|
9
|
+
import {PlatformAdapter} from "./PlatformAdapter.js";
|
|
10
|
+
|
|
11
|
+
export class PlatformMiddlewaresChain {
|
|
12
|
+
protected acceptMimes = constant<string[]>("acceptMimes", []);
|
|
13
|
+
protected adapter = inject(PlatformAdapter);
|
|
14
|
+
|
|
15
|
+
get(handlers: AlterEndpointHandlersArg, operationRoute: JsonOperationRoute): AlterEndpointHandlersArg {
|
|
16
|
+
const {ACCEPT_MIMES, FILE} = this.getParamTypes(handlers, operationRoute);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
...handlers,
|
|
20
|
+
before: [ACCEPT_MIMES && PlatformAcceptMimesMiddleware, ...handlers.before, FILE && PlatformMulterMiddleware].filter(Boolean) as any[]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected hasAcceptMimes(operationRoute: JsonOperationRoute) {
|
|
25
|
+
return operationRoute.endpoint.acceptMimes.length || this.acceptMimes.length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected getParamTypes(middlewares: AlterEndpointHandlersArg, operationRoute: JsonOperationRoute) {
|
|
29
|
+
return middlewares.before
|
|
30
|
+
.concat(middlewares.after)
|
|
31
|
+
.filter(isClass)
|
|
32
|
+
.reduce(
|
|
33
|
+
(paramTypes, token) => {
|
|
34
|
+
const entity = JsonEntityStore.fromMethod(token, "use");
|
|
35
|
+
|
|
36
|
+
if (entity.decoratorType === "method") {
|
|
37
|
+
const {FILE, RAW_BODY, BODY} = entity.getParamTypes();
|
|
38
|
+
paramTypes.FILE = paramTypes.FILE || FILE;
|
|
39
|
+
paramTypes.RAW_BODY = paramTypes.RAW_BODY || RAW_BODY;
|
|
40
|
+
paramTypes.BODY = paramTypes.BODY || BODY;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return paramTypes;
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
ACCEPT_MIMES: this.hasAcceptMimes(operationRoute),
|
|
47
|
+
FILE: operationRoute.has(ParamTypes.FILES),
|
|
48
|
+
RAW_BODY: operationRoute.has(ParamTypes.RAW_BODY),
|
|
49
|
+
BODY: operationRoute.has(ParamTypes.BODY) || operationRoute.method === "ALL"
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
injectable(PlatformMiddlewaresChain);
|