All files / ext.wikilambda.app/store/classes ApiError.js

100% Statements 158/158
100% Branches 21/21
100% Functions 6/6
100% Lines 158/158

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 1597x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 20x 20x 20x 20x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 18x 18x 2x 2x 18x 18x 18x 18x 3x 3x 2x 2x 3x 18x 18x 18x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 3x 7x 7x 7x 1x 7x 7x 7x 1x 7x 7x 2x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 6x 6x 6x 6x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 3x 3x 1x 1x 1x 2x 2x 2x 2x 2x 2x 3x 3x 7x 7x 7x  
/*!
 * WikiLambda ApiError class
 *
 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
 * @license MIT
 */
'use strict';
 
const Constants = require( '../../Constants.js' ),
	utils = require( '../../mixins/utilsMixins.js' ).methods;
 
/**
 * ApiError class contains a set of utilities to transform any kind of
 * errors into a standard object usable by all components of the app.
 *
 * @class
 * @property {string} code
 * @property {Object} response
 */
class ApiError extends Error {
 
	constructor( code, response ) {
		super();
		this.code = code;
		this.response = response;
	}
 
	/**
	 * Returns the most accurate and informative error message from the
	 * available error data. When the error is a ZError, returns a message
	 * crafted from its ZError body (if possible).
	 *
	 * If there's no message or response has the wrong structure, returns undefined
	 *
	 * @return {string|undefined}
	 */
	get message() {
		const error = utils.getNestedProperty( this.response, 'error' );
		if ( typeof error !== 'object' ) {
			return undefined;
		}
 
		// Special treatment for ZErrors
		const errorCode = utils.getNestedProperty( error, 'code' );
		if ( errorCode === 'wikilambda-zerror' ) {
			const errorMessage = this.messageForZError;
			if ( errorMessage ) {
				return errorMessage;
			}
		}
 
		return this.response.error.message || this.response.error.info;
	}
 
	/**
	 * Special case for some ZErrors: extract error message from the body
	 * of the ZError object
	 *
	 * @return {string|undefined}
	 */
	get messageForZError() {
		const errorType = utils.getNestedProperty( this.response.error.zerror, Constants.Z_ERROR_TYPE );
		switch ( errorType ) {
			// Z500: Return content of first key (message)
			case Constants.Z_ERRORS.Z_ERROR_UNKNOWN:
				return utils.getNestedProperty( this.response.error.zerror, `${ Constants.Z_ERROR_VALUE }.K1` );
 
			// Z548: Return content of first key (message)
			case Constants.Z_ERRORS.Z_ERROR_INVALID_JSON:
				return utils.getNestedProperty( this.response.error.zerror, `${ Constants.Z_ERROR_VALUE }.K1` );
 
			// Z557: Return content of first key (message)
			case Constants.Z_ERRORS.Z_ERROR_USER_CANNOT_EDIT:
				return utils.getNestedProperty( this.response.error.zerror, `${ Constants.Z_ERROR_VALUE }.K1` );
 
			default:
				return undefined;
		}
	}
 
	/**
	 * Returns the error message if this was an intentionally raised error in the API (with
	 * dieWithError or dieWithZError) and there's an available error message in the payload.
	 * Else, it returns the string for the i18n message passed as the fallbackCode argument.
	 * If no argument, it returns an even more generic error message (UNKNOWN_ERROR).
	 *
	 * @param {string} fallbackCode valid i18n message code
	 * @return {string} error message
	 */
	messageOrFallback( fallbackCode = null ) {
		return ( this.code === 'http' && this.message ) ?
			this.message :
			mw.message( fallbackCode || Constants.errorCodes.UNKNOWN_ERROR ).text();
	}
 
	/**
	 * Static method to handle mw.Api rejected promises and convert the rejection
	 * arguments into a more standard ApiError object. Since mw.Api uses jQuery
	 * Deferreds, there can be up to four arguments:
	 *
	 * * jQuery AJAX failures:
	 *   * code string is 'http'
	 *   * arg2 contains an Object like { xhr: JQuery.jqXHR, textStatus: string, exception: string }
	 *
	 * * API failures:
	 *   * code string is 'internal_api_error_' + error type
	 *   * arg2 contains a response Object like { error: { code: string, info: string, ... }, servedby: string }
	 *   * arg3 contains the same as arg2
	 *   * arg4 contains a JQuery.jqXHR object
	 *
	 * Use it as a mw.Api() Promise rejection callback function:
	 *
	 * ```
	 * function callApi () {
	 *   const api = new mw.Api();
	 *   return api
	 *     .post( { action: 'some_action_api' } )
	 *     .then( ( data ) => data )
	 *     .catch( ApiError.fromMwApiRejection );
	 * }
	 *
	 * callApi()
	 *   .then( ( data ) => doSomethingWith( data ) )
	 *   .catch( ( error ) => {
	 *     // error is an ApiError instance
	 *     // print error message returned in the response
	 *     console.log( error.message );
	 *     // print error message, if any; else, the fallback error message
	 *     console.log( error.messageorfallback() );
	 *     // print error message, if any; else, the string passed as argument
	 *     console.log( error.messageorfallback( 'something went wrong' ) );
	 *   } );
	 * ```
	 *
	 * @param {string} code
	 * @param {Object} arg2
	 * @param {Object|undefined} _arg3
	 * @param {Object|undefined} _arg4
	 * @throws {ApiError}
	 */
	// eslint-disable-next-line no-unused-vars
	static fromMwApiRejection( code, arg2, _arg3, _arg4 ) {
		let response;
		if ( code === 'http' ) {
			// jQuery AJAX failure:
			// arg2.xhr contains an object of class jQuery.jqXHR
			response = arg2.xhr.responseJSON;
		} else {
			// API failure:
			// arg2 and arg3 contain a response object
			// arg4 contains an object of class jQuery.jqXHR
			response = arg2;
		}
		throw new ApiError( code, response );
	}
}
 
module.exports = exports = ApiError;