import { intersection } from 'lodash-es'; import { TranslateService } from '@ngx-translate/core'; // Module stuff import { BackendResponseModel } from '../models/backend-response.model'; import { BaseErrorHandler } from './errorHandlers/base-error.handler'; import { MwAuthorizationExceptionHandler, ExceptionHandler, BusinessLogicExceptionHandler, UnknownErrorHandler, } from './errorHandlers'; import { ErrorCodes, RawHttpErrorResponse } from '../models'; import { MwNotificationService } from 'projects/core/src//services'; import { CustomHttpError } from '../models/custom-http-error.model'; import { HttpErrorResponse } from '@angular/common/http'; import { MwHttpErrorParser } from './http-error-parser.service'; import { MwInternetOfflineErrorHandler } from './errorHandlers/internet-offline-error.handler'; export interface MwHttpErrorHandlerOptions { interpolateParams?: any; ignoreNotifyErrorCodes?: ErrorCodes[]; ignoreNotifyStatusCodes?: number[]; } /** * @description * Error handler which handle errors from HTTP requests. Now we handle requests from: * - WA backend; * - Angular app backend; * * @howToUse * Case 1. New error handler * 1) Create new error handler pipe with name * a) backend-.handler.ts - for angular app backend; * b) wa-.handler.ts - for WA backend; * 2) Pipe should extend ErrorHandlerPipe (error.handler.ts) and implements these methods: * a) handle - code which handle error and transform it to BackendResponseModel * b) canHandle - checks whether error is valid for handle; * 2) Register newly created pipe in (@ling HttpErrorHandler) constructor; * * * - register new error codes in ErrorCode enum (BackendResponseModel.ts) * ** - register new error codes translations in translations section ERROR * */ export class MwHttpErrorHandler { private handlers: BaseErrorHandler[] = []; private showNotificationWithRawDescription: ErrorCodes[] = [ ErrorCodes.RequestInvalid, ErrorCodes.BadRequest, ]; private errorParser = new MwHttpErrorParser(); constructor( protected readonly translateService: TranslateService, private readonly notificationService: MwNotificationService ) { // Order of handlers is important this.handlers.push(new MwInternetOfflineErrorHandler()); this.handlers.push(new MwAuthorizationExceptionHandler()); this.handlers.push(new ExceptionHandler()); this.handlers.push(new BusinessLogicExceptionHandler()); this.handlers.push(new UnknownErrorHandler()); } handleError( error: HttpErrorResponse | CustomHttpError, options?: MwHttpErrorHandlerOptions ): BackendResponseModel { options = options || {}; const httpResponseError = this.errorParser.parse(error); for (const pipe of this.handlers) { const result = this.processError(options, httpResponseError, pipe); if (result) { return result; } } return new BackendResponseModel(); } addCustomErrorHandlerPipe(errorHandlerPipe: any): void { this.handlers.unshift(errorHandlerPipe); } private addToNotificationsState( response: BackendResponseModel, options?: MwHttpErrorHandlerOptions ): void { const ignoreErrorCodes = options?.ignoreNotifyErrorCodes || []; for (const error of response.errors.filter( (t) => ignoreErrorCodes.indexOf(t.errorCode) === -1 )) { let description = error.errorDescription; // Some errors should show full stack of raw description (Exception case). if ( this.showNotificationWithRawDescription.indexOf(error.errorCode) !== -1 ) { description = error.originalDescription; } if (error.errorCode === ErrorCodes.GeneralException) { this.notificationService.exception(error.errorDescription); } else { this.notificationService.error(description); } } } private processError( options: MwHttpErrorHandlerOptions, httpResponseError: RawHttpErrorResponse, pipe: BaseErrorHandler ): BackendResponseModel | null { pipe.httpResponseError = httpResponseError; if (pipe.canHandle()) { const result = pipe.handle(); result.isShowNotification = this.isShowNotification( options, result, httpResponseError.status ); this.translateErrors(options, result); if (result.isShowNotification) { this.addToNotificationsState(result, options); } return result; } return null; } private isIgnoreByStatusCode( options: MwHttpErrorHandlerOptions, statusCode: number ): boolean { return ( (options && options.ignoreNotifyStatusCodes && options.ignoreNotifyStatusCodes.filter((t) => t === statusCode).length > 0) === true ); } private isIgnoreByErrorCode( options: MwHttpErrorHandlerOptions, errorCodes: ErrorCodes[] ): boolean { return ( options && intersection(errorCodes, options.ignoreNotifyErrorCodes).length > 0 ); } private isShowNotification( options: MwHttpErrorHandlerOptions, response: BackendResponseModel, statusCode: number ): boolean { const errorCodes = response.errors.map((t) => t.errorCode) || []; const ignoreNotificationByStatusCode = this.isIgnoreByStatusCode( options, statusCode ); const ignoreNotificationByErrorCode = this.isIgnoreByErrorCode( options, errorCodes ); return !ignoreNotificationByStatusCode && !ignoreNotificationByErrorCode; } private translateErrors( options: MwHttpErrorHandlerOptions, response: BackendResponseModel ): void { response.errors.forEach((error) => { const key = 'ERROR.' + error.errorCode; const translation = this.translateService.instant( key, options.interpolateParams ); if (translation !== key) { error.errorDescription = translation; } }); } }