Compare commits

..

2 Commits

Author SHA1 Message Date
Kamil Myśliwiec
ab660c7edd test: add unit test 2024-11-25 13:48:54 +01:00
Kamil Myśliwiec
21279a9c49 feat(microservices): support nats queue per handler 2024-11-25 13:42:24 +01:00
9 changed files with 50 additions and 67 deletions

View File

@@ -3,7 +3,6 @@ import {
HttpExceptionBodyMessage,
} from '../interfaces/http/http-exception-body.interface';
import { isNumber, isObject, isString } from '../utils/shared.utils';
import { IntrinsicException } from './intrinsic.exception';
export interface HttpExceptionOptions {
/** original cause of the error */
@@ -24,13 +23,7 @@ export interface DescriptionAndOptions {
*
* @publicApi
*/
export class HttpException extends IntrinsicException {
/**
* Exception cause. Indicates the specific original cause of the error.
* It is used when catching and re-throwing an error with a more-specific or useful error message in order to still have access to the original error.
*/
public cause: unknown;
export class HttpException extends Error {
/**
* Instantiate a plain HTTP Exception.
*
@@ -75,6 +68,8 @@ export class HttpException extends IntrinsicException {
this.initCause();
}
public cause: unknown;
/**
* Configures error chaining support
*

View File

@@ -1,23 +1,22 @@
export * from './bad-gateway.exception';
export * from './bad-request.exception';
export * from './conflict.exception';
export * from './forbidden.exception';
export * from './gateway-timeout.exception';
export * from './gone.exception';
export * from './http-version-not-supported.exception';
export * from './http.exception';
export * from './im-a-teapot.exception';
export * from './internal-server-error.exception';
export * from './intrinsic.exception';
export * from './method-not-allowed.exception';
export * from './misdirected.exception';
export * from './not-acceptable.exception';
export * from './not-found.exception';
export * from './not-implemented.exception';
export * from './payload-too-large.exception';
export * from './precondition-failed.exception';
export * from './request-timeout.exception';
export * from './service-unavailable.exception';
export * from './unauthorized.exception';
export * from './unprocessable-entity.exception';
export * from './method-not-allowed.exception';
export * from './not-found.exception';
export * from './forbidden.exception';
export * from './not-acceptable.exception';
export * from './request-timeout.exception';
export * from './conflict.exception';
export * from './gone.exception';
export * from './payload-too-large.exception';
export * from './unsupported-media-type.exception';
export * from './unprocessable-entity.exception';
export * from './internal-server-error.exception';
export * from './not-implemented.exception';
export * from './http-version-not-supported.exception';
export * from './bad-gateway.exception';
export * from './service-unavailable.exception';
export * from './gateway-timeout.exception';
export * from './im-a-teapot.exception';
export * from './precondition-failed.exception';
export * from './misdirected.exception';

View File

@@ -1,7 +0,0 @@
/**
* Exception that represents an intrinsic error in the application.
* When thrown, the default exception filter will not log the error message.
*
* @publicApi
*/
export class IntrinsicException extends Error {}

View File

@@ -5,7 +5,6 @@ import {
HttpServer,
HttpStatus,
Inject,
IntrinsicException,
Logger,
Optional,
} from '@nestjs/common';
@@ -69,9 +68,7 @@ export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
applicationRef.end(response);
}
if (!(exception instanceof IntrinsicException)) {
BaseExceptionFilter.logger.error(exception);
}
return BaseExceptionFilter.logger.error(exception);
}
public isExceptionObject(err: any): err is Error {

View File

@@ -1,13 +1,10 @@
import { ArgumentsHost, IntrinsicException, Logger } from '@nestjs/common';
import { ArgumentsHost, HttpException, Logger } from '@nestjs/common';
export class ExternalExceptionFilter<T = any, R = any> {
private static readonly logger = new Logger('ExceptionsHandler');
catch(exception: T, host: ArgumentsHost): R | Promise<R> {
if (
exception instanceof Error &&
!(exception instanceof IntrinsicException)
) {
if (exception instanceof Error && !(exception instanceof HttpException)) {
ExternalExceptionFilter.logger.error(exception);
}
throw exception;

View File

@@ -1,10 +1,5 @@
/* eslint-disable prefer-spread */
import {
ArgumentsHost,
IntrinsicException,
Logger,
RpcExceptionFilter,
} from '@nestjs/common';
import { ArgumentsHost, Logger, RpcExceptionFilter } from '@nestjs/common';
import { isObject } from '@nestjs/common/utils/shared.utils';
import { MESSAGES } from '@nestjs/core/constants';
import { Observable, throwError as _throw } from 'rxjs';
@@ -31,10 +26,8 @@ export class BaseRpcExceptionFilter<T = any, R = any>
public handleUnknownError(exception: T, status: string) {
const errorMessage = MESSAGES.UNKNOWN_EXCEPTION_MESSAGE;
if (!(exception instanceof IntrinsicException)) {
const logger = BaseRpcExceptionFilter.logger;
logger.error(exception);
}
const logger = BaseRpcExceptionFilter.logger;
logger.error.apply(logger, [exception]);
return _throw(() => ({ status, message: errorMessage }));
}

View File

@@ -77,16 +77,18 @@ export class ServerNats<
}
public bindEvents(client: Client) {
const queue = this.getOptionsProp(this.options, 'queue');
const subscribe = (channel: string) =>
const subscribe = (channel: string, queue: string) =>
client.subscribe(channel, {
queue,
callback: this.getMessageHandler(channel).bind(this),
});
const defaultQueue = this.getOptionsProp(this.options, 'queue');
const registeredPatterns = [...this.messageHandlers.keys()];
for (const channel of registeredPatterns) {
const sub = subscribe(channel);
const handlerRef = this.messageHandlers.get(channel);
const queue = handlerRef.extras?.queue ?? defaultQueue;
const sub = subscribe(channel, queue);
this.subscriptions.push(sub);
}
}

View File

@@ -113,11 +113,26 @@ describe('ServerNats', () => {
[pattern]: messageHandler,
});
});
it('should subscribe to each acknowledge patterns', () => {
it('should subscribe to every pattern', () => {
server.bindEvents(natsClient);
expect(subscribeSpy.calledWith(pattern)).to.be.true;
});
it('should use a per pattern queue if provided', () => {
const queue = 'test';
untypedServer.messageHandlers = objectToMap({
[pattern]: Object.assign(messageHandler, {
extras: {
queue,
},
}),
});
server.bindEvents(natsClient);
const lastCall = subscribeSpy.lastCall;
expect(lastCall.args[1].queue).to.be.eql(queue);
});
it('should fill the subscriptions array properly', () => {
server.bindEvents(natsClient);
expect(server['subscriptions'].length).to.be.equals(1);

View File

@@ -1,9 +1,4 @@
import {
ArgumentsHost,
IntrinsicException,
Logger,
WsExceptionFilter,
} from '@nestjs/common';
import { ArgumentsHost, Logger, WsExceptionFilter } from '@nestjs/common';
import { isObject } from '@nestjs/common/utils/shared.utils';
import { MESSAGES } from '@nestjs/core/constants';
import { WsException } from '../errors/ws-exception';
@@ -51,10 +46,7 @@ export class BaseWsExceptionFilter<TError = any>
message: MESSAGES.UNKNOWN_EXCEPTION_MESSAGE,
});
if (!(exception instanceof IntrinsicException)) {
const logger = BaseWsExceptionFilter.logger;
logger.error(exception);
}
return BaseWsExceptionFilter.logger.error(exception);
}
public isExceptionObject(err: any): err is Error {