Skip to content

Commit b84246c

Browse files
Merge branch 'johnbiundo-feature-add-redirect-decorator' into 6.6.0
2 parents e7a0498 + 458e2cc commit b84246c

11 files changed

Lines changed: 134 additions & 17 deletions

File tree

packages/common/decorators/http/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from './route-params.decorator';
33
export * from './http-code.decorator';
44
export * from './create-route-param-metadata.decorator';
55
export * from './render.decorator';
6-
export * from './header.decorator';
6+
export * from './header.decorator';
7+
export * from './redirect.decorator';

packages/common/decorators/http/redirect.decorator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import { REDIRECT_METADATA } from '../../constants';
33
/**
44
* Redirects request.
55
*/
6-
export function Redirect(url: string): MethodDecorator {
6+
export function Redirect(url: string, statusCode?: number): MethodDecorator {
77
return (target: object, key, descriptor) => {
8-
Reflect.defineMetadata(REDIRECT_METADATA, url, descriptor.value);
8+
Reflect.defineMetadata(
9+
REDIRECT_METADATA,
10+
{ statusCode, url },
11+
descriptor.value,
12+
);
913
return descriptor;
1014
};
1115
}

packages/common/interfaces/http/http-server.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
4545
reply(response: any, body: any, statusCode?: number): any;
4646
status(response: any, statusCode: number): any;
4747
render(response: any, view: string, options: any): any;
48+
redirect(response: any, statusCode: number, url: string): any;
4849
setHeader(response: any, name: string, value: string): any;
4950
setErrorHandler?(handler: Function): any;
5051
setNotFoundHandler?(handler: Function): any;
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { expect } from 'chai';
22
import { Redirect } from '../../decorators/http/redirect.decorator';
33
import { REDIRECT_METADATA } from '../../constants';
4+
import { HttpStatus } from '@nestjs/common';
45

56
describe('@Redirect', () => {
67
const url = 'http://test.com';
8+
const statusCode = HttpStatus.FOUND;
79

810
class Test {
9-
@Redirect(url)
11+
@Redirect(url, statusCode)
1012
public static test() {}
1113
}
1214

13-
it('should enhance method with expected template string', () => {
15+
it('should enhance method with expected redirect url string', () => {
1416
const metadata = Reflect.getMetadata(REDIRECT_METADATA, Test.test);
15-
expect(metadata).to.be.eql(url);
17+
expect(metadata.url).to.be.eql(url);
18+
});
19+
20+
it('should enhance method with expected response code', () => {
21+
const metadata = Reflect.getMetadata(REDIRECT_METADATA, Test.test);
22+
expect(metadata.statusCode).to.be.eql(statusCode);
1623
});
1724
});

packages/core/adapters/http-adapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export abstract class AbstractHttpAdapter<
8585
abstract status(response, statusCode: number);
8686
abstract reply(response, body: any, statusCode?: number);
8787
abstract render(response, view: string, options: any);
88+
abstract redirect(response, statusCode: number, url: string);
8889
abstract setErrorHandler(handler: Function);
8990
abstract setNotFoundHandler(handler: Function);
9091
abstract setHeader(response, name: string, value: string);

packages/core/router/router-execution-context.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CUSTOM_ROUTE_AGRS_METADATA,
1010
HEADERS_METADATA,
1111
HTTP_CODE_METADATA,
12+
REDIRECT_METADATA,
1213
RENDER_METADATA,
1314
ROUTE_ARGS_METADATA,
1415
} from '@nestjs/common/constants';
@@ -36,6 +37,7 @@ import { PipesContextCreator } from '../pipes/pipes-context-creator';
3637
import { IRouteParamsFactory } from './interfaces/route-params-factory.interface';
3738
import {
3839
CustomHeader,
40+
RedirectResponse,
3941
RouterResponseController,
4042
} from './router-response-controller';
4143

@@ -192,9 +194,11 @@ export class RouterExecutionContext {
192194
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
193195
);
194196

197+
const httpRedirectResponse = this.reflectRedirect(callback);
195198
const fnHandleResponse = this.createHandleResponseFn(
196199
callback,
197200
isResponseHandled,
201+
httpRedirectResponse,
198202
);
199203

200204
const httpCode = this.reflectHttpStatusCode(callback);
@@ -204,7 +208,6 @@ export class RouterExecutionContext {
204208

205209
const responseHeaders = this.reflectResponseHeaders(callback);
206210
const hasCustomHeaders = !isEmpty(responseHeaders);
207-
208211
const handlerMetadata: HandlerMetadata = {
209212
argsLength,
210213
fnHandleResponse,
@@ -218,6 +221,10 @@ export class RouterExecutionContext {
218221
return handlerMetadata;
219222
}
220223

224+
public reflectRedirect(callback: (...args: any[]) => any): RedirectResponse {
225+
return Reflect.getMetadata(REDIRECT_METADATA, callback);
226+
}
227+
221228
public reflectHttpStatusCode(callback: (...args: any[]) => any): number {
222229
return Reflect.getMetadata(HTTP_CODE_METADATA, callback);
223230
}
@@ -358,15 +365,20 @@ export class RouterExecutionContext {
358365
public createHandleResponseFn(
359366
callback: (...args: any[]) => any,
360367
isResponseHandled: boolean,
368+
redirectResponse?: RedirectResponse,
361369
httpStatusCode?: number,
362370
) {
363371
const renderTemplate = this.reflectRenderTemplate(callback);
364-
365372
if (renderTemplate) {
366373
return async <TResult, TResponse>(result: TResult, res: TResponse) => {
367374
await this.responseController.render(result, res, renderTemplate);
368375
};
369376
}
377+
if (redirectResponse && redirectResponse.url) {
378+
return async <TResult, TResponse>(result: TResult, res: TResponse) => {
379+
await this.responseController.redirect(result, res, redirectResponse);
380+
};
381+
}
370382
return async <TResult, TResponse>(result: TResult, res: TResponse) => {
371383
result = await this.responseController.transformToResult(result);
372384
!isResponseHandled &&

packages/core/router/router-response-controller.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export interface CustomHeader {
66
value: string;
77
}
88

9+
export interface RedirectResponse {
10+
url: string;
11+
statusCode: number;
12+
}
913
export class RouterResponseController {
1014
constructor(private readonly applicationRef: HttpServer) {}
1115

@@ -17,20 +21,36 @@ export class RouterResponseController {
1721
return this.applicationRef.reply(response, result, httpStatusCode);
1822
}
1923

24+
public async redirect<TInput = any, TResponse = any>(
25+
resultOrDeferred: TInput,
26+
response: TResponse,
27+
redirectResponse: RedirectResponse,
28+
) {
29+
const result = await this.transformToResult(resultOrDeferred);
30+
const statusCode =
31+
result && result.statusCode
32+
? result.statusCode
33+
: redirectResponse.statusCode
34+
? redirectResponse.statusCode
35+
: HttpStatus.FOUND;
36+
const url = result && result.url ? result.url : redirectResponse.url;
37+
this.applicationRef.redirect(response, statusCode, url);
38+
}
39+
2040
public async render<TInput = any, TResponse = any>(
21-
resultOrDeffered: TInput,
41+
resultOrDeferred: TInput,
2242
response: TResponse,
2343
template: string,
2444
) {
25-
const result = await this.transformToResult(resultOrDeffered);
45+
const result = await this.transformToResult(resultOrDeferred);
2646
this.applicationRef.render(response, template, result);
2747
}
2848

29-
public async transformToResult(resultOrDeffered: any) {
30-
if (resultOrDeffered && isFunction(resultOrDeffered.subscribe)) {
31-
return resultOrDeffered.toPromise();
49+
public async transformToResult(resultOrDeferred: any) {
50+
if (resultOrDeferred && isFunction(resultOrDeferred.subscribe)) {
51+
return resultOrDeferred.toPromise();
3252
}
33-
return resultOrDeffered;
53+
return resultOrDeferred;
3454
}
3555

3656
public getStatusByMethod(requestMethod: RequestMethod): number {

packages/core/test/router/router-execution-context.spec.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,12 @@ describe('RouterExecutionContext', () => {
301301
sinon.stub(contextCreator, 'reflectResponseHeaders').returns([]);
302302
sinon.stub(contextCreator, 'reflectRenderTemplate').returns(template);
303303

304-
const handler = contextCreator.createHandleResponseFn(null, true, 200);
304+
const handler = contextCreator.createHandleResponseFn(
305+
null,
306+
true,
307+
undefined,
308+
200,
309+
);
305310
await handler(value, response);
306311

307312
expect(response.render.calledWith(template, value)).to.be.true;
@@ -315,11 +320,67 @@ describe('RouterExecutionContext', () => {
315320
sinon.stub(contextCreator, 'reflectResponseHeaders').returns([]);
316321
sinon.stub(contextCreator, 'reflectRenderTemplate').returns(undefined);
317322

318-
const handler = contextCreator.createHandleResponseFn(null, true, 200);
323+
const handler = contextCreator.createHandleResponseFn(
324+
null,
325+
true,
326+
undefined,
327+
200,
328+
);
319329
handler(result, response);
320330

321331
expect(response.render.called).to.be.false;
322332
});
323333
});
334+
describe('when "redirectResponse" is present', () => {
335+
beforeEach(() => {
336+
sinon
337+
.stub(adapter, 'redirect')
338+
.callsFake((response, statusCode: number, url: string) => {
339+
return response.redirect(statusCode, url);
340+
});
341+
});
342+
it('should call "res.redirect()" with expected args', async () => {
343+
const redirectResponse = {
344+
url: 'http://test.com',
345+
statusCode: 302,
346+
};
347+
const response = { redirect: sinon.spy() };
348+
349+
const handler = contextCreator.createHandleResponseFn(
350+
() => {},
351+
true,
352+
redirectResponse,
353+
200,
354+
);
355+
await handler(redirectResponse, response);
356+
357+
expect(
358+
response.redirect.calledWith(
359+
redirectResponse.statusCode,
360+
redirectResponse.url,
361+
),
362+
).to.be.true;
363+
});
364+
});
365+
366+
describe('when "redirectResponse" is undefined', () => {
367+
it('should not call "res.render()"', () => {
368+
const result = Promise.resolve('test');
369+
const response = { redirect: sinon.spy() };
370+
371+
sinon.stub(contextCreator, 'reflectResponseHeaders').returns([]);
372+
sinon.stub(contextCreator, 'reflectRenderTemplate').returns(undefined);
373+
374+
const handler = contextCreator.createHandleResponseFn(
375+
null,
376+
true,
377+
undefined,
378+
200,
379+
);
380+
handler(result, response);
381+
382+
expect(response.redirect.called).to.be.false;
383+
});
384+
});
324385
});
325386
});

packages/core/test/utils/noop-adapter.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class NoopHttpAdapter extends AbstractHttpAdapter {
1414
reply(response: any, body: any): any {}
1515
status(response: any, statusCode: number): any {}
1616
render(response: any, view: string, options: any): any {}
17+
redirect(response: any, statusCode: number, url: string) {}
1718
setErrorHandler(handler: Function): any {}
1819
setNotFoundHandler(handler: Function): any {}
1920
setHeader(response: any, name: string, value: string): any {}

packages/platform-express/adapters/express-adapter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export class ExpressAdapter extends AbstractHttpAdapter {
3636
return response.render(view, options);
3737
}
3838

39+
public redirect(response: any, statusCode: number, url: string) {
40+
return response.redirect(statusCode, url);
41+
}
42+
3943
public setErrorHandler(handler: Function) {
4044
return this.use(handler);
4145
}

0 commit comments

Comments
 (0)