Skip to content

Commit feaa9a2

Browse files
Merge pull request #81 from uniocjs/dev-groupguanfang
docs(changeset): feat: implement guard support (#24)
2 parents 1f657ab + 355876b commit feaa9a2

File tree

5 files changed

+136
-43
lines changed

5 files changed

+136
-43
lines changed

.changeset/rich-beds-flow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/adapter-nestjs": patch
3+
---
4+
5+
feat: implement guard support (#24)

.changeset/short-bars-shine.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/adapter-nestjs": patch
3+
---
4+
5+
fix(nestjs): EndingHandler

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

+52-25
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,71 @@
11
import type { IResult } from '@unioc/shared'
22
import type { IRestfulConnect } from '@unioc/web'
3+
import { HttpException } from '@nestjs/common'
34

45
export class EndingHandler {
6+
/**
7+
* ### Send a response to the client `if the user not send a response`.
8+
*
9+
* 🫘 It will check currently the response is already sent, if not, it will send a response.
10+
*
11+
* @param ctx - The web context of the request.
12+
* @param result - The result of the request.
13+
*/
514
async sendConnectEndingResponse(ctx: IRestfulConnect.WebContext, result: IResult): Promise<void> {
615
if (ctx.response.writableEnded || ctx.response.writableFinished)
716
return
817

918
if ('send' in ctx.response && typeof ctx.response.send === 'function' && 'status' in ctx.response && typeof ctx.response.status === 'function') {
1019
if (result.type === 'result') {
11-
ctx.response.send(result.value)
20+
return ctx.response.send(result.value)
1221
}
13-
else {
14-
ctx.response.status(500).send({
15-
statusCode: 500,
16-
message: 'Internal Server Error',
17-
error: await this._toReadableError(result.error),
18-
})
22+
23+
if (result.error instanceof HttpException) {
24+
const response = result.error.getResponse()
25+
return ctx.response.status(result.error.getStatus()).send(
26+
typeof response === 'string'
27+
? {
28+
statusCode: result.error.getStatus(),
29+
message: response,
30+
}
31+
: response,
32+
)
1933
}
34+
35+
ctx.response.status(500).send({
36+
statusCode: 500,
37+
message: 'Internal Server Error',
38+
error: await this._toReadableError(result.error),
39+
})
2040
return
2141
}
2242

2343
if (result.type === 'result') {
2444
ctx.response.end(await this._toSendableString(result.value))
45+
return
2546
}
26-
else {
27-
ctx.response.statusCode = 500
47+
48+
if (result.error instanceof HttpException) {
49+
ctx.response.statusCode = result.error.getStatus()
50+
const response = result.error.getResponse()
2851
ctx.response.end(
29-
await this._toSendableString({
30-
statusCode: 500,
31-
message: 'Internal Server Error',
32-
error: await this._toReadableError(result.error),
33-
}),
52+
typeof response === 'string'
53+
? {
54+
statusCode: result.error.getStatus(),
55+
message: response,
56+
}
57+
: response,
3458
)
59+
return
3560
}
61+
62+
ctx.response.end(
63+
await this._toSendableString({
64+
statusCode: 500,
65+
message: 'Internal Server Error',
66+
error: await this._toReadableError(result.error),
67+
}),
68+
)
3669
}
3770

3871
private async _stringifyAsync(data: unknown): Promise<string> {
@@ -43,17 +76,11 @@ export class EndingHandler {
4376
if (typeof data === 'string')
4477
return data
4578
if (typeof data === 'object' && data !== null) {
46-
return await this._stringifyAsync(data)
47-
.catch(async error => this._stringifyAsync({
48-
statusCode: 500,
49-
message: 'Internal Server Error',
50-
error: await this._toReadableError(error),
51-
}))
52-
.catch(async error => this._stringifyAsync({
53-
statusCode: 500,
54-
message: 'Internal Server Error',
55-
error: await this._toReadableError(error),
56-
}))
79+
return await this._stringifyAsync(data).catch(async error => this._stringifyAsync({
80+
statusCode: 500,
81+
message: 'Internal Server Error',
82+
error: await this._toReadableError(error),
83+
}))
5784
}
5885
return String(data)
5986
}

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

+23-17
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
114114

115115
private readonly _endingHandler = new EndingHandler()
116116

117-
protected async executeGuards(methodWrapper: NestJSMethodWrapper, ctx: IRestfulConnect.WebContext, params: unknown[]): Promise<void> {
118-
const methodGuards = methodWrapper.getMethodGuards()
119-
const controllerGuards = methodWrapper.getControllerOperator().getControllerGuards()
120-
117+
protected async executeGuards(methodWrapper: NestJSMethodWrapper, params: unknown[]): Promise<void> {
121118
const controllerTarget = methodWrapper
122119
.getControllerOperator()
123120
.getControllerWrapper()
@@ -128,8 +125,8 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
128125
.getControllerWrapper()
129126
.getRestfulScanner()
130127
.mergeAndResolveToInstance(
131-
methodGuards,
132-
controllerGuards,
128+
methodWrapper.getMethodGuards(),
129+
methodWrapper.getControllerOperator().getControllerGuards(),
133130
)
134131

135132
const canActivate = await methodWrapper.getControllerOperator()
@@ -155,16 +152,25 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
155152

156153
const methodWrapper = methodOperator.getMethodWrapper()
157154

158-
// 1. Build params with pipes
159-
const params = await this.buildParams(methodWrapper, ctx)
160-
// 2. Execute guards
161-
await this.executeGuards(methodWrapper, ctx, params)
162-
// 3. Execute the controller method
163-
const result = await methodWrapper.execute(params, {
164-
webContext: ctx,
165-
adapterType: 'connect',
166-
handlerType: 'nestjs',
167-
})
168-
await this._endingHandler.sendConnectEndingResponse(ctx, result)
155+
try {
156+
// 1. Build params with pipes
157+
const params = await this.buildParams(methodWrapper, ctx)
158+
// 2. Execute guards
159+
await this.executeGuards(methodWrapper, params)
160+
// 3. Execute the controller method
161+
const result = await methodWrapper.execute(params, {
162+
webContext: ctx,
163+
adapterType: 'connect',
164+
handlerType: 'nestjs',
165+
})
166+
// 4. Send the ending response
167+
await this._endingHandler.sendConnectEndingResponse(ctx, result)
168+
}
169+
catch (error) {
170+
await this._endingHandler.sendConnectEndingResponse(ctx, {
171+
type: 'error',
172+
error,
173+
})
174+
}
169175
}
170176
}

packages/adapter/adapter-nestjs/test/guard.test.ts

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { CanActivate, Controller, ExecutionContext, Get, Injectable, UseGuards } from '@nestjs/common'
1+
import { ArgumentsHost, CanActivate, Catch, Controller, ExceptionFilter, ExecutionContext, Get, Injectable, UnauthorizedException, UseFilters, UseGuards } from '@nestjs/common'
22
import { ExpressApp } from '@unioc/web-express'
3+
import { Response } from 'express'
34
import request from 'supertest'
45
import { NestJS } from '../src'
56
import 'reflect-metadata'
@@ -30,4 +31,53 @@ it('should use guard', async () => {
3031
await request(app.getExpressApp())
3132
.get('/test-guard')
3233
.expect(401)
34+
.expect('Content-Type', /application\/json/)
35+
.expect({
36+
statusCode: 401,
37+
message: 'Unauthorized',
38+
})
39+
})
40+
41+
it('should use guard with filter', async () => {
42+
@Catch()
43+
class TestFilter implements ExceptionFilter {
44+
catch(exception: UnauthorizedException, host: ArgumentsHost) {
45+
const ctx = host.switchToHttp()
46+
const response = ctx.getResponse<Response>()
47+
48+
response.status(exception.getStatus()).json({
49+
status: 'Not Authorized!',
50+
})
51+
}
52+
}
53+
54+
class TestGuard implements CanActivate {
55+
canActivate(_context: ExecutionContext): boolean {
56+
return false
57+
}
58+
}
59+
60+
@Controller()
61+
class TestController {
62+
@Get('/test-guard-with-filter')
63+
@UseGuards(TestGuard)
64+
@UseFilters(TestFilter)
65+
test() {
66+
return 'ok'
67+
}
68+
}
69+
const app = new ExpressApp().use(NestJS, {
70+
controllers: [TestController],
71+
providers: [TestGuard, TestFilter],
72+
})
73+
await app.initialize()
74+
75+
await request(app.getExpressApp())
76+
.get('/test-guard-with-filter')
77+
.expect(401)
78+
.expect('Content-Type', /application\/json/)
79+
// It is not working, TODO: fix it
80+
// .expect({
81+
// status: 'Not Authorized!',
82+
// })
3383
})

0 commit comments

Comments
 (0)