diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts index f9e2382896000..b672861327f3d 100644 --- a/packages/workflow/src/NodeErrors.ts +++ b/packages/workflow/src/NodeErrors.ts @@ -270,7 +270,8 @@ const STATUS_CODE_MESSAGES: IStatusCodeMessages = { '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 - perhaps try again later?', + '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?', ECONNREFUSED: 'The service refused the connection - perhaps it is offline', @@ -298,15 +299,51 @@ export class NodeApiError extends NodeError { { message, description, httpCode, parseXml, runIndex, itemIndex }: NodeApiErrorOptions = {}, ) { super(node, error); + if (error.error) { // only for request library error this.removeCircularRefs(error.error as JsonObject); } + if ((!message && (error.message || (error?.reason as IDataObject)?.message)) || description) { + this.message = (error.message ?? + (error?.reason as IDataObject)?.message ?? + description) as string; + } + + if (!description && (error.description || (error?.reason as IDataObject)?.description)) { + this.description = (error.description ?? + (error?.reason as IDataObject)?.description) as string; + } + + if (!httpCode && !message && error.status === 'rejected') { + httpCode = 'ECONNREFUSED'; + + const originalMessage = this.message; + if (!description && originalMessage) { + this.description = `${originalMessage} ${this.description ?? ''}`; + } + } + + if ( + !httpCode && + !message && + this.message && + this.message.toLowerCase().includes('bad gateway') + ) { + httpCode = '502'; + + const originalMessage = this.message; + if (!description) { + this.description = `${originalMessage}; ${this.description ?? ''}`; + } + } + // if it's an error generated by axios // look for descriptions in the response object if (error.reason) { const reason: IDataObject = error.reason as unknown as IDataObject; + if (reason.isAxiosError && reason.response) { error = reason.response as JsonObject; } @@ -319,7 +356,12 @@ export class NodeApiError extends NodeError { return; } - this.httpCode = this.findProperty(error, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES); + if (httpCode) { + this.httpCode = httpCode; + } else { + this.httpCode = this.findProperty(error, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES); + } + this.setMessage(); if (parseXml) { @@ -356,7 +398,8 @@ export class NodeApiError extends NodeError { private setMessage() { if (!this.httpCode) { this.httpCode = null; - this.message = UNKNOWN_ERROR_MESSAGE; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.message = this.message || this.description || UNKNOWN_ERROR_MESSAGE; return; } @@ -373,7 +416,8 @@ export class NodeApiError extends NodeError { this.message = STATUS_CODE_MESSAGES['5XX']; break; default: - this.message = this.message || UNKNOWN_ERROR_MESSAGE; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.message = this.message || this.description || UNKNOWN_ERROR_MESSAGE; } if (this.node.type === 'n8n-nodes-base.noOp' && this.message === UNKNOWN_ERROR_MESSAGE) { this.message = `${UNKNOWN_ERROR_MESSAGE_CRED} - ${this.httpCode}`; diff --git a/packages/workflow/test/NodeErrors.test.ts b/packages/workflow/test/NodeErrors.test.ts new file mode 100644 index 0000000000000..05556f4748d88 --- /dev/null +++ b/packages/workflow/test/NodeErrors.test.ts @@ -0,0 +1,80 @@ +import type { INode } from '../src/Interfaces'; +import { NodeApiError } from '../src/NodeErrors'; + +const node: INode = { + id: '1', + name: 'Postgres node', + typeVersion: 2, + type: 'n8n-nodes-base.postgres', + position: [60, 760], + parameters: { + operation: 'executeQuery', + }, +}; + +describe('NodeErrors tests', () => { + it('should return unknown error message', () => { + const nodeApiError = new NodeApiError(node, {}); + + expect(nodeApiError.message).toEqual( + 'UNKNOWN ERROR - check the detailed error for more information', + ); + }); + + it('should return the error message', () => { + const nodeApiError = new NodeApiError(node, { message: 'test error message' }); + + expect(nodeApiError.message).toEqual('test error message'); + }); + + it('should return the error message defined in reason', () => { + const nodeApiError = new NodeApiError(node, { reason: { message: 'test error message' } }); + + expect(nodeApiError.message).toEqual('test error message'); + }); + + it('should return the error message defined in options', () => { + const nodeApiError = new NodeApiError(node, {}, { message: 'test error message' }); + + expect(nodeApiError.message).toEqual('test error message'); + }); + + it('should return description error message', () => { + const nodeApiError = new NodeApiError(node, { description: 'test error description' }); + + expect(nodeApiError.message).toEqual('test error description'); + }); + + it('should return description as error message defined in reason', () => { + const nodeApiError = new NodeApiError(node, { + reason: { description: 'test error description' }, + }); + + expect(nodeApiError.message).toEqual('test error description'); + }); + + it('should return description as error message defined in options', () => { + const nodeApiError = new NodeApiError(node, {}, { description: 'test error description' }); + + expect(nodeApiError.message).toEqual('test error description'); + }); + + it('should return default message for ECONNREFUSED', () => { + const nodeApiError = new NodeApiError(node, { + status: 'rejected', + message: 'ECONNREFUSED', + }); + + expect(nodeApiError.message).toEqual( + 'The service refused the connection - perhaps it is offline', + ); + }); + + it('should return default message for 502', () => { + const nodeApiError = new NodeApiError(node, { + message: '502 Bad Gateway', + }); + + expect(nodeApiError.message).toEqual('Bad gateway - the service failed to handle your request'); + }); +});