2023-11-27 06:33:21 -08:00
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
|
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
|
|
|
|
|
|
import { parseString } from 'xml2js';
|
2023-11-28 07:47:28 -08:00
|
|
|
import type {
|
|
|
|
INode,
|
|
|
|
JsonObject,
|
|
|
|
IDataObject,
|
|
|
|
IStatusCodeMessages,
|
|
|
|
Functionality,
|
|
|
|
} from '../Interfaces';
|
2023-11-27 06:33:21 -08:00
|
|
|
import { NodeError } from './abstract/node.error';
|
|
|
|
import { removeCircularRefs } from '../utils';
|
2023-12-07 07:57:02 -08:00
|
|
|
import type { ReportingOptions } from './application.error';
|
2024-02-02 03:22:26 -08:00
|
|
|
import { AxiosError } from 'axios';
|
2024-03-07 08:08:01 -08:00
|
|
|
import {
|
|
|
|
NO_OP_NODE_TYPE,
|
|
|
|
UNKNOWN_ERROR_DESCRIPTION,
|
|
|
|
UNKNOWN_ERROR_MESSAGE,
|
|
|
|
UNKNOWN_ERROR_MESSAGE_CRED,
|
|
|
|
} from '../Constants';
|
2023-11-27 06:33:21 -08:00
|
|
|
|
|
|
|
export interface NodeOperationErrorOptions {
|
|
|
|
message?: string;
|
|
|
|
description?: string;
|
|
|
|
runIndex?: number;
|
|
|
|
itemIndex?: number;
|
2023-12-07 07:57:02 -08:00
|
|
|
level?: ReportingOptions['level'];
|
2023-11-27 06:33:21 -08:00
|
|
|
messageMapping?: { [key: string]: string }; // allows to pass custom mapping for error messages scoped to a node
|
2023-11-28 07:47:28 -08:00
|
|
|
functionality?: Functionality;
|
2024-02-12 08:32:27 -08:00
|
|
|
type?: string;
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
interface NodeApiErrorOptions extends NodeOperationErrorOptions {
|
|
|
|
message?: string;
|
|
|
|
httpCode?: string;
|
|
|
|
parseXml?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Top-level properties where an error message can be found in an API response.
|
|
|
|
*/
|
|
|
|
const ERROR_MESSAGE_PROPERTIES = [
|
|
|
|
'cause',
|
|
|
|
'error',
|
|
|
|
'message',
|
|
|
|
'Message',
|
|
|
|
'msg',
|
|
|
|
'messages',
|
|
|
|
'description',
|
|
|
|
'reason',
|
|
|
|
'detail',
|
|
|
|
'details',
|
|
|
|
'errors',
|
|
|
|
'errorMessage',
|
|
|
|
'errorMessages',
|
|
|
|
'ErrorMessage',
|
|
|
|
'error_message',
|
|
|
|
'_error_message',
|
|
|
|
'errorDescription',
|
|
|
|
'error_description',
|
|
|
|
'error_summary',
|
|
|
|
'title',
|
|
|
|
'text',
|
|
|
|
'field',
|
|
|
|
'err',
|
|
|
|
'type',
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Top-level properties where an HTTP error code can be found in an API response.
|
|
|
|
*/
|
|
|
|
const ERROR_STATUS_PROPERTIES = [
|
|
|
|
'statusCode',
|
|
|
|
'status',
|
|
|
|
'code',
|
|
|
|
'status_code',
|
|
|
|
'errorCode',
|
|
|
|
'error_code',
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Properties where a nested object can be found in an API response.
|
|
|
|
*/
|
|
|
|
const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data'];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Descriptive messages for common HTTP status codes
|
|
|
|
* this is used by NodeApiError class
|
|
|
|
*/
|
|
|
|
const STATUS_CODE_MESSAGES: IStatusCodeMessages = {
|
|
|
|
'4XX': 'Your request is invalid or could not be processed by the service',
|
|
|
|
'400': 'Bad request - please check your parameters',
|
|
|
|
'401': 'Authorization failed - please check your credentials',
|
|
|
|
'402': 'Payment required - perhaps check your payment details?',
|
|
|
|
'403': 'Forbidden - perhaps check your credentials?',
|
|
|
|
'404': 'The resource you are requesting could not be found',
|
|
|
|
'405': 'Method not allowed - please check you are using the right HTTP method',
|
|
|
|
'429': 'The service is receiving too many requests from you',
|
|
|
|
|
|
|
|
'5XX': 'The service failed to process your request',
|
|
|
|
'500': 'The service was not able to process your request',
|
|
|
|
'502': 'Bad gateway - the service failed to handle your request',
|
|
|
|
'503':
|
|
|
|
'Service unavailable - try again later or consider setting this node to retry automatically (in the node settings)',
|
|
|
|
'504': 'Gateway timed out - perhaps try again later?',
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class for instantiating an error in an API response, e.g. a 404 Not Found response,
|
|
|
|
* with an HTTP error code, an error message and a description.
|
|
|
|
*/
|
|
|
|
export class NodeApiError extends NodeError {
|
|
|
|
httpCode: string | null;
|
|
|
|
|
2024-04-10 05:02:02 -07:00
|
|
|
// eslint-disable-next-line complexity
|
2023-11-27 06:33:21 -08:00
|
|
|
constructor(
|
|
|
|
node: INode,
|
2023-11-30 05:44:10 -08:00
|
|
|
errorResponse: JsonObject,
|
2023-11-27 06:33:21 -08:00
|
|
|
{
|
|
|
|
message,
|
|
|
|
description,
|
|
|
|
httpCode,
|
|
|
|
parseXml,
|
|
|
|
runIndex,
|
|
|
|
itemIndex,
|
2023-12-07 07:57:02 -08:00
|
|
|
level,
|
2023-11-28 07:47:28 -08:00
|
|
|
functionality,
|
2023-11-27 06:33:21 -08:00
|
|
|
messageMapping,
|
|
|
|
}: NodeApiErrorOptions = {},
|
|
|
|
) {
|
2023-11-30 05:44:10 -08:00
|
|
|
super(node, errorResponse);
|
2023-11-27 06:33:21 -08:00
|
|
|
|
2024-03-07 08:08:01 -08:00
|
|
|
this.addToMessages(errorResponse.message as string);
|
|
|
|
|
2024-02-02 03:22:26 -08:00
|
|
|
if (!httpCode && errorResponse instanceof AxiosError) {
|
|
|
|
httpCode = errorResponse.response?.status?.toString();
|
|
|
|
}
|
|
|
|
|
2023-11-27 06:33:21 -08:00
|
|
|
// only for request library error
|
2023-11-30 05:44:10 -08:00
|
|
|
if (errorResponse.error) {
|
|
|
|
removeCircularRefs(errorResponse.error as JsonObject);
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// if not description provided, try to find it in the error object
|
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2023-11-30 05:44:10 -08:00
|
|
|
if (
|
|
|
|
!description &&
|
|
|
|
(errorResponse.description || (errorResponse?.reason as IDataObject)?.description)
|
|
|
|
) {
|
2023-11-27 06:33:21 -08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2023-11-30 05:44:10 -08:00
|
|
|
this.description = (errorResponse.description ||
|
|
|
|
(errorResponse?.reason as IDataObject)?.description) as string;
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// if not message provided, try to find it in the error object or set description as message
|
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2023-11-30 05:44:10 -08:00
|
|
|
if (
|
|
|
|
!message &&
|
|
|
|
(errorResponse.message || (errorResponse?.reason as IDataObject)?.message || description)
|
|
|
|
) {
|
2023-11-27 06:33:21 -08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2023-11-30 05:44:10 -08:00
|
|
|
this.message = (errorResponse.message ||
|
2023-11-27 06:33:21 -08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2023-11-30 05:44:10 -08:00
|
|
|
(errorResponse?.reason as IDataObject)?.message ||
|
2023-11-27 06:33:21 -08:00
|
|
|
description) as string;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it's an error generated by axios
|
|
|
|
// look for descriptions in the response object
|
2023-11-30 05:44:10 -08:00
|
|
|
if (errorResponse.reason) {
|
|
|
|
const reason: IDataObject = errorResponse.reason as unknown as IDataObject;
|
2023-11-27 06:33:21 -08:00
|
|
|
|
|
|
|
if (reason.isAxiosError && reason.response) {
|
2023-11-30 05:44:10 -08:00
|
|
|
errorResponse = reason.response as JsonObject;
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set http code of this error
|
|
|
|
if (httpCode) {
|
|
|
|
this.httpCode = httpCode;
|
2024-03-07 08:08:01 -08:00
|
|
|
} else if (errorResponse.httpCode) {
|
|
|
|
this.httpCode = errorResponse.httpCode as string;
|
2023-11-27 06:33:21 -08:00
|
|
|
} else {
|
|
|
|
this.httpCode =
|
2023-11-30 05:44:10 -08:00
|
|
|
this.findProperty(errorResponse, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES) ?? null;
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
|
2023-12-07 07:57:02 -08:00
|
|
|
if (level) {
|
|
|
|
this.level = level;
|
2023-11-27 06:33:21 -08:00
|
|
|
} else if (this.httpCode?.charAt(0) !== '5') {
|
2023-12-07 07:57:02 -08:00
|
|
|
this.level = 'warning';
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
|
|
|
|
2024-03-07 08:08:01 -08:00
|
|
|
if (
|
|
|
|
errorResponse?.response &&
|
|
|
|
typeof errorResponse?.response === 'object' &&
|
|
|
|
!Array.isArray(errorResponse.response) &&
|
|
|
|
errorResponse.response.data &&
|
|
|
|
typeof errorResponse.response.data === 'object' &&
|
|
|
|
!Array.isArray(errorResponse.response.data)
|
|
|
|
) {
|
|
|
|
const data = errorResponse.response.data;
|
|
|
|
|
|
|
|
if (data.message) {
|
|
|
|
description = data.message as string;
|
|
|
|
} else if (data.error && ((data.error as IDataObject) || {}).message) {
|
|
|
|
description = (data.error as IDataObject).message as string;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.context.data = data;
|
|
|
|
}
|
|
|
|
|
2023-11-27 06:33:21 -08:00
|
|
|
// set description of this error
|
|
|
|
if (description) {
|
|
|
|
this.description = description;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.description) {
|
|
|
|
if (parseXml) {
|
2023-11-30 05:44:10 -08:00
|
|
|
this.setDescriptionFromXml(errorResponse.error as string);
|
2023-11-27 06:33:21 -08:00
|
|
|
} else {
|
|
|
|
this.description = this.findProperty(
|
2023-11-30 05:44:10 -08:00
|
|
|
errorResponse,
|
2023-11-27 06:33:21 -08:00
|
|
|
ERROR_MESSAGE_PROPERTIES,
|
|
|
|
ERROR_NESTING_PROPERTIES,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-07 08:08:01 -08:00
|
|
|
// set message if provided
|
|
|
|
// set default message based on http code
|
|
|
|
// or use raw error message
|
2023-11-27 06:33:21 -08:00
|
|
|
if (message) {
|
|
|
|
this.message = message;
|
|
|
|
} else {
|
|
|
|
this.setDefaultStatusCodeMessage();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if message and description are the same, unset redundant description
|
|
|
|
if (this.message === this.description) {
|
|
|
|
this.description = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if message contain common error code set descriptive message and update description
|
2024-03-07 08:08:01 -08:00
|
|
|
[this.message, this.messages] = this.setDescriptiveErrorMessage(
|
2023-11-27 06:33:21 -08:00
|
|
|
this.message,
|
2024-03-07 08:08:01 -08:00
|
|
|
this.messages,
|
2023-11-27 06:33:21 -08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
|
|
this.httpCode ||
|
2023-11-30 05:44:10 -08:00
|
|
|
(errorResponse?.code as string) ||
|
|
|
|
((errorResponse?.reason as JsonObject)?.code as string) ||
|
2023-11-27 06:33:21 -08:00
|
|
|
undefined,
|
|
|
|
messageMapping,
|
|
|
|
);
|
|
|
|
|
2023-11-28 07:47:28 -08:00
|
|
|
if (functionality !== undefined) this.context.functionality = functionality;
|
2023-11-27 06:33:21 -08:00
|
|
|
if (runIndex !== undefined) this.context.runIndex = runIndex;
|
|
|
|
if (itemIndex !== undefined) this.context.itemIndex = itemIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
private setDescriptionFromXml(xml: string) {
|
|
|
|
parseString(xml, { explicitArray: false }, (_, result) => {
|
|
|
|
if (!result) return;
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
|
|
const topLevelKey = Object.keys(result)[0];
|
|
|
|
this.description = this.findProperty(
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
|
|
result[topLevelKey],
|
|
|
|
ERROR_MESSAGE_PROPERTIES,
|
|
|
|
['Error'].concat(ERROR_NESTING_PROPERTIES),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the error's message based on the HTTP status code.
|
|
|
|
*/
|
|
|
|
private setDefaultStatusCodeMessage() {
|
|
|
|
// Set generic error message for 502 Bad Gateway
|
|
|
|
if (!this.httpCode && this.message && this.message.toLowerCase().includes('bad gateway')) {
|
|
|
|
this.httpCode = '502';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.httpCode) {
|
|
|
|
this.httpCode = null;
|
2024-03-07 08:08:01 -08:00
|
|
|
|
|
|
|
if (!this.message) {
|
|
|
|
if (this.description) {
|
|
|
|
this.message = this.description;
|
|
|
|
this.description = undefined;
|
|
|
|
} else {
|
|
|
|
this.message = UNKNOWN_ERROR_MESSAGE;
|
|
|
|
this.description = UNKNOWN_ERROR_DESCRIPTION;
|
|
|
|
}
|
|
|
|
}
|
2023-11-27 06:33:21 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STATUS_CODE_MESSAGES[this.httpCode]) {
|
2024-03-07 08:08:01 -08:00
|
|
|
this.addToMessages(this.message);
|
2023-11-27 06:33:21 -08:00
|
|
|
this.message = STATUS_CODE_MESSAGES[this.httpCode];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (this.httpCode.charAt(0)) {
|
|
|
|
case '4':
|
2024-03-07 08:08:01 -08:00
|
|
|
this.addToMessages(this.message);
|
2023-11-27 06:33:21 -08:00
|
|
|
this.message = STATUS_CODE_MESSAGES['4XX'];
|
|
|
|
break;
|
|
|
|
case '5':
|
2024-03-07 08:08:01 -08:00
|
|
|
this.addToMessages(this.message);
|
2023-11-27 06:33:21 -08:00
|
|
|
this.message = STATUS_CODE_MESSAGES['5XX'];
|
|
|
|
break;
|
|
|
|
default:
|
2024-03-07 08:08:01 -08:00
|
|
|
if (!this.message) {
|
|
|
|
if (this.description) {
|
|
|
|
this.message = this.description;
|
|
|
|
this.description = undefined;
|
|
|
|
} else {
|
|
|
|
this.message = UNKNOWN_ERROR_MESSAGE;
|
|
|
|
this.description = UNKNOWN_ERROR_DESCRIPTION;
|
|
|
|
}
|
|
|
|
}
|
2023-11-27 06:33:21 -08:00
|
|
|
}
|
2024-03-07 02:46:07 -08:00
|
|
|
if (this.node.type === NO_OP_NODE_TYPE && this.message === UNKNOWN_ERROR_MESSAGE) {
|
2023-11-27 06:33:21 -08:00
|
|
|
this.message = `${UNKNOWN_ERROR_MESSAGE_CRED} - ${this.httpCode}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|