0.2.13 • Published 4 years ago

demo-nest-auth-fastify v0.2.13

Weekly downloads
5
License
MIT
Repository
-
Last release
4 years ago

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 (или разбиваются на несколько файлов, если их много).

Все классы исключений должны быть унаследованы от общего для домена класса domainNameException (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 содержит два фильтра, подключаемых по умолчанию:

  1. UnhandledErrorsFilter - отвечает за формирование ответа сервера в случае необработанных исключений.
  2. 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.