mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
175 lines
6.3 KiB
TypeScript
175 lines
6.3 KiB
TypeScript
import { isTraversableObject } from '../../utils';
|
|
import type { IDataObject, INode, JsonObject } from '../..';
|
|
import { ExecutionBaseError } from './execution-base.error';
|
|
|
|
/**
|
|
* Descriptive messages for common errors.
|
|
*/
|
|
const COMMON_ERRORS: IDataObject = {
|
|
// nodeJS errors
|
|
ECONNREFUSED: 'The service refused the connection - perhaps it is offline',
|
|
ECONNRESET:
|
|
'The connection to the server wes closed unexpectedly, perhaps it is offline. You can retry request immidiately or wait and retry later.',
|
|
ENOTFOUND:
|
|
'The connection cannot be established, this usually occurs due to an incorrect host(domain) value',
|
|
ETIMEDOUT:
|
|
"The connection timed out, consider setting 'Retry on Fail' option in the node settings",
|
|
ERRADDRINUSE:
|
|
'The port is already occupied by some other application, if possible change the port or kill the application that is using it',
|
|
EADDRNOTAVAIL: 'The address is not available, ensure that you have the right IP address',
|
|
ECONNABORTED: 'The connection was aborted, perhaps the server is offline',
|
|
EHOSTUNREACH: 'The host is unreachable, perhaps the server is offline',
|
|
EAI_AGAIN: 'The DNS server returned an error, perhaps the server is offline',
|
|
ENOENT: 'The file or directory does not exist',
|
|
EISDIR: 'The file path expected but a given path is a directory',
|
|
ENOTDIR: 'The directory path expected but a given path is a file',
|
|
EACCES: 'Forbidden by access permissions, make sure you have the right permissions',
|
|
EEXIST: 'The file or directory already exists',
|
|
EPERM: 'Operation not permitted, make sure you have the right permissions',
|
|
// other errors
|
|
GETADDRINFO: 'The server closed the connection unexpectedly',
|
|
};
|
|
|
|
/**
|
|
* Base class for specific NodeError-types, with functionality for finding
|
|
* a value recursively inside an error object.
|
|
*/
|
|
export abstract class NodeError extends ExecutionBaseError {
|
|
constructor(
|
|
readonly node: INode,
|
|
error: Error | JsonObject,
|
|
) {
|
|
const isError = error instanceof Error;
|
|
const message = isError ? error.message : '';
|
|
const options = isError ? { cause: error } : { errorResponse: error };
|
|
super(message, options);
|
|
|
|
if (error instanceof NodeError) {
|
|
this.tags.reWrapped = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds property through exploration based on potential keys and traversal keys.
|
|
* Depth-first approach.
|
|
*
|
|
* This method iterates over `potentialKeys` and, if the value at the key is a
|
|
* truthy value, the type of the value is checked:
|
|
* (1) if a string or number, the value is returned as a string; or
|
|
* (2) if an array,
|
|
* its string or number elements are collected as a long string,
|
|
* its object elements are traversed recursively (restart this function
|
|
* with each object as a starting point), or
|
|
* (3) if it is an object, it traverses the object and nested ones recursively
|
|
* based on the `potentialKeys` and returns a string if found.
|
|
*
|
|
* If nothing found via `potentialKeys` this method iterates over `traversalKeys` and
|
|
* if the value at the key is a traversable object, it restarts with the object as the
|
|
* new starting point (recursion).
|
|
* If nothing found for any of the `traversalKeys`, exploration continues with remaining
|
|
* `traversalKeys`.
|
|
*
|
|
* Otherwise, if all the paths have been exhausted and no value is eligible, `null` is
|
|
* returned.
|
|
*
|
|
*/
|
|
protected findProperty(
|
|
jsonError: JsonObject,
|
|
potentialKeys: string[],
|
|
traversalKeys: string[] = [],
|
|
): string | null {
|
|
for (const key of potentialKeys) {
|
|
const value = jsonError[key];
|
|
if (value) {
|
|
if (typeof value === 'string') return value;
|
|
if (typeof value === 'number') return value.toString();
|
|
if (Array.isArray(value)) {
|
|
const resolvedErrors: string[] = value
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
.map((jsonError) => {
|
|
if (typeof jsonError === 'string') return jsonError;
|
|
if (typeof jsonError === 'number') return jsonError.toString();
|
|
if (isTraversableObject(jsonError)) {
|
|
return this.findProperty(jsonError, potentialKeys);
|
|
}
|
|
return null;
|
|
})
|
|
.filter((errorValue): errorValue is string => errorValue !== null);
|
|
|
|
if (resolvedErrors.length === 0) {
|
|
return null;
|
|
}
|
|
return resolvedErrors.join(' | ');
|
|
}
|
|
if (isTraversableObject(value)) {
|
|
const property = this.findProperty(value, potentialKeys);
|
|
if (property) {
|
|
return property;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const key of traversalKeys) {
|
|
const value = jsonError[key];
|
|
if (isTraversableObject(value)) {
|
|
const property = this.findProperty(value, potentialKeys, traversalKeys);
|
|
if (property) {
|
|
return property;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Set descriptive error message if code is provided or if message contains any of the common errors,
|
|
* update description to include original message plus the description
|
|
*/
|
|
protected setDescriptiveErrorMessage(
|
|
message: string,
|
|
description: string | undefined | null,
|
|
code?: string | null,
|
|
messageMapping?: { [key: string]: string },
|
|
) {
|
|
let newMessage = message;
|
|
let newDescription = description as string;
|
|
|
|
if (messageMapping) {
|
|
for (const [mapKey, mapMessage] of Object.entries(messageMapping)) {
|
|
if ((message || '').toUpperCase().includes(mapKey.toUpperCase())) {
|
|
newMessage = mapMessage;
|
|
newDescription = this.updateDescription(message, description);
|
|
break;
|
|
}
|
|
}
|
|
if (newMessage !== message) {
|
|
return [newMessage, newDescription];
|
|
}
|
|
}
|
|
|
|
// if code is provided and it is in the list of common errors set the message and return early
|
|
if (code && COMMON_ERRORS[code.toUpperCase()]) {
|
|
newMessage = COMMON_ERRORS[code] as string;
|
|
newDescription = this.updateDescription(message, description);
|
|
return [newMessage, newDescription];
|
|
}
|
|
|
|
// check if message contains any of the common errors and set the message and description
|
|
for (const [errorCode, errorDescriptiveMessage] of Object.entries(COMMON_ERRORS)) {
|
|
if ((message || '').toUpperCase().includes(errorCode.toUpperCase())) {
|
|
newMessage = errorDescriptiveMessage as string;
|
|
newDescription = this.updateDescription(message, description);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [newMessage, newDescription];
|
|
}
|
|
|
|
protected updateDescription(message: string, description: string | undefined | null) {
|
|
return `${message}${description ? ` - ${description}` : ''}`;
|
|
}
|
|
}
|