Source: lib/errors.js

'use strict'

const _ = require('lodash')

// const eyes = require('eyes'), p = eyes.inspect.bind(eyes)

/**
 * Base class for [`ClientError`]{@link module:classeur-api-client.ClientError} and [`ServerError`]{@link module:classeur-api-client.ServerError} objects.
 * @deprecated  You should never need to construct one of these directly they will be provided to callbacks for REST API operations as the first argument if they occur.
 * @extends [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
 * @memberof module:classeur-api-client
 */
class ErrorBase extends Error {
	/** @lends module:classeur-api-client.ErrorBase */
	constructor(error, data, response) {
		if ( _.isString(error) ) {
			error = new Error(error)
		}
		if ( ! _.isError(error) ) {
			throw new Error(`Expected string or Error object, got ${error} instead`)
		}

		super(error)
		let message = _.replace(this.message, /^\s*Error:\s*/, '')
		_.merge(this, data)
		_.merge(this, error)
		this.message = message

		if (response) {
			/**
			 * JSON decoding failures will raise a {@link module:classeur-api-client.ClientError} instead.
			 * This field is only present if its value could be derived from the error object returned from the REST client.
			 * @summary The JSON-decoded response sent back from the server with the error.
			 * @deprecated  This is usually not useful the properties `reason` and `status` are usually of more interest than `response`, and they are directly readable from instances of this class.
			 * @type {Object?}
			 * @instance
			 * @readonly
			 */
			this.response = response
		}

		if ( data ) {
			/**
			 * JSON decoding failures will raise a {@link module:classeur-api-client.ClientError} instead.
			 * This field is only present if its value could be derived from the error object returned from the REST client.
			 * @summary The JSON-decoded response-data sent back from the server with the error.
			 * @deprecated  This is usually not useful the properties `reason` and `status` are usually all that is contained in `data`, and they are directly readable from instances of this class.
			 * @type {Object?}
			 * @instance
			 * @readonly
			 */
			this.data = data
		}

		if ( _.property('body.reason')(error) ) {
			/**
			 * This is usually the best source for detailed failure/debugging information.
			 * This field is only present if its value could be derived from the error object returned from the REST client.
			 * @summary Information sent back by the server indicating why a request failed. 
			 * @type {String?}
			 * @instance
			 * @readonly
			 */
			this.reason = error.body.reason
		}
		
		if ( _.property('body.error')(error) ) {
		 	/**
		 	 * This field is only present if its value could be derived from the error object returned from the REST client.
			 * Usually a short word or phrase explaining what a given error code means in HTTP convention.
		 	 * @summary English version of [ServerError#status]{@link module:classeur-api-client.ServerError#status}.
		 	 * @type {String?}
		 	 * @instance
		 	 * @readonly
		 	 */
		 	this.shortReason = error.body.error
		}
	}
}

// Bizarre link formatting for the @extends argument is due to jsdoc parser limitations:
// it doesn't support spaces in @extends, even if those spaces are in a @link location field.
/**
 * Object representation of an error that occurred on the server while performing a REST API operation.
 * This is a thin wrapper around a [flashheart](https://github.com/bbc/flashheart) error object, and exists as a separate class only to deliniate server-caused errors from client-caused errors.
 * @deprecated  You should never need to construct one of these directly they will be provided to callbacks for REST API operations as the first argument if they occur.
 * @extends [ErrorBase](./module-classeur-api-client.ErrorBase.html)
 * @memberof module:classeur-api-client
 * @see The [flashheart documentation](https://github.com/bbc/flashheart#errors) for more info on possible Error contents.
 * @example
 * const ServerError = require('classeur-api-client').ServerError
 * myClient.getFile('nonexistent', (error) => {
 *     if ( error instanceof ServerError ) {
 *         console.log(error.message) // e.g. 'Server error: Received HTTP code 403 for GET https:// ...'
 *         console.log(error.reason) // e.g. 'file_is_not_readable'
 *         console.log(error.status) // e.g. 403
 *         throw error
 *     }
 * })
 */
class ServerError extends ErrorBase {
	/** @lends module:classeur-api-client.ServerError */
	constructor(error, body, response) {
		if ( ! _.property('statusCode')(error) ) {
			throw new Error(`Expected error object to have 'statusCode' property: ${error}`)
		}
		if ( ! _.isNumber(error.statusCode) ) {
			throw new Error(`Expected error.statusCode to be numeric, got ${error.statusCode} instead`)
		}
		super(error, body, response)
		this.message = 'Server error: ' + this.message
		/**
		 * If headers were returned from the server with the error, this object will contain their contents. It is supplied directly by the flashheart REST client library.
		 * @memberOf module:classeur-api-client.ServerError
		 * @member {Object} headers
		 * @instance
		 * @readonly
		 * @see The [flashheart documentation]{@link https://github.com/bbc/flashheart#errors} for more information about this field's contents.
		 */

		/**
		 * HTTP status code of the error.
		 * @type {Number}
		 * @instance
		 * @readonly
		 */
		this.status = error.statusCode		
	}
}

/**
 * Object representation of an error that occurred on the client during or after the processing REST API operation.
 * Example causes if a client error (and valid values for the `message` property) include:
 * - Request aborted
 * - Request timed out
 * - Corrupt/non-JSON-decodable content received
 *
 * ClientError instances are usually thin wrappers around [flashheart](https://github.com/bbc/flashheart) error objects, and exist only to deliniate server-caused errors from client-caused errors. ClientErrors may not contain all/most error fields supplied by flashheart, though, since ClientErrors may occur before error metadata is returned from the server.
 * @deprecated You should never need to construct one of these directly they will be provided to callbacks for REST API operations as the first argument if they occur.
 * @extends [ErrorBase](./module-classeur-api-client.ErrorBase.html)
 * @memberof module:classeur-api-client
 * @see The [flashheart documentation](https://github.com/bbc/flashheart#errors) for more info on possible Error contents.
 * @example
 * const ClientError = require('classeur-api-client').ClientError
 * myClient.getFile('nonexistent', (error) => {
 *     if ( error instanceof ClientError ) {
 *         console.log(error.message) // e.g. 'JSON decoding failed!'
 *         throw error
 *     }
 * })
 */
class ClientError extends ErrorBase {
	/** @lends module:classeur-api-client.ClientError*/
	constructor(error, body, response, clntimeout) {
		super(error, body, response)
		this.message = 'Client error: ' + this.message

		if ( this.message.includes('ETIMEDOUT') ) {
			/**
			 * If a timeout occurred, this property will be set to the timeout threshold that was exceeded, in milliseconds. This property will only be set if a ClientError occurred due to a timeout.
			 * @type {Number?}
			 * @instance
			 * @readonly
			 */
		    this.timeout = clntimeout
		}
	}
}

module.exports.ServerError = ServerError
module.exports.ClientError = ClientError