Skip to content

Commit

Permalink
feat(core): add dynamic error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Waidmann authored and jmcdo29 committed Jun 14, 2024
1 parent e332a78 commit 6aba508
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/throttler-module-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExecutionContext, ModuleMetadata, Type } from '@nestjs/common/interfaces';
import { ThrottlerStorage } from './throttler-storage.interface';
import { ThrottlerLimitDetail } from './throttler.guard.interface';

export type Resolvable<T extends number | string | boolean> =
| T
Expand Down Expand Up @@ -72,7 +73,9 @@ export type ThrottlerModuleOptions =
/**
* An optional message to override the default error message.
*/
errorMessage?: string;
errorMessage?:
| string
| ((context: ExecutionContext, throttlerLimitDetail: ThrottlerLimitDetail) => string);

/**
* The storage class to use where all the record will be stored in.
Expand Down
6 changes: 5 additions & 1 deletion src/throttler.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@ export class ThrottlerGuard implements CanActivate {
_throttlerLimitDetail: ThrottlerLimitDetail,
): Promise<string> {
if (!Array.isArray(this.options)) {
return this.options.errorMessage || this.errorMessage;
if (!this.options.errorMessage) return this.errorMessage;

return typeof this.options.errorMessage === 'function'
? this.options.errorMessage(_context, _throttlerLimitDetail)
: this.options.errorMessage;
}
return this.errorMessage;
}
Expand Down
35 changes: 35 additions & 0 deletions test/error-message/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { seconds, ThrottlerGuard, ThrottlerModule } from '../../src';
import { CustomErrorMessageController } from './custom-error-message.controller';

@Module({
imports: [
ThrottlerModule.forRoot({
errorMessage: (context, throttlerLimitDetail) =>
`${context.getClass().name}-${
context.getHandler().name
} ${throttlerLimitDetail.tracker} ${throttlerLimitDetail.totalHits}`,
throttlers: [
{
name: 'default',
ttl: seconds(3),
limit: 2,
},
{
name: 'other',
ttl: seconds(3),
limit: 2,
},
],
}),
],
controllers: [CustomErrorMessageController],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class CustomErrorMessageThrottlerModule {}
17 changes: 17 additions & 0 deletions test/error-message/custom-error-message.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Controller, Get } from '@nestjs/common';
import { SkipThrottle } from '../../src';

@Controller()
export class CustomErrorMessageController {
@SkipThrottle({ other: true })
@Get('default')
defaultRoute() {
return { success: true };
}

@SkipThrottle({ default: true })
@Get('other')
otherRoute() {
return { success: true };
}
}
49 changes: 49 additions & 0 deletions test/error-message/custom-error-message.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { INestApplication, Type } from '@nestjs/common';
import { AbstractHttpAdapter } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { request, spec } from 'pactum';
import { CustomErrorMessageThrottlerModule } from './app.module';

jest.setTimeout(10000);

describe.each`
adapter | name
${ExpressAdapter} | ${'express'}
${FastifyAdapter} | ${'fastify'}
`(
'Function-Overrides-Throttler Named Usage - $name',
({ adapter }: { adapter: Type<AbstractHttpAdapter> }) => {
let app: INestApplication;
beforeAll(async () => {
const modRef = await Test.createTestingModule({
imports: [CustomErrorMessageThrottlerModule],
}).compile();
app = modRef.createNestApplication(new adapter());
await app.listen(0);
request.setBaseUrl(await app.getUrl());
});
afterAll(async () => {
await app.close();
});

describe.each`
route | errorMessage
${'default'} | ${'CustomErrorMessageController-defaultRoute ::1 3'}
${'other'} | ${'CustomErrorMessageController-otherRoute ::1 3'}
`(
'Custom-error-message Route - $route',
({ route, errorMessage }: { route: string; errorMessage: string }) => {
it('should receive a custom exception', async () => {
const limit = 2;
for (let i = 0; i < limit; i++) {
await spec().get(`/${route}`).expectStatus(200);
}

await spec().get(`/${route}`).expectStatus(429).expectBodyContains(errorMessage);
});
},
);
},
);

0 comments on commit 6aba508

Please sign in to comment.