Skip to content

Commit 54b436b

Browse files
committed
feat(nestjs): add interceptor support (#25)
1 parent 3486a42 commit 54b436b

File tree

4 files changed

+69
-5
lines changed

4 files changed

+69
-5
lines changed

packages/adapter/adapter-nestjs/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@unioc/decorator": "workspace:*",
5959
"@unioc/meta": "workspace:*",
6060
"@unioc/shared": "workspace:*",
61-
"@unioc/web": "workspace:*"
61+
"@unioc/web": "workspace:*",
62+
"rxjs": "^7.8.2"
6263
}
6364
}

packages/adapter/adapter-nestjs/src/restful/restful-handler.ts

+47-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type { INestJSMethodParamMetadata, NestJSMethodWrapper } from './method-w
55
import type { NestJSRestfulScanner } from './restful-scanner'
66
import { UnauthorizedException } from '@nestjs/common'
77
import { isClass } from '@unioc/shared'
8+
import { Observable } from 'rxjs'
89
import { ExecutionContextBuilder } from '../execution-context-builder'
10+
import { toResult } from '../utils'
911
import { EndingHandler } from './ending-handler'
1012
import { NestJSMethodOperator } from './method-operator'
1113

@@ -156,6 +158,34 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
156158
throw new UnauthorizedException()
157159
}
158160

161+
protected async executeInterceptors(methodWrapper: NestJSMethodWrapper, params: unknown[], observable: Observable<unknown>): Promise<Observable<unknown>> {
162+
const controllerTarget = methodWrapper
163+
.getControllerOperator()
164+
.getControllerWrapper()
165+
.getClassWrapper()
166+
.getTarget()
167+
168+
const resolvedInterceptors = await methodWrapper.getControllerOperator().getControllerWrapper().getRestfulScanner().mergeAndResolveToInstance(
169+
methodWrapper.getMethodInterceptors(),
170+
methodWrapper.getControllerOperator().getControllerInterceptors(),
171+
)
172+
173+
for (const interceptor of resolvedInterceptors) {
174+
observable = await interceptor.intercept(
175+
new ExecutionContextBuilder(
176+
params,
177+
'http',
178+
controllerTarget,
179+
controllerTarget.prototype[methodWrapper.getPropertyKey()],
180+
),
181+
{
182+
handle: () => observable,
183+
},
184+
)
185+
}
186+
return observable
187+
}
188+
159189
async initialize(ctx: IRestfulConnect.InitializeContext): Promise<void> {
160190
const map = await this.getRestfulScanner()
161191
.getMiddlewareResolver()
@@ -195,10 +225,23 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
195225
methodArguments = await this.buildParams(methodWrapper, ctx)
196226
// 2. Execute guards
197227
await this.executeGuards(methodWrapper, methodArguments)
198-
// 3. Execute the controller method
199-
const result = await methodWrapper.execute(methodArguments, extraOptions)
200-
// 4. TODO: Execute the interceptors
201-
// 5. Send the ending response
228+
// 3. Execute the controller method in an observable
229+
const responseObservable = new Observable((subscribe) => {
230+
methodWrapper.execute(methodArguments, extraOptions)
231+
.then((result) => {
232+
if (result.type === 'result')
233+
subscribe.next(result.value)
234+
else
235+
subscribe.error(result.error)
236+
subscribe.complete()
237+
})
238+
.catch(error => subscribe.error(error))
239+
})
240+
// 4. Execute the before interceptors
241+
const interceptedObservable = await this.executeInterceptors(methodWrapper, methodArguments, responseObservable)
242+
// 5. Get the result from the observable
243+
const result = await toResult(interceptedObservable)
244+
// 6. Send the ending response if not sent
202245
await this._endingHandler.sendConnectEndingIfNotSent(ctx, result)
203246
}
204247
catch (error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { IResult } from '@unioc/shared'
2+
import type { Observable } from 'rxjs'
3+
4+
/**
5+
* ### 🎦 Convert an observable to a promise
6+
*
7+
* @param observable - The observable to convert
8+
* @returns A promise that resolves to the result of the observable
9+
*/
10+
export function toResult<T>(observable: Observable<T>): Promise<IResult> {
11+
return new Promise((resolve) => {
12+
observable.subscribe({
13+
next: value => resolve({ type: 'result', value }),
14+
error: error => resolve({ type: 'error', error }),
15+
})
16+
})
17+
}

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)