From 1f610b90f6ce4b9b203544ed5db2d17afdc1be00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 26 Oct 2022 11:55:39 +0200 Subject: [PATCH] feat: use ES2022 native error chaining to improve error reporting (#4431) feat: use ES2022 native error chaining --- packages/cli/src/ActiveWorkflowRunner.ts | 3 +- packages/cli/src/ExternalHooks.ts | 7 +- packages/core/src/ActiveWorkflows.ts | 6 +- packages/nodes-base/tsconfig.json | 2 +- packages/workflow/src/ExpressionError.ts | 3 +- packages/workflow/src/NodeErrors.ts | 88 ++++++++----------- packages/workflow/src/RoutingNode.ts | 12 +-- .../workflow/src/WorkflowActivationError.ts | 20 +++-- tsconfig.json | 2 +- 9 files changed, 69 insertions(+), 74 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 2c0e9435ba..dfaea975f4 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -717,8 +717,7 @@ export class ActiveWorkflowRunner { // Run Error Workflow if defined const activationError = new WorkflowActivationError( `There was a problem with the trigger node "${node.name}", for that reason did the workflow had to be deactivated`, - error, - node, + { cause: error, node }, ); this.executeErrorWorkflow(activationError, workflowData, mode); diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index d7a09957bc..501ea5e14f 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -50,8 +50,11 @@ class ExternalHooksClass implements IExternalHooksClass { const hookFile = require(hookFilePath) as IExternalHooksFileData; this.loadHooks(hookFile); } catch (error) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access - throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`); + throw new Error( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions + `Problem loading external hook file "${hookFilePath}": ${error.message}`, + { cause: error as Error }, + ); } } } diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index da31a3c621..c37eeba1cd 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -95,8 +95,7 @@ export class ActiveWorkflows { throw new WorkflowActivationError( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access `There was a problem activating the workflow: "${error.message}"`, - error, - triggerNode, + { cause: error as Error, node: triggerNode }, ); } } @@ -122,8 +121,7 @@ export class ActiveWorkflows { throw new WorkflowActivationError( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access `There was a problem activating the workflow: "${error.message}"`, - error, - pollNode, + { cause: error as Error, node: pollNode }, ); } } diff --git a/packages/nodes-base/tsconfig.json b/packages/nodes-base/tsconfig.json index 7efd786ab9..396a982982 100644 --- a/packages/nodes-base/tsconfig.json +++ b/packages/nodes-base/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "lib": ["dom", "es2020"], + "lib": ["dom", "es2020", "es2022.error"], "types": ["node", "jest"], // TODO: remove all options below this line "noImplicitReturns": false, diff --git a/packages/workflow/src/ExpressionError.ts b/packages/workflow/src/ExpressionError.ts index 2d025d1005..245034dfca 100644 --- a/packages/workflow/src/ExpressionError.ts +++ b/packages/workflow/src/ExpressionError.ts @@ -9,6 +9,7 @@ export class ExpressionError extends ExecutionBaseError { constructor( message: string, options?: { + cause?: Error; causeDetailed?: string; description?: string; descriptionTemplate?: string; @@ -22,7 +23,7 @@ export class ExpressionError extends ExecutionBaseError { type?: string; }, ) { - super(new Error(message)); + super(message, { cause: options?.cause }); if (options?.description !== undefined) { this.description = options.description; diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts index 44abd0b88e..ad921e5a33 100644 --- a/packages/workflow/src/NodeErrors.ts +++ b/packages/workflow/src/NodeErrors.ts @@ -56,29 +56,28 @@ const ERROR_STATUS_PROPERTIES = [ */ const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data']; +interface ExecutionBaseErrorOptions { + cause?: Error | JsonObject; +} + export abstract class ExecutionBaseError extends Error { description: string | null | undefined; - cause: Error | JsonObject; - timestamp: number; context: IDataObject = {}; lineNumber: number | undefined; - constructor(error: Error | ExecutionBaseError | JsonObject) { - super(); + constructor(message: string, { cause }: ExecutionBaseErrorOptions) { + const options = cause instanceof Error ? { cause } : {}; + super(message, options); + this.name = this.constructor.name; - this.cause = error; this.timestamp = Date.now(); - if (error.message) { - this.message = error.message as string; - } - - if (error instanceof ExecutionBaseError) { - this.context = error.context; + if (cause instanceof ExecutionBaseError) { + this.context = cause.context; } } } @@ -91,7 +90,8 @@ abstract class NodeError extends ExecutionBaseError { node: INode; constructor(node: INode, error: Error | JsonObject) { - super(error); + const message = error instanceof Error ? error.message : ''; + super(message, { cause: error }); this.node = node; } @@ -120,23 +120,23 @@ abstract class NodeError extends ExecutionBaseError { * */ protected findProperty( - error: JsonObject, + jsonError: JsonObject, potentialKeys: string[], traversalKeys: string[] = [], ): string | null { // eslint-disable-next-line no-restricted-syntax for (const key of potentialKeys) { - const value = error[key]; + 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 - .map((error) => { - if (typeof error === 'string') return error; - if (typeof error === 'number') return error.toString(); - if (this.isTraversableObject(error)) { - return this.findProperty(error, potentialKeys); + .map((jsonError) => { + if (typeof jsonError === 'string') return jsonError; + if (typeof jsonError === 'number') return jsonError.toString(); + if (this.isTraversableObject(jsonError)) { + return this.findProperty(jsonError, potentialKeys); } return null; }) @@ -158,7 +158,7 @@ abstract class NodeError extends ExecutionBaseError { // eslint-disable-next-line no-restricted-syntax for (const key of traversalKeys) { - const value = error[key]; + const value = jsonError[key]; if (this.isTraversableObject(value)) { const property = this.findProperty(value, potentialKeys, traversalKeys); if (property) { @@ -209,33 +209,27 @@ abstract class NodeError extends ExecutionBaseError { } } +interface NodeOperationErrorOptions { + description?: string; + runIndex?: number; + itemIndex?: number; +} + /** * Class for instantiating an operational error, e.g. an invalid credentials error. */ export class NodeOperationError extends NodeError { lineNumber: number | undefined; - constructor( - node: INode, - error: Error | string, - options?: { description?: string; runIndex?: number; itemIndex?: number }, - ) { + constructor(node: INode, error: Error | string, options: NodeOperationErrorOptions = {}) { if (typeof error === 'string') { error = new Error(error); } super(node, error); - if (options?.description) { - this.description = options.description; - } - - if (options?.runIndex !== undefined) { - this.context.runIndex = options.runIndex; - } - - if (options?.itemIndex !== undefined) { - this.context.itemIndex = options.itemIndex; - } + this.description = options.description; + this.context.runIndex = options.runIndex; + this.context.itemIndex = options.itemIndex; } } @@ -258,6 +252,12 @@ const STATUS_CODE_MESSAGES: IStatusCodeMessages = { const UNKNOWN_ERROR_MESSAGE = 'UNKNOWN ERROR - check the detailed error for more information'; +interface NodeApiErrorOptions extends NodeOperationErrorOptions { + message?: string; + httpCode?: string; + parseXml?: boolean; +} + /** * 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. @@ -268,21 +268,7 @@ export class NodeApiError extends NodeError { constructor( node: INode, error: JsonObject, - { - message, - description, - httpCode, - parseXml, - runIndex, - itemIndex, - }: { - message?: string; - description?: string; - httpCode?: string; - parseXml?: boolean; - runIndex?: number; - itemIndex?: number; - } = {}, + { message, description, httpCode, parseXml, runIndex, itemIndex }: NodeApiErrorOptions = {}, ) { super(node, error); if (error.error) { diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index 131e68431a..1c645923c0 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -277,12 +277,12 @@ export class RoutingNode { }; }); }); - } catch (e) { - throw new NodeOperationError( - this.node, - `The rootProperty "${action.properties.property}" could not be found on item.`, - { runIndex, itemIndex }, - ); + } catch (error) { + throw new NodeOperationError(this.node, error, { + runIndex, + itemIndex, + description: `The rootProperty "${action.properties.property}" could not be found on item.`, + }); } } if (action.type === 'limit') { diff --git a/packages/workflow/src/WorkflowActivationError.ts b/packages/workflow/src/WorkflowActivationError.ts index dfaa6db4d3..f2e182afe9 100644 --- a/packages/workflow/src/WorkflowActivationError.ts +++ b/packages/workflow/src/WorkflowActivationError.ts @@ -1,19 +1,27 @@ import { INode } from './Interfaces'; import { ExecutionBaseError } from './NodeErrors'; +interface WorkflowActivationErrorOptions { + cause?: Error; + node?: INode; +} + /** * Class for instantiating an workflow activation error */ export class WorkflowActivationError extends ExecutionBaseError { node: INode | undefined; - constructor(message: string, error: Error, node?: INode) { - super(error); + constructor(message: string, { cause, node }: WorkflowActivationErrorOptions) { + let error = cause as Error; + if (cause instanceof ExecutionBaseError) { + error = new Error(cause.message); + error.constructor = cause.constructor; + error.name = cause.name; + error.stack = cause.stack; + } + super(message, { cause: error }); this.node = node; - this.cause = { - message: error.message, - stack: error.stack as string, - }; this.message = message; } } diff --git a/tsconfig.json b/tsconfig.json index 953ada389c..986eb63ac6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "module": "commonjs", "moduleResolution": "node", "target": "es2019", - "lib": ["es2019", "es2020"], + "lib": ["es2019", "es2020", "es2022.error"], "removeComments": true, "useUnknownInCatchVariables": true, "forceConsistentCasingInFileNames": true,