demo-nest-auth-fastify
v0.2.13
Published
http://10.10.11.120:4873/ verdaccio
Downloads
6
Readme
http://10.10.11.120:4873/ verdaccio
Экспепшены влияют ли на control flow? Конкретные примеры: ошибка верификации jwt-токена. пользователь с указанным id не найден в базе. (Его просто нет) Это исключения или нет.
Nest с его HttpExceptions и exception filter подталкивает нас к тому, что эксепшен - любая ситуация, которая должна прекратить обработку запроса (неверный токен, отсутствие прав, что угодно ещё)
try/catch в сервисе - это вовсе не control flow, это именно проброс с заворачиванием.
Example 1
try {
const isVerified = Jwt.verify(...);
if (!isVerified) {
throw new AuthException()
// Или сразу res.send?
// Или throw ForbiddenHttpException, чтобы потом его exception filter отловил.
}
}
catch (err) {
// Тут надо разбираться, что это за ошибка.
// Может это auth ошибка?
}
Example 2
try {
await Jwt.verify(token) // will throw error if token is invalid
}
catch (err){
wrap(err).throwAs(AuthException) // will throw AuthException with inner exception being the one thrown by verify.
}
Обработка ошибок в домене (модуле)
Классы ошибок
Внутри домена могут возникать различные исключительные ситуации. Например попытка добавить в базу пользователся с уже существующим адресом эл. почты.
В этом случае контроллер (или сервис) должен выбросить соответствующее исключение, например:
throw new EmailAlreadyExistsException(email);
Классы исключений для домена создаются в файле [domainName].errors.ts (или разбиваются на несколько файлов, если их много).
Все классы исключений должны быть унаследованы от общего для домена класса [domainName]Exception (DriverException в данном примере), который в свою очередь наследуется от класса DomainException из пакета @skeleton/errors
export class DriverException extends DomainException {
constructor(msg: string, inner?: ErrorLike) {
super(msg, inner); // Or some custom logic.
}
}
export class EmailAlreadyExistsException extends DriverException {
constructor(email: string) {
super(`User with email <${email}> already exists`);
}
}
Класс DomainException преставляет собой простую обёртку над стандартным js-объектом Error, позволяющую вкладывать внутрь другой объект ошибки.
Например:
try {
// do something with datadase that throws some DB error;
} catch (err) {
// err - some data access layer error
throw new DriverException(err.message, err);
}
В таком сценарии, когда наш DriverException будет отловлен в интерсепторе (см. ниже), внутри будет содержаться более низкоуровневая ошибка (со стеком и прочими данными), что удобно для логирования и отладки.
Маппинг ошибок и HttpExceptionFilter
За формирования ответа пользователю отвечает контроллер. Но, чтобы не загружать каждый контроллер логикой по обработке всех видов ошибок в nesjs существует концепция Exception-фильтров.
Пакет @skeleton/errors содержит два фильтра, подключаемых по умолчанию:
- UnhandledErrorsFilter - отвечает за формирование ответа сервера в случае необработанных исключений.
- HttpExceptionFilter - отвечает за формирование ответа в случае выброшенных HttpException.
Благодаря п. 2, разработчику не нужно заботиться о формировании ответа в случае различных доменных ошибок. Достаточно просто выбросить нужный HttpException (например NotFoundException или ForbiddenException) с необходимой информацией внутри.
Какие ошибки в каком случае выбрасывать мы можем указать в перехватчике (interceptor).
DomainError interceptors (перехватчики исключений)
Перехватчик - особый класс в nestjs, которая может трансформировать данные (и исключения) происходящие во время обработки запроса.
Важный момент, установленный опытным путём: исключения выбрасываемые в middleware (то есть до начала обработки запроса как такового) отловить интерцептором нельзя.
Поэтому в таких случаях маппинг приходится делать прямо в middleware.
Перехватчики могут использоваться для разных целей. В данном случае мы используем их для того, чтобы указать, при каких доменных ошибках, какие HttpException следует выбрасывать (и, соответственно, какие ответы клиенту отправлять).
@Injectable()
export class DriverExceptionsInterceptor implements NestInterceptor {
constructor(
private readonly logger: AppLog
) { }
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle()
.pipe(catchError(err => {
// Logging domain error
this.logger.error(err.toString());
// Mapping it to HttpException
if (err instanceof DriverException) {
return throwError(new BadRequestException(err.message))
} else if (....
....
}
/* If err is some unknown exception we
just throw it. In the end it will be caught
by UnhandledExceptionFilter */
return throwError(err);
}));
}
}
next.handle() предоставляет доступ к Observable (потоку) в которой попадёт наше доменное исключение. При помощи метода catchError мы получаем саму ошибку и в зависимости от требований конкретной ситуации выбрасываем соответствущий HttpException.