import Service, { inject as service } from '@ember/service'; import { get } from '@ember/object'; import { isArray } from '@ember/array'; /** * Handles all operations that have to do with errors * * ```js * @service errorHandler; * this.errorHandler.notify() * ``` * * @class ErrorHandlerService * @constructor * @public */ export default class ErrorHandlerService extends Service { @service private intl: any; @service private notifications: any public toError(error: any) { if (this._capiError(error)) return this._capiError(error) if (this._networkError(error)) return this._networkError(error) let message = JSON.stringify(error) if (get(error, 'payload.message')) message = get(error, 'payload.message') else if (error.message) message = error.message return { type: 'unknown', error, message: this.tryToTranslateMessage(message), meta: null, } } /** * Use notifications to show user to error. Can be used directly in promise fail * EXAMPLE: promise.then(..., (reason) => get(this, 'error').notify(reason))) * @method notify * @param {*} error */ public notify(error: any) { this.notifications.error(this.errorToString(error), { autoClear: true }) } public errorToString(error: any) { if (!error) return // try to get message first from .message and if not found check also .error return this.toError(error)!.message ? this.toError(error)!.message : this.toError(error)!.error } private tryToTranslateMessage(message: any) { if (typeof message !== 'string') return message const strippedMessage = message.toLowerCase().replace(/ /g, ' '); if (this.intl.exists('error.' + strippedMessage)) { return this.intl.t('error.' + strippedMessage); } return message } /** * FORMAT EXAMPLE * if application adapter has normalized error it looks like this * { * message: 'Request was rejected because user is not permitted to perform this operation.' * name: 'Error' * payload: { * error: 'overlap', * error_msg: 'two or more rows have overlapping dates' * meta: {hits: 'worktime', ids: Array(1) * } * stack: .... * } * * OR * if application adapter has not normalized error it looks like this * { * errors: [ * { * error: 'overlap' * errorMeta: {hits: 'absence', ids: Array(1)} * error_msg: 'two or more rows have overlapping dates' * } * ] * message: 'Ember Data Request POST http://localhost:30001/abcenses returned a 403 ..... * name: 'Error' * * @method _capiError * @param {any} error * @return {object} */ private _capiError(error: any) { if (typeof error !== 'object') return null let ret = null let errors = 'errors' if (isArray(get(error, 'errors'))) { errors = 'errors.0' } if (get(error, 'payload.error')) { ret = { type: 'capi', error: get(error, 'payload.error'), message: get(error, 'payload.error_msg'), meta: get(error, 'payload.meta'), } } else if (get(error, errors + '.error')) { ret = { type: 'capi', error: get(error, errors + '.error'), message: get(error, errors + '.error_msg'), meta: get(error, errors + '.errorMeta'), } } else { return null } switch (ret.error) { case 'overlap': if (ret.meta.hits === 'absence') ret.message = this.intl.t('validation.absence_overlap') else if (ret.meta.hits === 'comp') ret.message = this.intl.t('validation.comp_overlap') else ret.message = this.intl.t('validation.worktime_overlap') break case 'not_only_row': ret.message = this.intl.t('validation.worktime_not_only_row') break case 'not_found': ret.message = this.intl.t('validation.not_found.general') break case 'validation_error': ret.message = this.intl.exists('validation.' + ret.message) ? this.intl.t('validation.' + ret.message) : ret.message } return ret } /** * FORMAT EXAMPLE * { * message: 'The ajax operation was aborted' * name: 'Error' * payload: null * stack: .... * @method _networkError * @param {any} error * @return {object} */ private _networkError(error: any) { if (typeof error !== 'object') return null if (error.message !== 'The ajax operation was aborted') return null return { type: 'network', error: error.message, message: this.intl.t('validation.network_error'), meta: null, }; } }